xref: /freebsd/stand/common/install.c (revision 8aac90f18aef7c9eea906c3ff9a001ca7b94f375)
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 
39 extern struct in_addr servip;
40 
41 extern int pkgfs_init(const char *, struct fs_ops *);
42 extern void pkgfs_cleanup(void);
43 
44 COMMAND_SET(install, "install", "install software package", command_install);
45 
46 static char *inst_kernel;
47 static char **inst_modules;
48 static char *inst_rootfs;
49 static char *inst_loader_rc;
50 
51 static int
52 setpath(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 
70 static int
71 setmultipath(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 
101 static int
102 read_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 
155 static void
156 cleanup(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  */
187 static int
188 install(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 
379 static int
380 command_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