chain: Use struct mbr where applicable
[profile/ivi/syslinux.git] / com32 / modules / 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  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
9  *   Boston MA 02111-1307, USA; either version 2 of the License, or
10  *   (at your option) any later version; incorporated herein by reference.
11  *
12  * ----------------------------------------------------------------------- */
13
14 /*
15  * chain.c
16  *
17  * Chainload a hard disk (currently rather braindead.)
18  *
19  * Usage: chain hd<disk#> [<partition>] [options]
20  *        chain fd<disk#> [options]
21  *        chain mbr:<id> [<partition>] [options]
22  *        chain boot [<partition>] [options]
23  *
24  * ... e.g. "chain hd0 1" will boot the first partition on the first hard
25  * disk.
26  *
27  *
28  * The mbr: syntax means search all the hard disks until one with a
29  * specific MBR serial number (bytes 440-443) is found.
30  *
31  * Partitions 1-4 are primary, 5+ logical, 0 = boot MBR (default.)
32  *
33  * Options:
34  *
35  * file=<loader>:
36  *      loads the file <loader> **from the SYSLINUX filesystem**
37  *      instead of loading the boot sector.
38  *
39  * seg=<segment>:
40  *      loads at and jumps to <seg>:0000 instead of 0000:7C00.
41  *
42  * isolinux=<loader>:
43  *      chainload another version/build of the ISOLINUX bootloader and patch
44  *      the loader with appropriate parameters in memory.
45  *      This avoids the need for the -eltorito-alt-boot parameter of mkisofs,
46  *      when you want more than one ISOLINUX per CD/DVD.
47  *
48  * ntldr=<loader>:
49  *      equivalent to seg=0x2000 file=<loader> sethidden,
50  *      used with WinNT's loaders
51  *
52  * cmldr=<loader>:
53  *      used with Recovery Console of Windows NT/2K/XP.
54  *      same as ntldr=<loader> & "cmdcons\0" written to
55  *      the system name field in the bootsector
56  *
57  * freedos=<loader>:
58  *      equivalent to seg=0x60 file=<loader> sethidden,
59  *      used with FreeDOS kernel.sys.
60  *
61  * msdos=<loader>
62  * pcdos=<loader>
63  *      equivalent to seg=0x70 file=<loader> sethidden,
64  *      used with DOS' io.sys.
65  *
66  * grub=<loader>:
67  *      same as seg=0x800 file=<loader> & jumping to seg 0x820,
68  *      used with GRUB stage2 files.
69  *
70  * swap:
71  *      if the disk is not fd0/hd0, install a BIOS stub which swaps
72  *      the drive numbers.
73  *
74  * hide:
75  *      change type of primary partitions with IDs 01, 04, 06, 07,
76  *      0b, 0c, or 0e to 1x, except for the selected partition, which
77  *      is converted the other way.
78  *
79  * sethidden:
80  *      update the "hidden sectors" (partition offset) field in a
81  *      FAT/NTFS boot sector.
82  */
83
84 #include <com32.h>
85 #include <stdlib.h>
86 #include <stdio.h>
87 #include <ctype.h>
88 #include <string.h>
89 #include <console.h>
90 #include <minmax.h>
91 #include <stdbool.h>
92 #include <syslinux/loadfile.h>
93 #include <syslinux/bootrm.h>
94 #include <syslinux/config.h>
95 #include <syslinux/video.h>
96
97 #define SECTOR 512              /* bytes/sector */
98
99 static struct options {
100     const char *loadfile;
101     uint16_t keeppxe;
102     uint16_t seg;
103     bool isolinux;
104     bool cmldr;
105     bool swap;
106     bool hide;
107     bool sethidden;
108     bool grub;
109 } opt;
110
111 struct data_area {
112     void *data;
113     addr_t base;
114     addr_t size;
115 };
116
117 static inline void error(const char *msg)
118 {
119     fputs(msg, stderr);
120 }
121
122 /*
123  * Call int 13h, but with retry on failure.  Especially floppies need this.
124  */
125 static int int13_retry(const com32sys_t * inreg, com32sys_t * outreg)
126 {
127     int retry = 6;              /* Number of retries */
128     com32sys_t tmpregs;
129
130     if (!outreg)
131         outreg = &tmpregs;
132
133     while (retry--) {
134         __intcall(0x13, inreg, outreg);
135         if (!(outreg->eflags.l & EFLAGS_CF))
136             return 0;           /* CF=0, OK */
137     }
138
139     return -1;                  /* Error */
140 }
141
142 /*
143  * Query disk parameters and EBIOS availability for a particular disk.
144  */
145 struct diskinfo {
146     int disk;
147     int ebios;                  /* EBIOS supported on this disk */
148     int cbios;                  /* CHS geometry is valid */
149     int head;
150     int sect;
151 } disk_info;
152
153 static int get_disk_params(int disk)
154 {
155     static com32sys_t getparm, parm, getebios, ebios;
156
157     disk_info.disk = disk;
158     disk_info.ebios = disk_info.cbios = 0;
159
160     /* Get EBIOS support */
161     getebios.eax.w[0] = 0x4100;
162     getebios.ebx.w[0] = 0x55aa;
163     getebios.edx.b[0] = disk;
164     getebios.eflags.b[0] = 0x3; /* CF set */
165
166     __intcall(0x13, &getebios, &ebios);
167
168     if (!(ebios.eflags.l & EFLAGS_CF) &&
169         ebios.ebx.w[0] == 0xaa55 && (ebios.ecx.b[0] & 1)) {
170         disk_info.ebios = 1;
171     }
172
173     /* Get disk parameters -- really only useful for
174        hard disks, but if we have a partitioned floppy
175        it's actually our best chance... */
176     getparm.eax.b[1] = 0x08;
177     getparm.edx.b[0] = disk;
178
179     __intcall(0x13, &getparm, &parm);
180
181     if (parm.eflags.l & EFLAGS_CF)
182         return disk_info.ebios ? 0 : -1;
183
184     disk_info.head = parm.edx.b[1] + 1;
185     disk_info.sect = parm.ecx.b[0] & 0x3f;
186     if (disk_info.sect == 0) {
187         disk_info.sect = 1;
188     } else {
189         disk_info.cbios = 1;    /* Valid geometry */
190     }
191
192     return 0;
193 }
194
195 /*
196  * Get a disk block and return a malloc'd buffer.
197  * Uses the disk number and information from disk_info.
198  */
199 struct ebios_dapa {
200     uint16_t len;
201     uint16_t count;
202     uint16_t off;
203     uint16_t seg;
204     uint64_t lba;
205 };
206
207 /* Read count sectors from drive, starting at lba.  Return a new buffer */
208 static void *read_sectors(uint64_t lba, uint8_t count)
209 {
210     com32sys_t inreg;
211     struct ebios_dapa *dapa = __com32.cs_bounce;
212     void *buf = (char *)__com32.cs_bounce + SECTOR;
213     void *data;
214
215     if (!count)
216         /* Silly */
217         return NULL;
218
219     memset(&inreg, 0, sizeof inreg);
220
221     if (disk_info.ebios) {
222         dapa->len = sizeof(*dapa);
223         dapa->count = count;
224         dapa->off = OFFS(buf);
225         dapa->seg = SEG(buf);
226         dapa->lba = lba;
227
228         inreg.esi.w[0] = OFFS(dapa);
229         inreg.ds = SEG(dapa);
230         inreg.edx.b[0] = disk_info.disk;
231         inreg.eax.b[1] = 0x42;  /* Extended read */
232     } else {
233         unsigned int c, h, s, t;
234
235         if (!disk_info.cbios) {
236             /* We failed to get the geometry */
237
238             if (lba)
239                 return NULL;    /* Can only read MBR */
240
241             s = 1;
242             h = 0;
243             c = 0;
244         } else {
245             s = (lba % disk_info.sect) + 1;
246             t = lba / disk_info.sect;   /* Track = head*cyl */
247             h = t % disk_info.head;
248             c = t / disk_info.head;
249         }
250
251         if (s > 63 || h > 256 || c > 1023)
252             return NULL;
253
254         inreg.eax.b[0] = count;
255         inreg.eax.b[1] = 0x02;  /* Read */
256         inreg.ecx.b[1] = c & 0xff;
257         inreg.ecx.b[0] = s + (c >> 6);
258         inreg.edx.b[1] = h;
259         inreg.edx.b[0] = disk_info.disk;
260         inreg.ebx.w[0] = OFFS(buf);
261         inreg.es = SEG(buf);
262     }
263
264     if (int13_retry(&inreg, NULL))
265         return NULL;
266
267     data = malloc(SECTOR);
268     if (data)
269         memcpy(data, buf, count * SECTOR);
270     return data;
271 }
272
273 static int write_sector(unsigned int lba, const void *data)
274 {
275     com32sys_t inreg;
276     struct ebios_dapa *dapa = __com32.cs_bounce;
277     void *buf = (char *)__com32.cs_bounce + SECTOR;
278
279     memcpy(buf, data, SECTOR);
280     memset(&inreg, 0, sizeof inreg);
281
282     if (disk_info.ebios) {
283         dapa->len = sizeof(*dapa);
284         dapa->count = 1;        /* 1 sector */
285         dapa->off = OFFS(buf);
286         dapa->seg = SEG(buf);
287         dapa->lba = lba;
288
289         inreg.esi.w[0] = OFFS(dapa);
290         inreg.ds = SEG(dapa);
291         inreg.edx.b[0] = disk_info.disk;
292         inreg.eax.w[0] = 0x4300;        /* Extended write */
293     } else {
294         unsigned int c, h, s, t;
295
296         if (!disk_info.cbios) {
297             /* We failed to get the geometry */
298
299             if (lba)
300                 return -1;      /* Can only write MBR */
301
302             s = 1;
303             h = 0;
304             c = 0;
305         } else {
306             s = (lba % disk_info.sect) + 1;
307             t = lba / disk_info.sect;   /* Track = head*cyl */
308             h = t % disk_info.head;
309             c = t / disk_info.head;
310         }
311
312         if (s > 63 || h > 256 || c > 1023)
313             return -1;
314
315         inreg.eax.w[0] = 0x0301;        /* Write one sector */
316         inreg.ecx.b[1] = c & 0xff;
317         inreg.ecx.b[0] = s + (c >> 6);
318         inreg.edx.b[1] = h;
319         inreg.edx.b[0] = disk_info.disk;
320         inreg.ebx.w[0] = OFFS(buf);
321         inreg.es = SEG(buf);
322     }
323
324     if (int13_retry(&inreg, NULL))
325         return -1;
326
327     return 0;                   /* ok */
328 }
329
330 static int write_verify_sector(unsigned int lba, const void *buf)
331 {
332     char *rb;
333     int rv;
334
335     rv = write_sector(lba, buf);
336     if (rv)
337         return rv;              /* Write failure */
338     rb = read_sectors(lba, 1);
339     if (!rb)
340         return -1;              /* Readback failure */
341     rv = memcmp(buf, rb, SECTOR);
342     free(rb);
343     return rv ? -1 : 0;
344 }
345
346 /*
347  * CHS (cylinder, head, sector) value extraction macros.
348  * Taken from WinVBlock.  Does not expand to an lvalue
349 */
350 #define     chs_head(chs) chs[0]
351 #define   chs_sector(chs) (chs[1] & 0x3F)
352 #define chs_cyl_high(chs) (((uint16_t)(chs[1] & 0xC0)) << 2)
353 #define  chs_cyl_low(chs) ((uint16_t)chs[2])
354 #define chs_cylinder(chs) (chs_cyl_high(chs) | chs_cyl_low(chs))
355 typedef uint8_t chs[3];
356
357 /* A DOS partition table entry */
358 struct part_entry {
359     uint8_t active_flag;        /* 0x80 if "active" */
360     chs start;
361     uint8_t ostype;
362     chs end;
363     uint32_t start_lba;
364     uint32_t length;
365 } __attribute__ ((packed));
366
367 /* A DOS MBR */
368 struct mbr {
369     char code[440];
370     uint32_t disk_sig;
371     char pad[2];
372     struct part_entry table[4];
373     uint16_t sig;
374 } __attribute__ ((packed));
375 static const uint16_t mbr_sig_magic = 0xAA55;
376
377 /* Search for a specific drive, based on the MBR signature; bytes
378    440-443. */
379 static int find_disk(uint32_t mbr_sig)
380 {
381     int drive;
382     bool is_me;
383     struct mbr *mbr;
384
385     for (drive = 0x80; drive <= 0xff; drive++) {
386         if (get_disk_params(drive))
387             continue;           /* Drive doesn't exist */
388         if (!(mbr = read_sectors(0, 1)))
389             continue;           /* Cannot read sector */
390         is_me = (mbr->disk_sig == mbr_sig);
391         free(mbr);
392         if (is_me)
393             return drive;
394     }
395     return -1;
396 }
397
398 /* Search for a logical partition.  Logical partitions are actually implemented
399    as recursive partition tables; theoretically they're supposed to form a
400    linked list, but other structures have been seen.
401
402    To make things extra confusing: data partition offsets are relative to where
403    the data partition record is stored, whereas extended partition offsets
404    are relative to the beginning of the extended partition all the way back
405    at the MBR... but still not absolute! */
406
407 int nextpart;                   /* Number of the next logical partition */
408
409 static struct part_entry *find_logical_partition(int whichpart, struct mbr *br,
410                                                  struct part_entry *self,
411                                                  struct part_entry *root)
412 {
413     static struct part_entry ltab_entry;
414     struct part_entry *ptab = br->table;
415     struct part_entry *found;
416     struct mbr *ebr;
417
418     int i;
419
420     if (br->sig != mbr_sig_magic)
421         return NULL;            /* Signature missing */
422
423     /* We are assumed to already having enumerated all the data partitions
424        in this table if this is the MBR.  For MBR, self == NULL. */
425
426     if (self) {
427         /* Scan the data partitions. */
428
429         for (i = 0; i < 4; i++) {
430             if (ptab[i].ostype == 0x00 || ptab[i].ostype == 0x05 ||
431                 ptab[i].ostype == 0x0f || ptab[i].ostype == 0x85)
432                 continue;       /* Skip empty or extended partitions */
433
434             if (!ptab[i].length)
435                 continue;
436
437             /* Adjust the offset to account for the extended partition itself */
438             ptab[i].start_lba += self->start_lba;
439
440             /*
441              * Sanity check entry: must not extend outside the
442              * extended partition.  This is necessary since some OSes
443              * put crap in some entries.  Note that root is non-NULL here.
444              */
445             if (ptab[i].start_lba + ptab[i].length <= root->start_lba ||
446                 ptab[i].start_lba >= root->start_lba + root->length)
447                 continue;
448
449             /* OK, it's a data partition.  Is it the one we're looking for? */
450             if (nextpart++ == whichpart) {
451                 memcpy(&ltab_entry, &ptab[i], sizeof ltab_entry);
452                 return &ltab_entry;
453             }
454         }
455     }
456
457     /* Scan the extended partitions. */
458     for (i = 0; i < 4; i++) {
459         if (ptab[i].ostype != 0x05 &&
460             ptab[i].ostype != 0x0f && ptab[i].ostype != 0x85)
461             continue;           /* Skip empty or data partitions */
462
463         if (!ptab[i].length)
464             continue;
465
466         /* Adjust the offset to account for the extended partition itself */
467         if (root)
468             ptab[i].start_lba += root->start_lba;
469
470         /* Sanity check entry: must not extend outside the extended partition.
471            This is necessary since some OSes put crap in some entries. */
472         if (root)
473             if (ptab[i].start_lba + ptab[i].length <= root->start_lba ||
474                 ptab[i].start_lba >= root->start_lba + root->length)
475                 continue;
476
477         /* Process this partition */
478         if (!(ebr = read_sectors(ptab[i].start_lba, 1)))
479             continue;           /* Read error, must be invalid */
480
481         found = find_logical_partition(whichpart, ebr, &ptab[i],
482                                        root ? root : &ptab[i]);
483         free(ebr);
484         if (found)
485             return found;
486     }
487
488     /* If we get here, there ain't nothing... */
489     return NULL;
490 }
491
492 static void do_boot(struct data_area *data, int ndata,
493                     struct syslinux_rm_regs *regs)
494 {
495     uint16_t *const bios_fbm = (uint16_t *) 0x413;
496     addr_t dosmem = *bios_fbm << 10;    /* Technically a low bound */
497     struct syslinux_memmap *mmap;
498     struct syslinux_movelist *mlist = NULL;
499     addr_t endimage;
500     uint8_t driveno = regs->edx.b[0];
501     uint8_t swapdrive = driveno & 0x80;
502     int i;
503
504     mmap = syslinux_memory_map();
505
506     if (!mmap) {
507         error("Cannot read system memory map\n");
508         return;
509     }
510
511     endimage = 0;
512     for (i = 0; i < ndata; i++) {
513         if (data[i].base + data[i].size > endimage)
514             endimage = data[i].base + data[i].size;
515     }
516     if (endimage > dosmem)
517         goto too_big;
518
519     for (i = 0; i < ndata; i++) {
520         if (syslinux_add_movelist(&mlist, data[i].base,
521                                   (addr_t) data[i].data, data[i].size))
522             goto enomem;
523     }
524
525     if (opt.swap && driveno != swapdrive) {
526         static const uint8_t swapstub_master[] = {
527             /* The actual swap code */
528             0x53,               /* 00: push bx */
529             0x0f, 0xb6, 0xda,   /* 01: movzx bx,dl */
530             0x2e, 0x8a, 0x57, 0x60,     /* 04: mov dl,[cs:bx+0x60] */
531             0x5b,               /* 08: pop bx */
532             0xea, 0, 0, 0, 0,   /* 09: jmp far 0:0 */
533             0x90, 0x90,         /* 0E: nop; nop */
534             /* Code to install this in the right location */
535             /* Entry with DS = CS; ES = SI = 0; CX = 256 */
536             0x26, 0x66, 0x8b, 0x7c, 0x4c,       /* 10: mov edi,[es:si+4*0x13] */
537             0x66, 0x89, 0x3e, 0x0a, 0x00,       /* 15: mov [0x0A],edi */
538             0x26, 0x8b, 0x3e, 0x13, 0x04,       /* 1A: mov di,[es:0x413] */
539             0x4f,               /* 1F: dec di */
540             0x26, 0x89, 0x3e, 0x13, 0x04,       /* 20: mov [es:0x413],di */
541             0x66, 0xc1, 0xe7, 0x16,     /* 25: shl edi,16+6 */
542             0x26, 0x66, 0x89, 0x7c, 0x4c,       /* 29: mov [es:si+4*0x13],edi */
543             0x66, 0xc1, 0xef, 0x10,     /* 2E: shr edi,16 */
544             0x8e, 0xc7,         /* 32: mov es,di */
545             0x31, 0xff,         /* 34: xor di,di */
546             0xf3, 0x66, 0xa5,   /* 36: rep movsd */
547             0xbe, 0, 0,         /* 39: mov si,0 */
548             0xbf, 0, 0,         /* 3C: mov di,0 */
549             0x8e, 0xde,         /* 3F: mov ds,si */
550             0x8e, 0xc7,         /* 41: mov es,di */
551             0x66, 0xb9, 0, 0, 0, 0,     /* 43: mov ecx,0 */
552             0x66, 0xbe, 0, 0, 0, 0,     /* 49: mov esi,0 */
553             0x66, 0xbf, 0, 0, 0, 0,     /* 4F: mov edi,0 */
554             0xea, 0, 0, 0, 0,   /* 55: jmp 0:0 */
555             /* pad out to segment boundary */
556             0x90, 0x90,         /* 5A: ... */
557             0x90, 0x90, 0x90, 0x90,     /* 5C: ... */
558         };
559         static uint8_t swapstub[1024];
560         uint8_t *p;
561
562         /* Note: we can't rely on either INT 13h nor the dosmem
563            vector to be correct at this stage, so we have to use an
564            installer stub to put things in the right place.
565            Round the installer location to a 1K boundary so the only
566            possible overlap is the identity mapping. */
567         endimage = (endimage + 1023) & ~1023;
568
569         /* Create swap stub */
570         memcpy(swapstub, swapstub_master, sizeof swapstub_master);
571         *(uint16_t *) & swapstub[0x3a] = regs->ds;
572         *(uint16_t *) & swapstub[0x3d] = regs->es;
573         *(uint32_t *) & swapstub[0x45] = regs->ecx.l;
574         *(uint32_t *) & swapstub[0x4b] = regs->esi.l;
575         *(uint32_t *) & swapstub[0x51] = regs->edi.l;
576         *(uint16_t *) & swapstub[0x56] = regs->ip;
577         *(uint16_t *) & swapstub[0x58] = regs->cs;
578         p = &swapstub[sizeof swapstub_master];
579
580         /* Mapping table; start out with identity mapping everything */
581         for (i = 0; i < 256; i++)
582             p[i] = i;
583
584         /* And the actual swap */
585         p[driveno] = swapdrive;
586         p[swapdrive] = driveno;
587
588         /* Adjust registers */
589         regs->ds = regs->cs = endimage >> 4;
590         regs->es = regs->esi.l = 0;
591         regs->ecx.l = sizeof swapstub >> 2;
592         regs->ip = 0x10;        /* Installer offset */
593         regs->ebx.b[0] = regs->edx.b[0] = swapdrive;
594
595         if (syslinux_add_movelist(&mlist, endimage, (addr_t) swapstub,
596                                   sizeof swapstub))
597             goto enomem;
598
599         endimage += sizeof swapstub;
600     }
601
602     /* Tell the shuffler not to muck with this area... */
603     syslinux_add_memmap(&mmap, endimage, 0xa0000 - endimage, SMT_RESERVED);
604
605     /* Force text mode */
606     syslinux_force_text_mode();
607
608     fputs("Booting...\n", stdout);
609     syslinux_shuffle_boot_rm(mlist, mmap, opt.keeppxe, regs);
610     error("Chainboot failed!\n");
611     return;
612
613 too_big:
614     error("Loader file too large\n");
615     return;
616
617 enomem:
618     error("Out of memory\n");
619     return;
620 }
621
622 static int hide_unhide(struct mbr *mbr, int part)
623 {
624     int i;
625     struct part_entry *pt;
626     const uint16_t mask =
627         (1 << 0x01) | (1 << 0x04) | (1 << 0x06) | (1 << 0x07) | (1 << 0x0b) | (1
628                                                                                <<
629                                                                                0x0c)
630         | (1 << 0x0e);
631     uint8_t t;
632     bool write_back = false;
633
634     for (i = 1; i <= 4; i++) {
635         pt = mbr->table + i - 1;
636         t = pt->ostype;
637         if ((t <= 0x1f) && ((mask >> (t & ~0x10)) & 1)) {
638             /* It's a hideable partition type */
639             if (i == part)
640                 t &= ~0x10;     /* unhide */
641             else
642                 t |= 0x10;      /* hide */
643         }
644         if (t != pt->ostype) {
645             write_back = true;
646             pt->ostype = t;
647         }
648     }
649
650     if (write_back)
651         return write_verify_sector(0, mbr);
652
653     return 0;                   /* ok */
654 }
655
656 static uint32_t get_file_lba(const char *filename)
657 {
658     com32sys_t inregs;
659     uint32_t lba;
660
661     /* Start with clean registers */
662     memset(&inregs, 0, sizeof(com32sys_t));
663
664     /* Put the filename in the bounce buffer */
665     strlcpy(__com32.cs_bounce, filename, __com32.cs_bounce_size);
666
667     /* Call comapi_open() which returns a structure pointer in SI
668      * to a structure whose first member happens to be the LBA.
669      */
670     inregs.eax.w[0] = 0x0006;
671     inregs.esi.w[0] = OFFS(__com32.cs_bounce);
672     inregs.es = SEG(__com32.cs_bounce);
673     __com32.cs_intcall(0x22, &inregs, &inregs);
674
675     if ((inregs.eflags.l & EFLAGS_CF) || inregs.esi.w[0] == 0) {
676         return 0;               /* Filename not found */
677     }
678
679     /* Since the first member is the LBA, we simply cast */
680     lba = *((uint32_t *) MK_PTR(inregs.ds, inregs.esi.w[0]));
681
682     /* Clean the registers for the next call */
683     memset(&inregs, 0, sizeof(com32sys_t));
684
685     /* Put the filename in the bounce buffer */
686     strlcpy(__com32.cs_bounce, filename, __com32.cs_bounce_size);
687
688     /* Call comapi_close() to free the structure */
689     inregs.eax.w[0] = 0x0008;
690     inregs.esi.w[0] = OFFS(__com32.cs_bounce);
691     inregs.es = SEG(__com32.cs_bounce);
692     __com32.cs_intcall(0x22, &inregs, &inregs);
693
694     return lba;
695 }
696
697 static void usage(void)
698 {
699     error("Usage:   chain.c32 hd<disk#> [<partition>] [options]\n"
700           "         chain.c32 fd<disk#> [options]\n"
701           "         chain.c32 mbr:<id> [<partition>] [options]\n"
702           "         chain.c32 boot [<partition>] [options]\n"
703           "Options: file=<loader>      load file, instead of boot sector\n"
704           "         isolinux=<loader>  load another version of ISOLINUX\n"
705           "         ntldr=<loader>     load Windows NTLDR, SETUPLDR.BIN or BOOTMGR\n"
706           "         cmldr=<loader>     load Recovery Console of Windows NT/2K/XP\n"
707           "         freedos=<loader>   load FreeDOS kernel.sys\n"
708           "         msdos=<loader>     load MS-DOS io.sys\n"
709           "         pcdos=<loader>     load PC-DOS ibmbio.com\n"
710           "         grub=<loader>      load GRUB stage2\n"
711           "         seg=<segment>      jump to <seg>:0000 instead of 0000:7C00\n"
712           "         swap               swap drive numbers, if bootdisk is not fd0/hd0\n"
713           "         hide               hide primary partitions, except selected partition\n"
714           "         sethidden          set the FAT/NTFS hidden sectors field\n");
715 }
716
717 int main(int argc, char *argv[])
718 {
719     struct mbr *mbr;
720     char *p;
721     struct part_entry *partinfo;
722     struct syslinux_rm_regs regs;
723     char *drivename, *partition;
724     int hd, drive, whichpart;
725     int i;
726     uint32_t file_lba = 0;
727     unsigned char *isolinux_bin;
728     uint32_t *checksum, *chkhead, *chktail;
729     struct data_area data[3];
730     int ndata = 0;
731     addr_t load_base;
732     static const char cmldr_signature[8] = "cmdcons";
733
734     openconsole(&dev_null_r, &dev_stdcon_w);
735
736     drivename = "boot";
737     partition = NULL;
738
739     /* Prepare the register set */
740     memset(&regs, 0, sizeof regs);
741
742     for (i = 1; i < argc; i++) {
743         if (!strncmp(argv[i], "file=", 5)) {
744             opt.loadfile = argv[i] + 5;
745         } else if (!strncmp(argv[i], "seg=", 4)) {
746             uint32_t segval = strtoul(argv[i] + 4, NULL, 0);
747             if (segval < 0x50 || segval > 0x9f000) {
748                 error("Invalid segment\n");
749                 goto bail;
750             }
751             opt.seg = segval;
752         } else if (!strncmp(argv[i], "isolinux=", 9)) {
753             opt.loadfile = argv[i] + 9;
754             opt.isolinux = true;
755         } else if (!strncmp(argv[i], "ntldr=", 6)) {
756             opt.seg = 0x2000;   /* NTLDR wants this address */
757             opt.loadfile = argv[i] + 6;
758             opt.sethidden = true;
759         } else if (!strncmp(argv[i], "cmldr=", 6)) {
760             opt.seg = 0x2000;   /* CMLDR wants this address */
761             opt.loadfile = argv[i] + 6;
762             opt.cmldr = true;
763             opt.sethidden = true;
764         } else if (!strncmp(argv[i], "freedos=", 8)) {
765             opt.seg = 0x60;     /* FREEDOS wants this address */
766             opt.loadfile = argv[i] + 8;
767             opt.sethidden = true;
768         } else if (!strncmp(argv[i], "msdos=", 6) ||
769                    !strncmp(argv[i], "pcdos=", 6)) {
770             opt.seg = 0x70;     /* MS-DOS 2.0+ wants this address */
771             opt.loadfile = argv[i] + 6;
772             opt.sethidden = true;
773         } else if (!strncmp(argv[i], "grub=", 5)) {
774             opt.seg = 0x800;    /* stage2 wants this address */
775             opt.loadfile = argv[i] + 5;
776             opt.grub = true;
777         } else if (!strcmp(argv[i], "swap")) {
778             opt.swap = true;
779         } else if (!strcmp(argv[i], "noswap")) {
780             opt.swap = false;
781         } else if (!strcmp(argv[i], "hide")) {
782             opt.hide = true;
783         } else if (!strcmp(argv[i], "nohide")) {
784             opt.hide = false;
785         } else if (!strcmp(argv[i], "keeppxe")) {
786             opt.keeppxe = 3;
787         } else if (!strcmp(argv[i], "sethidden")) {
788             opt.sethidden = true;
789         } else if (!strcmp(argv[i], "nosethidden")) {
790             opt.sethidden = false;
791         } else if (((argv[i][0] == 'h' || argv[i][0] == 'f')
792                     && argv[i][1] == 'd')
793                    || !strncmp(argv[i], "mbr:", 4)
794                    || !strncmp(argv[i], "mbr=", 4)
795                    || !strcmp(argv[i], "boot")
796                    || !strncmp(argv[i], "boot,", 5)) {
797             drivename = argv[i];
798             p = strchr(drivename, ',');
799             if (p) {
800                 *p = '\0';
801                 partition = p + 1;
802             } else if (argv[i + 1] && argv[i + 1][0] >= '0'
803                        && argv[i + 1][0] <= '9') {
804                 partition = argv[++i];
805             }
806         } else {
807             usage();
808             goto bail;
809         }
810     }
811
812     if (opt.seg) {
813         regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = opt.seg;
814     } else {
815         regs.ip = regs.esp.l = 0x7c00;
816     }
817
818     hd = 0;
819     if (!strncmp(drivename, "mbr", 3)) {
820         drive = find_disk(strtoul(drivename + 4, NULL, 0));
821         if (drive == -1) {
822             error("Unable to find requested MBR signature\n");
823             goto bail;
824         }
825     } else if ((drivename[0] == 'h' || drivename[0] == 'f') &&
826                drivename[1] == 'd') {
827         hd = drivename[0] == 'h';
828         drivename += 2;
829         drive = (hd ? 0x80 : 0) | strtoul(drivename, NULL, 0);
830     } else if (!strcmp(drivename, "boot")) {
831         const union syslinux_derivative_info *sdi;
832         sdi = syslinux_derivative_info();
833         if (sdi->c.filesystem == SYSLINUX_FS_PXELINUX)
834             drive = 0x80;       /* Boot drive not available */
835         else
836             drive = sdi->disk.drive_number;
837     } else {
838         error("Unparsable drive specification\n");
839         goto bail;
840     }
841
842     /* DOS kernels want the drive number in BL instead of DL.  Indulge them. */
843     regs.ebx.b[0] = regs.edx.b[0] = drive;
844
845     whichpart = 0;              /* Default */
846     if (partition)
847         whichpart = strtoul(partition, NULL, 0);
848
849     if (!(drive & 0x80) && whichpart) {
850         error("Warning: Partitions of floppy devices may not work\n");
851     }
852
853     /* 
854      * grldr of Grub4dos wants the partition number in DH:
855      * -1:   whole drive (default)
856      * 0-3:  primary partitions
857      * 4-*:  logical partitions
858      */
859     regs.edx.b[1] = whichpart - 1;
860
861     /* Get the disk geometry and disk access setup */
862     if (get_disk_params(drive)) {
863         error("Cannot get disk parameters\n");
864         goto bail;
865     }
866
867     /* Get MBR */
868     if (!(mbr = read_sectors(0, 1))) {
869         error("Cannot read Master Boot Record\n");
870         goto bail;
871     }
872
873     if (opt.hide) {
874         if (whichpart < 1 || whichpart > 4)
875             error("WARNING: hide specified without a non-primary partition\n");
876         if (hide_unhide(mbr, whichpart))
877             error("WARNING: failed to write MBR for 'hide'\n");
878     }
879
880     if (whichpart == 0) {
881         /* Boot the MBR */
882
883         partinfo = NULL;
884     } else if (whichpart <= 4) {
885         /* Boot a primary partition */
886
887         partinfo = mbr->table + whichpart - 1;
888         if (partinfo->ostype == 0) {
889             error("Invalid primary partition\n");
890             goto bail;
891         }
892     } else {
893         /* Boot a logical partition */
894
895         nextpart = 5;
896         partinfo = find_logical_partition(whichpart, mbr, NULL, NULL);
897
898         if (!partinfo || partinfo->ostype == 0) {
899             error("Requested logical partition not found\n");
900             goto bail;
901         }
902     }
903
904     /* Do the actual chainloading */
905     load_base = opt.seg ? (opt.seg << 4) : 0x7c00;
906
907     if (opt.loadfile) {
908         fputs("Loading the boot file...\n", stdout);
909         if (loadfile(opt.loadfile, &data[ndata].data, &data[ndata].size)) {
910             error("Failed to load the boot file\n");
911             goto bail;
912         }
913         data[ndata].base = load_base;
914         load_base = 0x7c00;     /* If we also load a boot sector */
915
916         /* Create boot info table: needed when you want to chainload
917            another version of ISOLINUX (or another bootlaoder that needs
918            the -boot-info-table switch of mkisofs)
919            (will only work when run from ISOLINUX) */
920         if (opt.isolinux) {
921             const union syslinux_derivative_info *sdi;
922             sdi = syslinux_derivative_info();
923
924             if (sdi->c.filesystem == SYSLINUX_FS_ISOLINUX) {
925                 /* Boot info table info (integers in little endian format)
926
927                    Offset Name         Size      Meaning
928                    8     bi_pvd       4 bytes   LBA of primary volume descriptor
929                    12     bi_file      4 bytes   LBA of boot file
930                    16     bi_length    4 bytes   Boot file length in bytes
931                    20     bi_csum      4 bytes   32-bit checksum
932                    24     bi_reserved  40 bytes  Reserved
933
934                    The 32-bit checksum is the sum of all the 32-bit words in the
935                    boot file starting at byte offset 64. All linear block
936                    addresses (LBAs) are given in CD sectors (normally 2048 bytes).
937
938                    LBA of primary volume descriptor should already be set to 16. 
939                  */
940
941                 isolinux_bin = (unsigned char *)data[ndata].data;
942
943                 /* Get LBA address of bootfile */
944                 file_lba = get_file_lba(opt.loadfile);
945
946                 if (file_lba == 0) {
947                     error("Failed to find LBA offset of the boot file\n");
948                     goto bail;
949                 }
950                 /* Set it */
951                 *((uint32_t *) & isolinux_bin[12]) = file_lba;
952
953                 /* Set boot file length */
954                 *((uint32_t *) & isolinux_bin[16]) = data[ndata].size;
955
956                 /* Calculate checksum */
957                 checksum = (uint32_t *) & isolinux_bin[20];
958                 chkhead = (uint32_t *) & isolinux_bin[64];
959                 chktail = (uint32_t *) & isolinux_bin[data[ndata].size & ~3];
960                 *checksum = 0;
961                 while (chkhead < chktail)
962                     *checksum += *chkhead++;
963
964                 /*
965                  * Deal with possible fractional dword at the end;
966                  * this *should* never happen...
967                  */
968                 if (data[ndata].size & 3) {
969                     uint32_t xword = 0;
970                     memcpy(&xword, chkhead, data[ndata].size & 3);
971                     *checksum += xword;
972                 }
973             } else {
974                 error
975                     ("The isolinux= option is only valid when run from ISOLINUX\n");
976                 goto bail;
977             }
978         }
979
980         if (opt.grub) {
981             regs.ip = 0x200;    /* jump 0x200 bytes into the loadfile */
982
983             /* 0xffffff00 seems to be GRUB ways to record that it's
984                "root" is the whole disk (and not a partition). */
985             *(uint32_t *) ((unsigned char *)data[ndata].data + 0x208) =
986                 0xffffff00ul;
987         }
988
989         ndata++;
990     }
991
992     if (!opt.loadfile || data[0].base >= 0x7c00 + SECTOR) {
993         /* Actually read the boot sector */
994         if (!partinfo) {
995             data[ndata].data = mbr;
996         } else if (!(data[ndata].data = read_sectors(partinfo->start_lba, 1))) {
997             error("Cannot read boot sector\n");
998             goto bail;
999         }
1000         data[ndata].size = SECTOR;
1001         data[ndata].base = load_base;
1002
1003         if (!opt.loadfile) {
1004             const struct mbr *br =
1005                 (const struct mbr *)((char *)data[ndata].data +
1006                                      data[ndata].size - sizeof(struct mbr));
1007             if (br->sig != mbr_sig_magic) {
1008                 error
1009                     ("Boot sector signature not found (unbootable disk/partition?)\n");
1010                 goto bail;
1011             }
1012         }
1013         /*
1014          * To boot the Recovery Console of Windows NT/2K/XP we need to write
1015          * the string "cmdcons\0" to memory location 0000:7C03.
1016          * Memory location 0000:7C00 contains the bootsector of the partition.
1017          */
1018         if (partinfo && opt.cmldr) {
1019             memcpy((char *)data[ndata].data + 3, cmldr_signature,
1020                    sizeof cmldr_signature);
1021         }
1022
1023         /*
1024          * Modify the hidden sectors (partition offset) copy in memory;
1025          * this modifies the field used by FAT and NTFS filesystems, and
1026          * possibly other boot loaders which use the same format.
1027          */
1028         if (partinfo && opt.sethidden) {
1029             *(uint32_t *) ((char *)data[ndata].data + 28) = partinfo->start_lba;
1030         }
1031
1032         ndata++;
1033     }
1034
1035     if (partinfo) {
1036         /* 0x7BE is the canonical place for the first partition entry. */
1037         data[ndata].data = partinfo;
1038         data[ndata].base = 0x7be;
1039         data[ndata].size = sizeof *partinfo;
1040         ndata++;
1041         regs.esi.w[0] = 0x7be;
1042     }
1043
1044     do_boot(data, ndata, &regs);
1045
1046 bail:
1047     return 255;
1048 }