xref: /freebsd/stand/common/install.c (revision 4d846d26)
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 
42 extern struct in_addr servip;
43 
44 extern int pkgfs_init(const char *, struct fs_ops *);
45 extern void pkgfs_cleanup(void);
46 
47 COMMAND_SET(install, "install", "install software package", command_install);
48 
49 static char *inst_kernel;
50 static char **inst_modules;
51 static char *inst_rootfs;
52 static char *inst_loader_rc;
53 
54 static int
55 setpath(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 
73 static int
74 setmultipath(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 
104 static int
105 read_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 
158 static void
159 cleanup(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  */
190 static int
191 install(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 
382 static int
383 command_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