chain.c: initial move of documentation to doc/chain.txt
[profile/ivi/syslinux.git] / com32 / chain / chain.c
1 /* ----------------------------------------------------------------------- *
2  *
3  *   Copyright 2003-2009 H. Peter Anvin - All Rights Reserved
4  *   Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin
5  *   Significant portions copyright (C) 2010 Shao Miller
6  *                                      [partition iteration, GPT, "fs"]
7  *
8  *   This program is free software; you can redistribute it and/or modify
9  *   it under the terms of the GNU General Public License as published by
10  *   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
11  *   Boston MA 02111-1307, USA; either version 2 of the License, or
12  *   (at your option) any later version; incorporated herein by reference.
13  *
14  * ----------------------------------------------------------------------- */
15
16 /*
17  * Please see doc/chain.txt for detailed documentation.
18  */
19
20 #include <com32.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <ctype.h>
24 #include <string.h>
25 #include <console.h>
26 #include <minmax.h>
27 #include <stdbool.h>
28 #include <dprintf.h>
29 #include <syslinux/loadfile.h>
30 #include <syslinux/bootrm.h>
31 #include <syslinux/config.h>
32 #include <syslinux/disk.h>
33 #include <syslinux/video.h>
34 #include "partiter.h"
35
36 static struct options {
37     const char *loadfile;
38     uint16_t keeppxe;
39     uint16_t seg;
40     bool isolinux;
41     bool cmldr;
42     bool grub;
43     bool grldr;
44     const char *grubcfg;
45     bool swap;
46     bool hide;
47     bool sethidden;
48     bool drmk;
49 } opt;
50
51 struct data_area {
52     void *data;
53     addr_t base;
54     addr_t size;
55 };
56
57 static inline void error(const char *msg)
58 {
59     fputs(msg, stderr);
60 }
61
62 static struct disk_info diskinfo;
63
64 /* Search for a specific drive, based on the MBR signature; bytes 440-443 */
65 static int find_by_sig(uint32_t mbr_sig)
66 {
67     struct part_iter *boot_part = NULL;
68     int drive;
69
70     for (drive = 0x80; drive <= 0xff; drive++) {
71         if (disk_get_params(drive, &diskinfo))
72             continue;           /* Drive doesn't exist */
73         /* Check for a MBR disk */
74         boot_part = pi_begin(&diskinfo);
75         if (boot_part->type != typedos) {
76             pi_del(&boot_part);
77             continue;
78         }
79         if (boot_part->sub.dos.disk_sig == mbr_sig) {
80             pi_del(&boot_part);
81             goto ok;
82         }
83     }
84     drive = -1;
85 ok:
86     return drive;
87 }
88
89 /*
90  * Search for a specific drive/partition, based on the GPT GUID.
91  * We return the disk drive number if found, as well as populating the
92  * boot_part pointer with the matching partition, if applicable.
93  * If no matching partition is found or the GUID is a disk GUID,
94  * boot_part will be populated with NULL.  If not matching disk is
95  * found, we return -1.
96  */
97 static int find_by_guid(const struct guid *gpt_guid,
98                         struct part_iter **_boot_part)
99 {
100     struct part_iter *boot_part = NULL;
101     int drive;
102
103     for (drive = 0x80; drive <= 0xff; drive++) {
104         if (disk_get_params(drive, &diskinfo))
105             continue;           /* Drive doesn't exist */
106         /* Check for a GPT disk */
107         boot_part = pi_begin(&diskinfo);
108         if (boot_part->type != typegpt) {
109             pi_del(&boot_part);
110             continue;
111         }
112         /* Check for a matching GPT disk guid */
113         if(!memcmp(&boot_part->sub.gpt.disk_guid, gpt_guid, sizeof(*gpt_guid))) {
114             pi_del(&boot_part);
115             goto ok;
116         }
117         /* disk guid doesn't match, maybe partition guid will */
118         while (pi_next(&boot_part)) {
119             if(!memcmp(&boot_part->sub.gpt.part_guid, gpt_guid, sizeof(*gpt_guid)))
120                 goto ok;
121         }
122     }
123     drive = -1;
124 ok:
125     *_boot_part = boot_part;
126     return drive;
127 }
128
129 /*
130  * Search for a specific partition, based on the GPT label.
131  * We return the disk drive number if found, as well as populating the
132  * boot_part pointer with the matching partition, if applicable.
133  * If no matching partition is found, boot_part will be populated with
134  * NULL and we return -1.
135  */
136 static int find_by_label(const char *label, struct part_iter **_boot_part)
137 {
138     struct part_iter *boot_part = NULL;
139     int drive;
140
141     for (drive = 0x80; drive <= 0xff; drive++) {
142         if (disk_get_params(drive, &diskinfo))
143             continue;           /* Drive doesn't exist */
144         /* Check for a GPT disk */
145         boot_part = pi_begin(&diskinfo);
146         if (!(boot_part->type == typegpt)) {
147             pi_del(&boot_part);
148             continue;
149         }
150         /* Check for a matching partition */
151         while (pi_next(&boot_part)) {
152             if (!strcmp(label, boot_part->sub.gpt.part_label))
153                 goto ok;
154         }
155     }
156     drive = -1;
157 ok:
158     *_boot_part = boot_part;
159     return drive;
160 }
161
162 static void do_boot(struct data_area *data, int ndata,
163                     struct syslinux_rm_regs *regs)
164 {
165     uint16_t *const bios_fbm = (uint16_t *) 0x413;
166     addr_t dosmem = *bios_fbm << 10;    /* Technically a low bound */
167     struct syslinux_memmap *mmap;
168     struct syslinux_movelist *mlist = NULL;
169     addr_t endimage;
170     uint8_t driveno = regs->edx.b[0];
171     uint8_t swapdrive = driveno & 0x80;
172     int i;
173
174     mmap = syslinux_memory_map();
175
176     if (!mmap) {
177         error("Cannot read system memory map\n");
178         return;
179     }
180
181     endimage = 0;
182     for (i = 0; i < ndata; i++) {
183         if (data[i].base + data[i].size > endimage)
184             endimage = data[i].base + data[i].size;
185     }
186     if (endimage > dosmem)
187         goto too_big;
188
189     for (i = 0; i < ndata; i++) {
190         if (syslinux_add_movelist(&mlist, data[i].base,
191                                   (addr_t) data[i].data, data[i].size))
192             goto enomem;
193     }
194
195     if (opt.swap && driveno != swapdrive) {
196         static const uint8_t swapstub_master[] = {
197             /* The actual swap code */
198             0x53,               /* 00: push bx */
199             0x0f, 0xb6, 0xda,   /* 01: movzx bx,dl */
200             0x2e, 0x8a, 0x57, 0x60,     /* 04: mov dl,[cs:bx+0x60] */
201             0x5b,               /* 08: pop bx */
202             0xea, 0, 0, 0, 0,   /* 09: jmp far 0:0 */
203             0x90, 0x90,         /* 0E: nop; nop */
204             /* Code to install this in the right location */
205             /* Entry with DS = CS; ES = SI = 0; CX = 256 */
206             0x26, 0x66, 0x8b, 0x7c, 0x4c,       /* 10: mov edi,[es:si+4*0x13] */
207             0x66, 0x89, 0x3e, 0x0a, 0x00,       /* 15: mov [0x0A],edi */
208             0x26, 0x8b, 0x3e, 0x13, 0x04,       /* 1A: mov di,[es:0x413] */
209             0x4f,               /* 1F: dec di */
210             0x26, 0x89, 0x3e, 0x13, 0x04,       /* 20: mov [es:0x413],di */
211             0x66, 0xc1, 0xe7, 0x16,     /* 25: shl edi,16+6 */
212             0x26, 0x66, 0x89, 0x7c, 0x4c,       /* 29: mov [es:si+4*0x13],edi */
213             0x66, 0xc1, 0xef, 0x10,     /* 2E: shr edi,16 */
214             0x8e, 0xc7,         /* 32: mov es,di */
215             0x31, 0xff,         /* 34: xor di,di */
216             0xf3, 0x66, 0xa5,   /* 36: rep movsd */
217             0xbe, 0, 0,         /* 39: mov si,0 */
218             0xbf, 0, 0,         /* 3C: mov di,0 */
219             0x8e, 0xde,         /* 3F: mov ds,si */
220             0x8e, 0xc7,         /* 41: mov es,di */
221             0x66, 0xb9, 0, 0, 0, 0,     /* 43: mov ecx,0 */
222             0x66, 0xbe, 0, 0, 0, 0,     /* 49: mov esi,0 */
223             0x66, 0xbf, 0, 0, 0, 0,     /* 4F: mov edi,0 */
224             0xea, 0, 0, 0, 0,   /* 55: jmp 0:0 */
225             /* pad out to segment boundary */
226             0x90, 0x90,         /* 5A: ... */
227             0x90, 0x90, 0x90, 0x90,     /* 5C: ... */
228         };
229         static uint8_t swapstub[1024];
230         uint8_t *p;
231
232         /* Note: we can't rely on either INT 13h nor the dosmem
233            vector to be correct at this stage, so we have to use an
234            installer stub to put things in the right place.
235            Round the installer location to a 1K boundary so the only
236            possible overlap is the identity mapping. */
237         endimage = (endimage + 1023) & ~1023;
238
239         /* Create swap stub */
240         memcpy(swapstub, swapstub_master, sizeof swapstub_master);
241         *(uint16_t *) & swapstub[0x3a] = regs->ds;
242         *(uint16_t *) & swapstub[0x3d] = regs->es;
243         *(uint32_t *) & swapstub[0x45] = regs->ecx.l;
244         *(uint32_t *) & swapstub[0x4b] = regs->esi.l;
245         *(uint32_t *) & swapstub[0x51] = regs->edi.l;
246         *(uint16_t *) & swapstub[0x56] = regs->ip;
247         *(uint16_t *) & swapstub[0x58] = regs->cs;
248         p = &swapstub[sizeof swapstub_master];
249
250         /* Mapping table; start out with identity mapping everything */
251         for (i = 0; i < 256; i++)
252             p[i] = i;
253
254         /* And the actual swap */
255         p[driveno] = swapdrive;
256         p[swapdrive] = driveno;
257
258         /* Adjust registers */
259         regs->ds = regs->cs = endimage >> 4;
260         regs->es = regs->esi.l = 0;
261         regs->ecx.l = sizeof swapstub >> 2;
262         regs->ip = 0x10;        /* Installer offset */
263         regs->ebx.b[0] = regs->edx.b[0] = swapdrive;
264
265         if (syslinux_add_movelist(&mlist, endimage, (addr_t) swapstub,
266                                   sizeof swapstub))
267             goto enomem;
268
269         endimage += sizeof swapstub;
270     }
271
272     /* Tell the shuffler not to muck with this area... */
273     syslinux_add_memmap(&mmap, endimage, 0xa0000 - endimage, SMT_RESERVED);
274
275     /* Force text mode */
276     syslinux_force_text_mode();
277
278     fputs("Booting...\n", stdout);
279     syslinux_shuffle_boot_rm(mlist, mmap, opt.keeppxe, regs);
280     error("Chainboot failed!\n");
281     return;
282
283 too_big:
284     error("Loader file too large\n");
285     return;
286
287 enomem:
288     error("Out of memory\n");
289     return;
290 }
291
292 static int hide_unhide(struct disk_dos_mbr *mbr, int part)
293 {
294     int i;
295     struct disk_dos_part_entry *pt;
296     const uint16_t mask =
297         (1 << 0x01) | (1 << 0x04) | (1 << 0x06) |
298         (1 << 0x07) | (1 << 0x0b) | (1 << 0x0c) | (1 << 0x0e);
299     uint8_t t;
300     bool write_back = false;
301
302     for (i = 1; i <= 4; i++) {
303         pt = mbr->table + i - 1;
304         t = pt->ostype;
305         if ((t <= 0x1f) && ((mask >> (t & ~0x10)) & 1)) {
306             /* It's a hideable partition type */
307             if (i == part)
308                 t &= ~0x10;     /* unhide */
309             else
310                 t |= 0x10;      /* hide */
311         }
312         if (t != pt->ostype) {
313             write_back = true;
314             pt->ostype = t;
315         }
316     }
317
318     if (write_back)
319         return disk_write_verify_sector(&diskinfo, 0, mbr);
320
321     return 0;                   /* ok */
322 }
323
324 static uint32_t get_file_lba(const char *filename)
325 {
326     com32sys_t inregs;
327     uint32_t lba;
328
329     /* Start with clean registers */
330     memset(&inregs, 0, sizeof(com32sys_t));
331
332     /* Put the filename in the bounce buffer */
333     strlcpy(__com32.cs_bounce, filename, __com32.cs_bounce_size);
334
335     /* Call comapi_open() which returns a structure pointer in SI
336      * to a structure whose first member happens to be the LBA.
337      */
338     inregs.eax.w[0] = 0x0006;
339     inregs.esi.w[0] = OFFS(__com32.cs_bounce);
340     inregs.es = SEG(__com32.cs_bounce);
341     __com32.cs_intcall(0x22, &inregs, &inregs);
342
343     if ((inregs.eflags.l & EFLAGS_CF) || inregs.esi.w[0] == 0) {
344         return 0;               /* Filename not found */
345     }
346
347     /* Since the first member is the LBA, we simply cast */
348     lba = *((uint32_t *) MK_PTR(inregs.ds, inregs.esi.w[0]));
349
350     /* Clean the registers for the next call */
351     memset(&inregs, 0, sizeof(com32sys_t));
352
353     /* Put the filename in the bounce buffer */
354     strlcpy(__com32.cs_bounce, filename, __com32.cs_bounce_size);
355
356     /* Call comapi_close() to free the structure */
357     inregs.eax.w[0] = 0x0008;
358     inregs.esi.w[0] = OFFS(__com32.cs_bounce);
359     inregs.es = SEG(__com32.cs_bounce);
360     __com32.cs_intcall(0x22, &inregs, &inregs);
361
362     return lba;
363 }
364
365 static void usage(void)
366 {
367     static const char usage[] = "\
368 Usage:   chain.c32 [options]\n\
369          chain.c32 hd<disk#> [<partition>] [options]\n\
370          chain.c32 fd<disk#> [options]\n\
371          chain.c32 mbr:<id> [<partition>] [options]\n\
372          chain.c32 guid:<guid> [<partition>] [options]\n\
373          chain.c32 label:<label> [<partition>] [options]\n\
374          chain.c32 boot [<partition>] [options]\n\
375          chain.c32 fs [options]\n\
376 Options: file=<loader>      Load and execute file, instead of boot sector\n\
377          isolinux=<loader>  Load another version of ISOLINUX\n\
378          ntldr=<loader>     Load Windows NTLDR, SETUPLDR.BIN or BOOTMGR\n\
379          cmldr=<loader>     Load Recovery Console of Windows NT/2K/XP/2003\n\
380          freedos=<loader>   Load FreeDOS KERNEL.SYS\n\
381          msdos=<loader>     Load MS-DOS IO.SYS\n\
382          pcdos=<loader>     Load PC-DOS IBMBIO.COM\n\
383          drmk=<loader>      Load DRMK DELLBIO.BIN\n\
384          grub=<loader>      Load GRUB Legacy stage2\n\
385          grubcfg=<filename> Set alternative config filename for GRUB Legacy\n\
386          grldr=<loader>     Load GRUB4DOS grldr\n\
387          seg=<segment>      Jump to <seg>:0000, instead of 0000:7C00\n\
388          swap               Swap drive numbers, if bootdisk is not fd0/hd0\n\
389          hide               Hide primary partitions, except selected partition\n\
390          sethidden          Set the FAT/NTFS hidden sectors field\n\
391          keeppxe            Keep the PXE and UNDI stacks in memory (PXELINUX)\n\
392 See syslinux/com32/modules/chain.c for more information\n";
393     error(usage);
394 }
395
396 int main(int argc, char *argv[])
397 {
398     struct disk_dos_mbr *mbr = NULL;
399     char *p;
400     struct part_iter *cur_part = NULL;
401
402     void *sect_area = NULL;
403     void *file_area = NULL;
404     struct disk_dos_part_entry *hand_area = NULL;
405
406     struct syslinux_rm_regs regs;
407     char *drivename, *partition;
408     int hd, drive, whichpart = 0;       /* MBR by default */
409     int i;
410     uint64_t fs_lba = 0;        /* Syslinux partition */
411     uint32_t file_lba = 0;
412     struct guid gpt_guid;
413     unsigned char *isolinux_bin;
414     uint32_t *checksum, *chkhead, *chktail;
415     struct data_area data[3];
416     int ndata = 0;
417     addr_t load_base;
418     static const char cmldr_signature[8] = "cmdcons";
419
420     openconsole(&dev_null_r, &dev_stdcon_w);
421
422     drivename = "boot";
423     partition = NULL;
424
425     /* Prepare the register set */
426     memset(&regs, 0, sizeof regs);
427
428     for (i = 1; i < argc; i++) {
429         if (!strncmp(argv[i], "file=", 5)) {
430             opt.loadfile = argv[i] + 5;
431         } else if (!strncmp(argv[i], "seg=", 4)) {
432             uint32_t segval = strtoul(argv[i] + 4, NULL, 0);
433             if (segval < 0x50 || segval > 0x9f00) {
434                 error("Invalid segment\n");
435                 goto bail;
436             }
437             opt.seg = segval;
438         } else if (!strncmp(argv[i], "isolinux=", 9)) {
439             opt.loadfile = argv[i] + 9;
440             opt.isolinux = true;
441         } else if (!strncmp(argv[i], "ntldr=", 6)) {
442             opt.seg = 0x2000;   /* NTLDR wants this address */
443             opt.loadfile = argv[i] + 6;
444             opt.sethidden = true;
445         } else if (!strncmp(argv[i], "cmldr=", 6)) {
446             opt.seg = 0x2000;   /* CMLDR wants this address */
447             opt.loadfile = argv[i] + 6;
448             opt.cmldr = true;
449             opt.sethidden = true;
450         } else if (!strncmp(argv[i], "freedos=", 8)) {
451             opt.seg = 0x60;     /* FREEDOS wants this address */
452             opt.loadfile = argv[i] + 8;
453             opt.sethidden = true;
454         } else if (!strncmp(argv[i], "msdos=", 6) ||
455                    !strncmp(argv[i], "pcdos=", 6)) {
456             opt.seg = 0x70;     /* MS-DOS 2.0+ wants this address */
457             opt.loadfile = argv[i] + 6;
458             opt.sethidden = true;
459         } else if (!strncmp(argv[i], "drmk=", 5)) {
460             opt.seg = 0x70;     /* DRMK wants this address */
461             opt.loadfile = argv[i] + 5;
462             opt.sethidden = true;
463             opt.drmk = true;
464         } else if (!strncmp(argv[i], "grub=", 5)) {
465             opt.seg = 0x800;    /* stage2 wants this address */
466             opt.loadfile = argv[i] + 5;
467             opt.grub = true;
468         } else if (!strncmp(argv[i], "grubcfg=", 8)) {
469             opt.grubcfg = argv[i] + 8;
470         } else if (!strncmp(argv[i], "grldr=", 6)) {
471             opt.loadfile = argv[i] + 6;
472             opt.grldr = true;
473         } else if (!strcmp(argv[i], "swap")) {
474             opt.swap = true;
475         } else if (!strcmp(argv[i], "noswap")) {
476             opt.swap = false;
477         } else if (!strcmp(argv[i], "hide")) {
478             opt.hide = true;
479         } else if (!strcmp(argv[i], "nohide")) {
480             opt.hide = false;
481         } else if (!strcmp(argv[i], "keeppxe")) {
482             opt.keeppxe = 3;
483         } else if (!strcmp(argv[i], "sethidden")) {
484             opt.sethidden = true;
485         } else if (!strcmp(argv[i], "nosethidden")) {
486             opt.sethidden = false;
487         } else if (((argv[i][0] == 'h' || argv[i][0] == 'f')
488                     && argv[i][1] == 'd')
489                    || !strncmp(argv[i], "mbr:", 4)
490                    || !strncmp(argv[i], "mbr=", 4)
491                    || !strncmp(argv[i], "guid:", 5)
492                    || !strncmp(argv[i], "guid=", 5)
493                    || !strncmp(argv[i], "label:", 6)
494                    || !strncmp(argv[i], "label=", 6)
495                    || !strcmp(argv[i], "boot")
496                    || !strncmp(argv[i], "boot,", 5)
497                    || !strcmp(argv[i], "fs")) {
498             drivename = argv[i];
499             p = strchr(drivename, ',');
500             if (p) {
501                 *p = '\0';
502                 partition = p + 1;
503             } else if (argv[i + 1] && argv[i + 1][0] >= '0'
504                        && argv[i + 1][0] <= '9') {
505                 partition = argv[++i];
506             }
507         } else {
508             usage();
509             goto bail;
510         }
511     }
512
513     if (opt.grubcfg && !opt.grub) {
514         error("grubcfg=<filename> must be used together with grub=<loader>.\n");
515         goto bail;
516     }
517
518     if (opt.seg) {
519         regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = opt.seg;
520     } else {
521         regs.ip = regs.esp.l = 0x7c00;
522     }
523
524     hd = 0;
525     if (!strncmp(drivename, "mbr", 3)) {
526         drive = find_by_sig(strtoul(drivename + 4, NULL, 0));
527         if (drive == -1) {
528             error("Unable to find requested MBR signature\n");
529             goto bail;
530         }
531     } else if (!strncmp(drivename, "guid", 4)) {
532         if (str_to_guid(drivename + 5, &gpt_guid))
533             goto bail;
534         drive = find_by_guid(&gpt_guid, &cur_part);
535         if (drive == -1) {
536             error("Unable to find requested GPT disk/partition\n");
537             goto bail;
538         }
539     } else if (!strncmp(drivename, "label", 5)) {
540         if (!drivename[6]) {
541             error("No label specified.\n");
542             goto bail;
543         }
544         drive = find_by_label(drivename + 6, &cur_part);
545         if (drive == -1) {
546             error("Unable to find requested partition by label\n");
547             goto bail;
548         }
549     } else if ((drivename[0] == 'h' || drivename[0] == 'f') &&
550                drivename[1] == 'd') {
551         hd = drivename[0] == 'h';
552         drivename += 2;
553         drive = (hd ? 0x80 : 0) | strtoul(drivename, NULL, 0);
554     } else if (!strcmp(drivename, "boot") || !strcmp(drivename, "fs")) {
555         const union syslinux_derivative_info *sdi;
556
557         sdi = syslinux_derivative_info();
558         if (sdi->c.filesystem == SYSLINUX_FS_PXELINUX)
559             drive = 0x80;       /* Boot drive not available */
560         else
561             drive = sdi->disk.drive_number;
562         if (!strcmp(drivename, "fs")
563             && (sdi->c.filesystem == SYSLINUX_FS_SYSLINUX
564                 || sdi->c.filesystem == SYSLINUX_FS_EXTLINUX
565                 || sdi->c.filesystem == SYSLINUX_FS_ISOLINUX))
566             /* We should lookup the Syslinux partition number and use it */
567             fs_lba = *sdi->disk.partoffset;
568     } else {
569         error("Unparsable drive specification\n");
570         goto bail;
571     }
572
573     /* DOS kernels want the drive number in BL instead of DL.  Indulge them. */
574     regs.ebx.b[0] = regs.edx.b[0] = drive;
575
576     /* Get the disk geometry and disk access setup */
577     if (disk_get_params(drive, &diskinfo)) {
578         error("Cannot get disk parameters\n");
579         goto bail;
580     }
581
582     /* Get MBR */
583     if (!(mbr = disk_read_sectors(&diskinfo, 0, 1))) {
584         error("Cannot read Master Boot Record or sector 0\n");
585         goto bail;
586     }
587
588     if (partition)
589         whichpart = strtoul(partition, NULL, 0);
590
591     /* "guid:" or "label:" might have specified a partition. In such case,
592      * this overrides explicit partition number specification. cur-part->index
593      * can't be 0 at this stage as find_by* won't export iterator at such
594      * position.
595      */
596     if (cur_part)
597         whichpart = cur_part->index;
598
599     /* If nothing was found, try fs/boot first */
600     if (!cur_part && fs_lba) {
601         cur_part = pi_begin(&diskinfo);
602         /* search for matching fs_lba, must be partition */
603         while (pi_next(&cur_part)) {
604             if (cur_part->start_lba == fs_lba)
605                 break;
606         }
607     }
608     /* If still nothing found, do standard search */
609     if (!cur_part) {
610         cur_part = pi_begin(&diskinfo);
611         /* search for matching part#, including disk */
612         do {
613             if (cur_part->index == whichpart)
614                 break;
615         } while (pi_next(&cur_part));
616     }
617     if (!cur_part) {
618         error("Requested disk / partition not found!\n");
619         goto bail;
620     }
621
622     whichpart = cur_part->index;
623
624     if (!(drive & 0x80) && whichpart) {
625         error("Warning: Partitions of floppy devices may not work\n");
626     }
627
628     /*
629      * GRLDR of GRUB4DOS wants the partition number in DH:
630      * -1:   whole drive (default)
631      * 0-3:  primary partitions
632      * 4-*:  logical partitions
633      */
634     if (opt.grldr)
635         regs.edx.b[1] = whichpart - 1;
636
637     if (opt.hide) {
638         if (whichpart < 1 || whichpart > 4)
639             error("WARNING: hide specified without a non-primary partition\n");
640         if (hide_unhide(mbr, whichpart))
641             error("WARNING: failed to write MBR for 'hide'\n");
642     }
643
644     /* Do the actual chainloading */
645     load_base = opt.seg ? (opt.seg << 4) : 0x7c00;
646
647     if (opt.loadfile) {
648         fputs("Loading the boot file...\n", stdout);
649         if (loadfile(opt.loadfile, &data[ndata].data, &data[ndata].size)) {
650             error("Failed to load the boot file\n");
651             goto bail;
652         }
653         data[ndata].base = load_base;
654         file_area = (void *)data[ndata].data;
655         load_base = 0x7c00;     /* If we also load a boot sector */
656
657         /* Create boot info table: needed when you want to chainload
658            another version of ISOLINUX (or another bootlaoder that needs
659            the -boot-info-table switch of mkisofs)
660            (will only work when run from ISOLINUX) */
661         if (opt.isolinux) {
662             const union syslinux_derivative_info *sdi;
663             sdi = syslinux_derivative_info();
664
665             if (sdi->c.filesystem == SYSLINUX_FS_ISOLINUX) {
666                 /* Boot info table info (integers in little endian format)
667
668                    Offset Name         Size      Meaning
669                    8      bi_pvd       4 bytes   LBA of primary volume descriptor
670                    12     bi_file      4 bytes   LBA of boot file
671                    16     bi_length    4 bytes   Boot file length in bytes
672                    20     bi_csum      4 bytes   32-bit checksum
673                    24     bi_reserved  40 bytes  Reserved
674
675                    The 32-bit checksum is the sum of all the 32-bit words in the
676                    boot file starting at byte offset 64. All linear block
677                    addresses (LBAs) are given in CD sectors (normally 2048 bytes).
678
679                    LBA of primary volume descriptor should already be set to 16.
680                  */
681
682                 isolinux_bin = (unsigned char *)data[ndata].data;
683
684                 /* Get LBA address of bootfile */
685                 file_lba = get_file_lba(opt.loadfile);
686
687                 if (file_lba == 0) {
688                     error("Failed to find LBA offset of the boot file\n");
689                     goto bail;
690                 }
691                 /* Set it */
692                 *((uint32_t *) & isolinux_bin[12]) = file_lba;
693
694                 /* Set boot file length */
695                 *((uint32_t *) & isolinux_bin[16]) = data[ndata].size;
696
697                 /* Calculate checksum */
698                 checksum = (uint32_t *) & isolinux_bin[20];
699                 chkhead = (uint32_t *) & isolinux_bin[64];
700                 chktail = (uint32_t *) & isolinux_bin[data[ndata].size & ~3];
701                 *checksum = 0;
702                 while (chkhead < chktail)
703                     *checksum += *chkhead++;
704
705                 /*
706                  * Deal with possible fractional dword at the end;
707                  * this *should* never happen...
708                  */
709                 if (data[ndata].size & 3) {
710                     uint32_t xword = 0;
711                     memcpy(&xword, chkhead, data[ndata].size & 3);
712                     *checksum += xword;
713                 }
714             } else {
715                 error
716                     ("The isolinux= option is only valid when run from ISOLINUX\n");
717                 goto bail;
718             }
719         }
720
721         if (opt.grub) {
722             /* Layout of stage2 file (from byte 0x0 to 0x270) */
723             struct grub_stage2_patch_area {
724                 /* 0x0 to 0x205 */
725                 char unknown[0x206];
726                 /* 0x206: compatibility version number major */
727                 uint8_t compat_version_major;
728                 /* 0x207: compatibility version number minor */
729                 uint8_t compat_version_minor;
730
731                 /* 0x208: install_partition variable */
732                 struct {
733                     /* 0x208: sub-partition in sub-partition part2 */
734                     uint8_t part3;
735                     /* 0x209: sub-partition in top-level partition */
736                     uint8_t part2;
737                     /* 0x20a: top-level partiton number */
738                     uint8_t part1;
739                     /* 0x20b: BIOS drive number (must be 0) */
740                     uint8_t drive;
741                 } __attribute__ ((packed)) install_partition;
742
743                 /* 0x20c: deprecated (historical reason only) */
744                 uint32_t saved_entryno;
745                 /* 0x210: stage2_ID: will always be STAGE2_ID_STAGE2 = 0 in stage2 */
746                 uint8_t stage2_id;
747                 /* 0x211: force LBA */
748                 uint8_t force_lba;
749                 /* 0x212: version string (will probably be 0.97) */
750                 char version_string[5];
751                 /* 0x217: config filename */
752                 char config_file[89];
753                 /* 0x270: start of code (after jump from 0x200) */
754                 char codestart[1];
755             } __attribute__ ((packed)) *stage2;
756
757             if (data[ndata].size < sizeof(struct grub_stage2_patch_area)) {
758                 error
759                     ("The file specified by grub=<loader> is too small to be stage2 of GRUB Legacy.\n");
760                 goto bail;
761             }
762
763             stage2 = data[ndata].data;
764
765             /*
766              * Check the compatibility version number to see if we loaded a real
767              * stage2 file or a stage2 file that we support.
768              */
769             if (stage2->compat_version_major != 3
770                 || stage2->compat_version_minor != 2) {
771                 error
772                     ("The file specified by grub=<loader> is not a supported stage2 GRUB Legacy binary\n");
773                 goto bail;
774             }
775
776             /* jump 0x200 bytes into the loadfile */
777             regs.ip = 0x200;
778
779             /*
780              * GRUB Legacy wants the partition number in the install_partition
781              * variable, located at offset 0x208 of stage2.
782              * When GRUB Legacy is loaded, it is located at memory address 0x8208.
783              *
784              * It looks very similar to the "boot information format" of the
785              * Multiboot specification:
786              *   http://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format
787              *
788              *   0x208 = part3: sub-partition in sub-partition part2
789              *   0x209 = part2: sub-partition in top-level partition
790              *   0x20a = part1: top-level partition number
791              *   0x20b = drive: BIOS drive number (must be 0)
792              *
793              * GRUB Legacy doesn't store the BIOS drive number at 0x20b, but at
794              * another location.
795              *
796              * Partition numbers always start from zero.
797              * Unused partition bytes must be set to 0xFF.
798              *
799              * We only care about top-level partition, so we only need to change
800              * "part1" to the appropriate value:
801              *   -1:   whole drive (default) (-1 = 0xFF)
802              *   0-3:  primary partitions
803              *   4-*:  logical partitions
804              */
805             stage2->install_partition.part1 = whichpart - 1;
806
807             /*
808              * Grub Legacy reserves 89 bytes (from 0x8217 to 0x826f) for the
809              * config filename. The filename passed via grubcfg= will overwrite
810              * the default config filename "/boot/grub/menu.lst".
811              */
812             if (opt.grubcfg) {
813                 if (strlen(opt.grubcfg) > sizeof(stage2->config_file) - 1) {
814                     error
815                         ("The config filename length can't exceed 88 characters.\n");
816                     goto bail;
817                 }
818
819                 strcpy((char *)stage2->config_file, opt.grubcfg);
820             }
821         }
822
823         if (opt.drmk) {
824             /* DRMK entry is different than MS-DOS/PC-DOS */
825             /*
826              * A new size, aligned to 16 bytes to ease use of ds:[bp+28].
827              * We only really need 4 new, usable bytes at the end.
828              */
829             int tsize = (data[ndata].size + 19) & 0xfffffff0;
830             regs.ss = regs.fs = regs.gs = 0;    /* Used before initialized */
831             if (!realloc(data[ndata].data, tsize)) {
832                 error("Failed to realloc for DRMK\n");
833                 goto bail;
834             }
835             data[ndata].size = tsize;
836             /* ds:[bp+28] must be 0x0000003f */
837             regs.ds = (tsize >> 4) + (opt.seg - 2);
838             /* "Patch" into tail of the new space */
839             *(int *)(data[ndata].data + tsize - 4) = 0x0000003f;
840         }
841
842         ndata++;
843     }
844
845     if (!opt.loadfile || data[0].base >= 0x7c00 + SECTOR) {
846         /* Actually read the boot sector */
847         if (!cur_part->index) {
848             data[ndata].data = mbr;
849         } else
850             if (!(data[ndata].data =
851                  disk_read_sectors(&diskinfo, cur_part->start_lba, 1))) {
852             error("Cannot read boot sector\n");
853             goto bail;
854         } else
855             sect_area = (void *)data[ndata].data;
856         data[ndata].size = SECTOR;
857         data[ndata].base = load_base;
858
859         if (!opt.loadfile) {
860             const struct disk_dos_mbr *br =
861                 (const struct disk_dos_mbr *)((char *)data[ndata].data +
862                                               data[ndata].size -
863                                               sizeof(struct disk_dos_mbr));
864             if (br->sig != disk_mbr_sig_magic) {
865                 error
866                     ("Boot sector signature not found (unbootable disk/partition?)\n");
867                 goto bail;
868             }
869         }
870         /*
871          * To boot the Recovery Console of Windows NT/2K/XP we need to write
872          * the string "cmdcons\0" to memory location 0000:7C03.
873          * Memory location 0000:7C00 contains the bootsector of the partition.
874          */
875         if (cur_part->index && opt.cmldr) {
876             memcpy((char *)data[ndata].data + 3, cmldr_signature,
877                    sizeof cmldr_signature);
878         }
879
880         /*
881          * Modify the hidden sectors (partition offset) copy in memory;
882          * this modifies the field used by FAT and NTFS filesystems, and
883          * possibly other boot loaders which use the same format.
884          */
885         if (cur_part->index && opt.sethidden) {
886             *(uint32_t *) ((char *)data[ndata].data + 28) = cur_part->start_lba;
887         }
888
889         ndata++;
890     }
891
892     if (cur_part->index) {
893         if (cur_part->type == typegpt) {
894             /* Do GPT hand-over, if applicable (as per syslinux/doc/gpt.txt) */
895             /* Look at the GPT partition */
896             const struct disk_gpt_part_entry *gp =
897                 (const struct disk_gpt_part_entry *)cur_part->record;
898             /* Note the partition length */
899             uint64_t lba_count = gp->lba_last - gp->lba_first + 1;
900             /* The length of the hand-over */
901             uint32_t synth_size =
902                 sizeof(struct disk_dos_part_entry) + sizeof(uint32_t) +
903                 cur_part->sub.gpt.pe_size;
904             /* Will point to the partition record length in the hand-over */
905             uint32_t *plen;
906
907             /* Allocate the hand-over record */
908             hand_area = malloc(synth_size);
909             if (!hand_area) {
910                 error("Could not build GPT hand-over record!\n");
911                 goto bail;
912             }
913             /* Synthesize the record */
914             memset(hand_area, 0, synth_size);
915             hand_area->active_flag = 0x80;
916             hand_area->ostype = 0xED;
917             /* All bits set by default */
918             hand_area->start_lba = ~(uint32_t) 0;
919             hand_area->length = ~(uint32_t) 0;
920             /* If these fit the precision, pass them on */
921             if (cur_part->start_lba < hand_area->start_lba)
922                 hand_area->start_lba = cur_part->start_lba;
923             if (lba_count < hand_area->length)
924                 hand_area->length = lba_count;
925             /* Next comes the GPT partition record length */
926             plen = (uint32_t *) (hand_area + 1);
927             plen[0] = cur_part->sub.gpt.pe_size;
928             /* Next comes the GPT partition record copy */
929             memcpy(plen + 1, gp, plen[0]);
930
931             regs.eax.l = 0x54504721;    /* '!GPT' */
932             data[ndata].base = 0x7be;
933             data[ndata].size = synth_size;
934             data[ndata].data = (void *)hand_area;
935             ndata++;
936             regs.esi.w[0] = 0x7be;
937 #ifdef DEBUG
938             dprintf("GPT handover:\n");
939             disk_dos_part_dump(hand_area);
940             disk_gpt_part_dump((struct disk_gpt_part_entry *)(plen + 1));
941 #endif
942         } else {
943             /* MBR handover protocol */
944             /* Allocate the hand-over record */
945             hand_area = malloc(sizeof(struct disk_dos_part_entry));
946             if (!hand_area) {
947                 error("Could not build MBR hand-over record!\n");
948                 goto bail;
949             }
950
951             memcpy(hand_area, cur_part->record, sizeof(struct disk_dos_part_entry));
952             hand_area->start_lba = cur_part->start_lba;
953
954             data[ndata].base = 0x7be;
955             data[ndata].size = sizeof(struct disk_dos_part_entry);
956             data[ndata].data = (void *)hand_area;
957             ndata++;
958             regs.esi.w[0] = 0x7be;
959 #ifdef DEBUG
960             dprintf("MBR handover:\n");
961             disk_dos_part_dump(hand_area);
962 #endif
963         }
964     }
965
966     do_boot(data, ndata, &regs);
967
968 bail:
969     pi_del(&cur_part);
970     /* Free allocated areas */
971     free(mbr);
972     free(file_area);
973     free(sect_area);
974     free(hand_area);
975     return 255;
976 }
977
978 /* vim: set ts=8 sts=4 sw=4 noet: */