1 /* ----------------------------------------------------------------------- *
2  *
3  *   Copyright 2007-2008 H. Peter Anvin - All Rights Reserved
4  *   Copyright 2009-2012 Intel Corporation; author: H. Peter Anvin
5  *
6  *   Permission is hereby granted, free of charge, to any person
7  *   obtaining a copy of this software and associated documentation
8  *   files (the "Software"), to deal in the Software without
9  *   restriction, including without limitation the rights to use,
10  *   copy, modify, merge, publish, distribute, sublicense, and/or
11  *   sell copies of the Software, and to permit persons to whom
12  *   the Software is furnished to do so, subject to the following
13  *   conditions:
14  *
15  *   The above copyright notice and this permission notice shall
16  *   be included in all copies or substantial portions of the Software.
17  *
18  *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19  *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20  *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21  *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22  *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23  *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24  *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25  *   OTHER DEALINGS IN THE SOFTWARE.
26  *
27  * ----------------------------------------------------------------------- */
28 
29 /*
30  * linux.c
31  *
32  * Sample module to load Linux kernels.  This module can also create
33  * a file out of the DHCP return data if running under PXELINUX.
34  *
35  * If -dhcpinfo is specified, the DHCP info is written into the file
36  * /dhcpinfo.dat in the initramfs.
37  *
38  * Usage: linux.c32 [-dhcpinfo] kernel arguments...
39  */
40 
41 #include <errno.h>
42 #include <stdbool.h>
43 #include <stdlib.h>
44 #include <stdio.h>
45 #include <string.h>
46 #include <console.h>
47 #include <syslinux/loadfile.h>
48 #include <syslinux/linux.h>
49 #include <syslinux/pxe.h>
50 
51 enum ldmode {
52     ldmode_raw,
53     ldmode_cpio,
54     ldmodes
55 };
56 
57 typedef int f_ldinitramfs(struct initramfs *, char *);
58 
59 const char *progname = "linux.c32";
60 
61 /* Find the last instance of a particular command line argument
62    (which should include the final =; do not use for boolean arguments) */
find_argument(char ** argv,const char * argument)63 static char *find_argument(char **argv, const char *argument)
64 {
65     int la = strlen(argument);
66     char **arg;
67     char *ptr = NULL;
68 
69     for (arg = argv; *arg; arg++) {
70 	if (!strncmp(*arg, argument, la))
71 	    ptr = *arg + la;
72     }
73 
74     return ptr;
75 }
76 
77 /* Find the next instance of a particular command line argument */
find_arguments(char ** argv,char ** ptr,const char * argument)78 static char **find_arguments(char **argv, char **ptr,
79 			     const char *argument)
80 {
81     int la = strlen(argument);
82     char **arg;
83 
84     for (arg = argv; *arg; arg++) {
85 	if (!strncmp(*arg, argument, la)) {
86 	    *ptr = *arg + la;
87 	    break;
88 	}
89     }
90 
91     /* Exhausted all arguments */
92     if (!*arg)
93 	return NULL;
94 
95     return arg;
96 }
97 
98 /* Search for a boolean argument; return its position, or 0 if not present */
find_boolean(char ** argv,const char * argument)99 static int find_boolean(char **argv, const char *argument)
100 {
101     char **arg;
102 
103     for (arg = argv; *arg; arg++) {
104 	if (!strcmp(*arg, argument))
105 	    return (arg - argv) + 1;
106     }
107 
108     return 0;
109 }
110 
111 /* Stitch together the command line from a set of argv's */
make_cmdline(char ** argv)112 static char *make_cmdline(char **argv)
113 {
114     char **arg;
115     size_t bytes;
116     char *cmdline, *p;
117 
118     bytes = 1;			/* Just in case we have a zero-entry cmdline */
119     for (arg = argv; *arg; arg++) {
120 	bytes += strlen(*arg) + 1;
121     }
122 
123     p = cmdline = malloc(bytes);
124     if (!cmdline)
125 	return NULL;
126 
127     for (arg = argv; *arg; arg++) {
128 	int len = strlen(*arg);
129 	memcpy(p, *arg, len);
130 	p[len] = ' ';
131 	p += len + 1;
132     }
133 
134     if (p > cmdline)
135 	p--;			/* Remove the last space */
136     *p = '\0';
137 
138     return cmdline;
139 }
140 
141 static f_ldinitramfs ldinitramfs_raw;
ldinitramfs_raw(struct initramfs * initramfs,char * fname)142 static int ldinitramfs_raw(struct initramfs *initramfs, char *fname)
143 {
144     return initramfs_load_archive(initramfs, fname);
145 }
146 
147 static f_ldinitramfs ldinitramfs_cpio;
ldinitramfs_cpio(struct initramfs * initramfs,char * fname)148 static int ldinitramfs_cpio(struct initramfs *initramfs, char *fname)
149 {
150     char *target_fname, *p;
151     int do_mkdir, unmangle, rc;
152 
153     /* Choose target_fname based on presence of "@" syntax */
154     target_fname = strchr(fname, '@');
155     if (target_fname) {
156 	/* Temporarily mangle */
157 	unmangle = 1;
158 	*target_fname++ = '\0';
159 
160 	/* Make parent directories? */
161 	do_mkdir = !!strchr(target_fname, '/');
162     } else {
163 	unmangle = 0;
164 
165 	/* Forget the source path */
166 	target_fname = fname;
167 	while ((p = strchr(target_fname, '/')))
168 	    target_fname = p + 1;
169 
170 	/* The user didn't specify a desired path */
171 	do_mkdir = 0;
172     }
173 
174     /*
175      * Load the file, encapsulate it with the desired path, make the
176      * parent directories if the desired path contains them, add to initramfs
177      */
178     rc = initramfs_load_file(initramfs, fname, target_fname, do_mkdir, 0755);
179 
180     /* Unmangle, if needed*/
181     if (unmangle)
182 	*--target_fname = '@';
183 
184     return rc;
185 }
186 
187 /* It only makes sense to call this function from main */
process_initramfs_args(char * arg,struct initramfs * initramfs,const char * kernel_name,enum ldmode mode,bool opt_quiet)188 static int process_initramfs_args(char *arg, struct initramfs *initramfs,
189 				  const char *kernel_name, enum ldmode mode,
190 				  bool opt_quiet)
191 {
192     const char *mode_msg;
193     f_ldinitramfs *ldinitramfs;
194     char *p;
195 
196     switch (mode) {
197     case ldmode_raw:
198 	mode_msg = "Loading";
199 	ldinitramfs = ldinitramfs_raw;
200 	break;
201     case ldmode_cpio:
202 	mode_msg = "Encapsulating";
203 	ldinitramfs = ldinitramfs_cpio;
204 	break;
205     case ldmodes:
206     default:
207 	return 1;
208     }
209 
210     do {
211 	p = strchr(arg, ',');
212 	if (p)
213 	    *p = '\0';
214 
215 	if (!opt_quiet)
216 	    printf("%s %s... ", mode_msg, arg);
217 	errno = 0;
218 	if (ldinitramfs(initramfs, arg)) {
219 	    if (opt_quiet)
220 		printf("Loading %s ", kernel_name);
221 	    printf("failed: ");
222 	    return 1;
223 	}
224 	if (!opt_quiet)
225 	    printf("ok\n");
226 
227 	if (p)
228 	    *p++ = ',';
229     } while ((arg = p));
230 
231     return 0;
232 }
233 
setup_data_file(struct setup_data * setup_data,uint32_t type,const char * filename,bool opt_quiet)234 static int setup_data_file(struct setup_data *setup_data,
235 			   uint32_t type, const char *filename,
236 			   bool opt_quiet)
237 {
238     if (!opt_quiet)
239 	printf("Loading %s... ", filename);
240 
241     if (setup_data_load(setup_data, type, filename)) {
242 	if (opt_quiet)
243 	    printf("Loading %s ", filename);
244 	printf("failed\n");
245 	return -1;
246     }
247 
248     if (!opt_quiet)
249 	printf("ok\n");
250 
251     return 0;
252 }
253 
main(int argc,char * argv[])254 int main(int argc, char *argv[])
255 {
256     const char *kernel_name;
257     struct initramfs *initramfs;
258     struct setup_data *setup_data;
259     char *cmdline;
260     char *boot_image;
261     void *kernel_data;
262     size_t kernel_len;
263     bool opt_dhcpinfo = false;
264     bool opt_quiet = false;
265     void *dhcpdata;
266     size_t dhcplen;
267     char **argp, **argl, *arg;
268 
269     (void)argc;
270     argp = argv + 1;
271 
272     while ((arg = *argp) && arg[0] == '-') {
273 	if (!strcmp("-dhcpinfo", arg)) {
274 	    opt_dhcpinfo = true;
275 	} else {
276 	    fprintf(stderr, "%s: unknown option: %s\n", progname, arg);
277 	    return 1;
278 	}
279 	argp++;
280     }
281 
282     if (!arg) {
283 	fprintf(stderr, "%s: missing kernel name\n", progname);
284 	return 1;
285     }
286 
287     kernel_name = arg;
288 
289     errno = 0;
290     boot_image = malloc(strlen(kernel_name) + 12);
291     if (!boot_image) {
292 	fprintf(stderr, "Error allocating BOOT_IMAGE string: ");
293 	goto bail;
294     }
295     strcpy(boot_image, "BOOT_IMAGE=");
296     strcpy(boot_image + 11, kernel_name);
297     /* argp now points to the kernel name, and the command line follows.
298        Overwrite the kernel name with the BOOT_IMAGE= argument, and thus
299        we have the final argument. */
300     *argp = boot_image;
301 
302     if (find_boolean(argp, "quiet"))
303 	opt_quiet = true;
304 
305     if (!opt_quiet)
306 	printf("Loading %s... ", kernel_name);
307     errno = 0;
308     if (loadfile(kernel_name, &kernel_data, &kernel_len)) {
309 	if (opt_quiet)
310 	    printf("Loading %s ", kernel_name);
311 	printf("failed: ");
312 	goto bail;
313     }
314     if (!opt_quiet)
315 	printf("ok\n");
316 
317     errno = 0;
318     cmdline = make_cmdline(argp);
319     if (!cmdline) {
320 	fprintf(stderr, "make_cmdline() failed: ");
321 	goto bail;
322     }
323 
324     /* Initialize the initramfs chain */
325     errno = 0;
326     initramfs = initramfs_init();
327     if (!initramfs) {
328 	fprintf(stderr, "initramfs_init() failed: ");
329 	goto bail;
330     }
331 
332     /* Process initramfs arguments */
333     if ((arg = find_argument(argp, "initrd="))) {
334 	if (process_initramfs_args(arg, initramfs, kernel_name, ldmode_raw,
335 				   opt_quiet))
336 	    goto bail;
337     }
338 
339     argl = argv;
340     while ((argl = find_arguments(argl, &arg, "initrd+="))) {
341 	argl++;
342 	if (process_initramfs_args(arg, initramfs, kernel_name, ldmode_raw,
343 				   opt_quiet))
344 	    goto bail;
345     }
346 
347     argl = argv;
348     while ((argl = find_arguments(argl, &arg, "initrdfile="))) {
349 	argl++;
350 	if (process_initramfs_args(arg, initramfs, kernel_name, ldmode_cpio,
351 				   opt_quiet))
352 	    goto bail;
353     }
354 
355     /* Append the DHCP info */
356     if (opt_dhcpinfo &&
357 	!pxe_get_cached_info(PXENV_PACKET_TYPE_DHCP_ACK, &dhcpdata, &dhcplen)) {
358 	errno = 0;
359 	if (initramfs_add_file(initramfs, dhcpdata, dhcplen, dhcplen,
360 			       "/dhcpinfo.dat", 0, 0755)) {
361 	    fprintf(stderr, "Unable to add DHCP info: ");
362 	    goto bail;
363 	}
364     }
365 
366     /* Handle dtb and eventually other setup data */
367     setup_data = setup_data_init();
368     if (!setup_data)
369 	goto bail;
370 
371     argl = argv;
372     while ((argl = find_arguments(argl, &arg, "dtb="))) {
373 	argl++;
374 	if (setup_data_file(setup_data, SETUP_DTB, arg, opt_quiet))
375 	    goto bail;
376     }
377 
378     argl = argv;
379     while ((argl = find_arguments(argl, &arg, "blob."))) {
380 	uint32_t type;
381 	char *ep;
382 
383 	argl++;
384 	type = strtoul(arg, &ep, 10);
385 	if (ep[0] != '=' || !ep[1])
386 	    continue;
387 
388 	if (!type)
389 	    continue;
390 
391 	if (setup_data_file(setup_data, type, ep+1, opt_quiet))
392 	    goto bail;
393     }
394 
395     /* This should not return... */
396     errno = 0;
397     syslinux_boot_linux(kernel_data, kernel_len, initramfs,
398 			setup_data, cmdline);
399     fprintf(stderr, "syslinux_boot_linux() failed: ");
400 
401 bail:
402     switch(errno) {
403     case ENOENT:
404 	fprintf(stderr, "File not found\n");
405 	break;
406     case ENOMEM:
407 	fprintf(stderr, "Out of memory\n");
408 	break;
409     default:
410 	fprintf(stderr, "Error %d\n", errno);
411 	break;
412     }
413     fprintf(stderr, "%s: Boot aborted!\n", progname);
414     return 1;
415 }
416