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