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