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