1/*-
2 * Copyright (c) 2008-2014, Juniper Networks, Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD$");
29
30#include <sys/param.h>
31#include <sys/socket.h>
32#include <net/if.h>
33#include <netinet/in.h>
34#include <netinet/in_systm.h>
35
36#include <stand.h>
37#include <net.h>
38#include <string.h>
39
40#include "bootstrap.h"
41
42extern struct in_addr servip;
43
44extern int pkgfs_init(const char *, struct fs_ops *);
45extern void pkgfs_cleanup(void);
46
47COMMAND_SET(install, "install", "install software package", command_install);
48
49static char *inst_kernel;
50static char **inst_modules;
51static char *inst_rootfs;
52static char *inst_loader_rc;
53
54static int
55setpath(char **what, char *val)
56{
57	char *path;
58	size_t len;
59	int rel;
60
61	len = strlen(val) + 1;
62	rel = (val[0] != '/') ? 1 : 0;
63	path = malloc(len + rel);
64	if (path == NULL)
65		return (ENOMEM);
66	path[0] = '/';
67	strcpy(path + rel, val);
68
69	*what = path;
70	return (0);
71}
72
73static int
74setmultipath(char ***what, char *val)
75{
76	char *s, *v;
77	int count, error, idx;
78
79	count = 0;
80	v = val;
81	do {
82		count++;
83		s = strchr(v, ',');
84		v = (s == NULL) ? NULL : s + 1;
85	} while (v != NULL);
86
87	*what = calloc(count + 1, sizeof(char *));
88	if (*what == NULL)
89		return (ENOMEM);
90
91	for (idx = 0; idx < count; idx++) {
92		s = strchr(val, ',');
93		if (s != NULL)
94			*s++ = '\0';
95		error = setpath(*what + idx, val);
96		if (error)
97			return (error);
98		val = s;
99	}
100
101	return (0);
102}
103
104static int
105read_metatags(int fd)
106{
107	char buf[1024];
108	char *p, *tag, *val;
109	ssize_t fsize;
110	int error;
111
112	fsize = read(fd, buf, sizeof(buf));
113	if (fsize == -1)
114		return (errno);
115
116	/*
117	 * Assume that if we read a whole buffer worth of data, we
118	 * haven't read the entire file. In other words, the buffer
119	 * size must always be larger than the file size. That way
120	 * we can append a '\0' and use standard string operations.
121	 * Return an error if this is not possible.
122	 */
123	if (fsize == sizeof(buf))
124		return (ENOMEM);
125
126	buf[fsize] = '\0';
127	error = 0;
128	tag = buf;
129	while (!error && *tag != '\0') {
130		val = strchr(tag, '=');
131		if (val == NULL) {
132			error = EINVAL;
133			break;
134		}
135		*val++ = '\0';
136		p = strchr(val, '\n');
137		if (p == NULL) {
138			error = EINVAL;
139			break;
140		}
141		*p++ = '\0';
142
143		if (strcmp(tag, "KERNEL") == 0)
144			error = setpath(&inst_kernel, val);
145		else if (strcmp(tag, "MODULES") == 0)
146			error = setmultipath(&inst_modules, val);
147		else if (strcmp(tag, "ROOTFS") == 0)
148			error = setpath(&inst_rootfs, val);
149		else if (strcmp(tag, "LOADER_RC") == 0)
150			error = setpath(&inst_loader_rc, val);
151
152		tag = p;
153	}
154
155	return (error);
156}
157
158static void
159cleanup(void)
160{
161	u_int i;
162
163	if (inst_kernel != NULL) {
164		free(inst_kernel);
165		inst_kernel = NULL;
166	}
167	if (inst_modules != NULL) {
168		i = 0;
169		while (inst_modules[i] != NULL)
170			free(inst_modules[i++]);
171		free(inst_modules);
172		inst_modules = NULL;
173	}
174	if (inst_rootfs != NULL) {
175		free(inst_rootfs);
176		inst_rootfs = NULL;
177	}
178	if (inst_loader_rc != NULL) {
179		free(inst_loader_rc);
180		inst_loader_rc = NULL;
181	}
182	pkgfs_cleanup();
183}
184
185/*
186 * usage: install URL
187 * where: URL = tftp://[host]/<package>
188 *	or	file://[devname[:fstype]]/<package>
189 */
190static int
191install(char *pkgname)
192{
193	static char buf[256];
194	struct fs_ops *proto;
195	struct preloaded_file *fp;
196	char *e, *s, *currdev;
197	char *devname;
198	size_t devnamelen;
199	int error, fd, i, local;
200
201	s = strstr(pkgname, "://");
202	if (s == NULL)
203		goto invalid_url;
204
205	i = s - pkgname;
206	s += 3;
207	if (*s == '\0')
208		goto invalid_url;
209
210	devname = NULL;
211	devnamelen = 0;
212	proto = NULL;
213	local = 0;
214
215	if (i == 4 && !strncasecmp(pkgname, "tftp", i)) {
216		devname = "net0";
217		devnamelen = 4;
218		proto = &tftp_fsops;
219	} else if (i == 4 && !strncasecmp(pkgname, "file", i)) {
220		currdev = getenv("currdev");
221		local = 1;
222
223		if (*s == '/') {	/* file:/// */
224			if (devname == NULL)
225				devname = currdev;
226			if (devname == NULL)
227				devname = "disk1";
228		} else {		/* file://devname[:fstype]/ */
229			devname = s;
230			e = strchr(devname, '/');
231			if (!e)
232				goto invalid_url;
233			devnamelen = e - devname;
234			s = e;		/* consume devname */
235		}
236		if ((e = strchr(devname, ':')) != NULL) {
237			/* could be :fstype */
238			devnamelen = e - devname;
239			switch (e[1]) {
240			case '\0':	/* just currdev */
241				break;
242			case 'd':
243				proto = &dosfs_fsops;
244				break;
245#ifdef HOSTPROG
246			case 'h':
247				{
248					extern struct fs_ops host_fsops;
249
250					proto = &host_fsops;
251				}
252				break;
253#endif
254			case 'u':
255				proto = &ufs_fsops;
256				break;
257			}
258		}
259		if (proto == NULL && strncmp(devname, "disk", 4) == 0) {
260			proto = &dosfs_fsops;
261		}
262	}
263
264	if (devname == NULL)
265		goto invalid_url;
266
267	if (devnamelen == 0) {
268		/* default is currdev which ends with ':' */
269		devnamelen = strlen(devname);
270		if (devname[devnamelen - 1] == ':')
271			devnamelen--;
272	}
273
274	if (*s != '/' ) {
275		if (local)
276			goto invalid_url;
277
278		pkgname = strchr(s, '/');
279		if (pkgname == NULL)
280			goto invalid_url;
281
282		*pkgname = '\0';
283		servip.s_addr = inet_addr(s);
284		if (servip.s_addr == htonl(INADDR_NONE))
285			goto invalid_url;
286
287		setenv("serverip", inet_ntoa(servip), 1);
288
289		*pkgname = '/';
290	} else
291		pkgname = s;
292
293	i = snprintf(buf, sizeof(buf), "%.*s:%s",
294	    (int) devnamelen, devname, pkgname);
295	if (i >= (int) sizeof(buf)) {
296		command_errmsg = "package name too long";
297		return (CMD_ERROR);
298	}
299	setenv("install_package", buf, 1);
300
301	error = pkgfs_init(buf, proto);
302	if (error) {
303		command_errmsg = "cannot open package";
304		goto fail;
305	}
306
307	/*
308	 * Point of no return: unload anything that may have been
309	 * loaded and prune the environment from harmful variables.
310	 */
311	unload();
312	unsetenv("vfs.root.mountfrom");
313
314	/*
315	 * read the metatags file.
316	 */
317	fd = open("/metatags", O_RDONLY);
318	if (fd != -1) {
319		error = read_metatags(fd);
320		close(fd);
321		if (error) {
322			command_errmsg = "cannot load metatags";
323			goto fail;
324		}
325	}
326
327	s = (inst_kernel == NULL) ? "/kernel" : inst_kernel;
328	error = mod_loadkld(s, 0, NULL);
329	if (error) {
330		command_errmsg = "cannot load kernel from package";
331		goto fail;
332	}
333
334	/* If there is a loader.rc in the package, execute it */
335	s = (inst_loader_rc == NULL) ? "/loader.rc" : inst_loader_rc;
336	fd = open(s, O_RDONLY);
337	if (fd != -1) {
338		close(fd);
339		error = interp_include(s);
340		if (error == CMD_ERROR)
341			goto fail;
342	}
343
344	i = 0;
345	while (inst_modules != NULL && inst_modules[i] != NULL) {
346		error = mod_loadkld(inst_modules[i], 0, NULL);
347		if (error) {
348			command_errmsg = "cannot load module(s) from package";
349			goto fail;
350		}
351		i++;
352	}
353
354	s = (inst_rootfs == NULL) ? "/install.iso" : inst_rootfs;
355	if (file_loadraw(s, "mfs_root", 1) == NULL) {
356		error = errno;
357		command_errmsg = "cannot load root file system";
358		goto fail;
359	}
360
361	cleanup();
362
363	fp = file_findfile(NULL, NULL);
364	if (fp != NULL)
365		file_formats[fp->f_loader]->l_exec(fp);
366	error = CMD_ERROR;
367	command_errmsg = "unable to start installation";
368
369 fail:
370	sprintf(buf, "%s (error %d)", command_errmsg, error);
371	cleanup();
372	unload();
373	exclusive_file_system = NULL;
374	command_errmsg = buf;	/* buf is static. */
375	return (CMD_ERROR);
376
377 invalid_url:
378	command_errmsg = "invalid URL";
379	return (CMD_ERROR);
380}
381
382static int
383command_install(int argc, char *argv[])
384{
385	int argidx;
386
387	unsetenv("install_format");
388
389	argidx = 1;
390	while (1) {
391		if (argc == argidx) {
392			command_errmsg =
393			    "usage: install [--format] <URL>";
394			return (CMD_ERROR);
395		}
396		if (!strcmp(argv[argidx], "--format")) {
397			setenv("install_format", "yes", 1);
398			argidx++;
399			continue;
400		}
401		break;
402	}
403
404	return (install(argv[argidx]));
405}
406