f0ddc389eb43dff4876101a722b142f79511e5c8
[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 /* Search for a specific drive, based on the MBR signature; bytes
347    440-443. */
348 static int find_disk(uint32_t mbr_sig)
349 {
350     int drive;
351     bool is_me;
352     char *buf;
353
354     for (drive = 0x80; drive <= 0xff; drive++) {
355         if (get_disk_params(drive))
356             continue;           /* Drive doesn't exist */
357         if (!(buf = read_sectors(0, 1)))
358             continue;           /* Cannot read sector */
359         is_me = (*(uint32_t *) ((char *)buf + 440) == mbr_sig);
360         free(buf);
361         if (is_me)
362             return drive;
363     }
364     return -1;
365 }
366
367 /*
368  * CHS (cylinder, head, sector) value extraction macros.
369  * Taken from WinVBlock.  Does not expand to an lvalue
370 */
371 #define     chs_head(chs) chs[0]
372 #define   chs_sector(chs) (chs[1] & 0x3F)
373 #define chs_cyl_high(chs) (((uint16_t)(chs[1] & 0xC0)) << 2)
374 #define  chs_cyl_low(chs) ((uint16_t)chs[2])
375 #define chs_cylinder(chs) (chs_cyl_high(chs) | chs_cyl_low(chs))
376 typedef uint8_t chs[3];
377
378 /* A DOS partition table entry */
379 struct part_entry {
380     uint8_t active_flag;        /* 0x80 if "active" */
381     chs start;
382     uint8_t ostype;
383     chs end;
384     uint32_t start_lba;
385     uint32_t length;
386 } __attribute__ ((packed));
387
388 /* Search for a logical partition.  Logical partitions are actually implemented
389    as recursive partition tables; theoretically they're supposed to form a
390    linked list, but other structures have been seen.
391
392    To make things extra confusing: data partition offsets are relative to where
393    the data partition record is stored, whereas extended partition offsets
394    are relative to the beginning of the extended partition all the way back
395    at the MBR... but still not absolute! */
396
397 int nextpart;                   /* Number of the next logical partition */
398
399 static struct part_entry *find_logical_partition(int whichpart, char *table,
400                                                  struct part_entry *self,
401                                                  struct part_entry *root)
402 {
403     static struct part_entry ltab_entry;
404     struct part_entry *ptab = (struct part_entry *)(table + 0x1be);
405     struct part_entry *found;
406     char *sector;
407
408     int i;
409
410     if (*(uint16_t *) (table + 0x1fe) != 0xaa55)
411         return NULL;            /* Signature missing */
412
413     /* We are assumed to already having enumerated all the data partitions
414        in this table if this is the MBR.  For MBR, self == NULL. */
415
416     if (self) {
417         /* Scan the data partitions. */
418
419         for (i = 0; i < 4; i++) {
420             if (ptab[i].ostype == 0x00 || ptab[i].ostype == 0x05 ||
421                 ptab[i].ostype == 0x0f || ptab[i].ostype == 0x85)
422                 continue;       /* Skip empty or extended partitions */
423
424             if (!ptab[i].length)
425                 continue;
426
427             /* Adjust the offset to account for the extended partition itself */
428             ptab[i].start_lba += self->start_lba;
429
430             /*
431              * Sanity check entry: must not extend outside the
432              * extended partition.  This is necessary since some OSes
433              * put crap in some entries.  Note that root is non-NULL here.
434              */
435             if (ptab[i].start_lba + ptab[i].length <= root->start_lba ||
436                 ptab[i].start_lba >= root->start_lba + root->length)
437                 continue;
438
439             /* OK, it's a data partition.  Is it the one we're looking for? */
440             if (nextpart++ == whichpart) {
441                 memcpy(&ltab_entry, &ptab[i], sizeof ltab_entry);
442                 return &ltab_entry;
443             }
444         }
445     }
446
447     /* Scan the extended partitions. */
448     for (i = 0; i < 4; i++) {
449         if (ptab[i].ostype != 0x05 &&
450             ptab[i].ostype != 0x0f && ptab[i].ostype != 0x85)
451             continue;           /* Skip empty or data partitions */
452
453         if (!ptab[i].length)
454             continue;
455
456         /* Adjust the offset to account for the extended partition itself */
457         if (root)
458             ptab[i].start_lba += root->start_lba;
459
460         /* Sanity check entry: must not extend outside the extended partition.
461            This is necessary since some OSes put crap in some entries. */
462         if (root)
463             if (ptab[i].start_lba + ptab[i].length <= root->start_lba ||
464                 ptab[i].start_lba >= root->start_lba + root->length)
465                 continue;
466
467         /* Process this partition */
468         if (!(sector = read_sectors(ptab[i].start_lba, 1)))
469             continue;           /* Read error, must be invalid */
470
471         found = find_logical_partition(whichpart, sector, &ptab[i],
472                                        root ? root : &ptab[i]);
473         free(sector);
474         if (found)
475             return found;
476     }
477
478     /* If we get here, there ain't nothing... */
479     return NULL;
480 }
481
482 static void do_boot(struct data_area *data, int ndata,
483                     struct syslinux_rm_regs *regs)
484 {
485     uint16_t *const bios_fbm = (uint16_t *) 0x413;
486     addr_t dosmem = *bios_fbm << 10;    /* Technically a low bound */
487     struct syslinux_memmap *mmap;
488     struct syslinux_movelist *mlist = NULL;
489     addr_t endimage;
490     uint8_t driveno = regs->edx.b[0];
491     uint8_t swapdrive = driveno & 0x80;
492     int i;
493
494     mmap = syslinux_memory_map();
495
496     if (!mmap) {
497         error("Cannot read system memory map\n");
498         return;
499     }
500
501     endimage = 0;
502     for (i = 0; i < ndata; i++) {
503         if (data[i].base + data[i].size > endimage)
504             endimage = data[i].base + data[i].size;
505     }
506     if (endimage > dosmem)
507         goto too_big;
508
509     for (i = 0; i < ndata; i++) {
510         if (syslinux_add_movelist(&mlist, data[i].base,
511                                   (addr_t) data[i].data, data[i].size))
512             goto enomem;
513     }
514
515     if (opt.swap && driveno != swapdrive) {
516         static const uint8_t swapstub_master[] = {
517             /* The actual swap code */
518             0x53,               /* 00: push bx */
519             0x0f, 0xb6, 0xda,   /* 01: movzx bx,dl */
520             0x2e, 0x8a, 0x57, 0x60,     /* 04: mov dl,[cs:bx+0x60] */
521             0x5b,               /* 08: pop bx */
522             0xea, 0, 0, 0, 0,   /* 09: jmp far 0:0 */
523             0x90, 0x90,         /* 0E: nop; nop */
524             /* Code to install this in the right location */
525             /* Entry with DS = CS; ES = SI = 0; CX = 256 */
526             0x26, 0x66, 0x8b, 0x7c, 0x4c,       /* 10: mov edi,[es:si+4*0x13] */
527             0x66, 0x89, 0x3e, 0x0a, 0x00,       /* 15: mov [0x0A],edi */
528             0x26, 0x8b, 0x3e, 0x13, 0x04,       /* 1A: mov di,[es:0x413] */
529             0x4f,               /* 1F: dec di */
530             0x26, 0x89, 0x3e, 0x13, 0x04,       /* 20: mov [es:0x413],di */
531             0x66, 0xc1, 0xe7, 0x16,     /* 25: shl edi,16+6 */
532             0x26, 0x66, 0x89, 0x7c, 0x4c,       /* 29: mov [es:si+4*0x13],edi */
533             0x66, 0xc1, 0xef, 0x10,     /* 2E: shr edi,16 */
534             0x8e, 0xc7,         /* 32: mov es,di */
535             0x31, 0xff,         /* 34: xor di,di */
536             0xf3, 0x66, 0xa5,   /* 36: rep movsd */
537             0xbe, 0, 0,         /* 39: mov si,0 */
538             0xbf, 0, 0,         /* 3C: mov di,0 */
539             0x8e, 0xde,         /* 3F: mov ds,si */
540             0x8e, 0xc7,         /* 41: mov es,di */
541             0x66, 0xb9, 0, 0, 0, 0,     /* 43: mov ecx,0 */
542             0x66, 0xbe, 0, 0, 0, 0,     /* 49: mov esi,0 */
543             0x66, 0xbf, 0, 0, 0, 0,     /* 4F: mov edi,0 */
544             0xea, 0, 0, 0, 0,   /* 55: jmp 0:0 */
545             /* pad out to segment boundary */
546             0x90, 0x90,         /* 5A: ... */
547             0x90, 0x90, 0x90, 0x90,     /* 5C: ... */
548         };
549         static uint8_t swapstub[1024];
550         uint8_t *p;
551
552         /* Note: we can't rely on either INT 13h nor the dosmem
553            vector to be correct at this stage, so we have to use an
554            installer stub to put things in the right place.
555            Round the installer location to a 1K boundary so the only
556            possible overlap is the identity mapping. */
557         endimage = (endimage + 1023) & ~1023;
558
559         /* Create swap stub */
560         memcpy(swapstub, swapstub_master, sizeof swapstub_master);
561         *(uint16_t *) & swapstub[0x3a] = regs->ds;
562         *(uint16_t *) & swapstub[0x3d] = regs->es;
563         *(uint32_t *) & swapstub[0x45] = regs->ecx.l;
564         *(uint32_t *) & swapstub[0x4b] = regs->esi.l;
565         *(uint32_t *) & swapstub[0x51] = regs->edi.l;
566         *(uint16_t *) & swapstub[0x56] = regs->ip;
567         *(uint16_t *) & swapstub[0x58] = regs->cs;
568         p = &swapstub[sizeof swapstub_master];
569
570         /* Mapping table; start out with identity mapping everything */
571         for (i = 0; i < 256; i++)
572             p[i] = i;
573
574         /* And the actual swap */
575         p[driveno] = swapdrive;
576         p[swapdrive] = driveno;
577
578         /* Adjust registers */
579         regs->ds = regs->cs = endimage >> 4;
580         regs->es = regs->esi.l = 0;
581         regs->ecx.l = sizeof swapstub >> 2;
582         regs->ip = 0x10;        /* Installer offset */
583         regs->ebx.b[0] = regs->edx.b[0] = swapdrive;
584
585         if (syslinux_add_movelist(&mlist, endimage, (addr_t) swapstub,
586                                   sizeof swapstub))
587             goto enomem;
588
589         endimage += sizeof swapstub;
590     }
591
592     /* Tell the shuffler not to muck with this area... */
593     syslinux_add_memmap(&mmap, endimage, 0xa0000 - endimage, SMT_RESERVED);
594
595     /* Force text mode */
596     syslinux_force_text_mode();
597
598     fputs("Booting...\n", stdout);
599     syslinux_shuffle_boot_rm(mlist, mmap, opt.keeppxe, regs);
600     error("Chainboot failed!\n");
601     return;
602
603 too_big:
604     error("Loader file too large\n");
605     return;
606
607 enomem:
608     error("Out of memory\n");
609     return;
610 }
611
612 static int hide_unhide(char *mbr, int part)
613 {
614     int i;
615     struct part_entry *pt;
616     const uint16_t mask =
617         (1 << 0x01) | (1 << 0x04) | (1 << 0x06) | (1 << 0x07) | (1 << 0x0b) | (1
618                                                                                <<
619                                                                                0x0c)
620         | (1 << 0x0e);
621     uint8_t t;
622     bool write_back = false;
623
624     for (i = 1; i <= 4; i++) {
625         pt = (struct part_entry *)&mbr[0x1be + 16 * (i - 1)];
626         t = pt->ostype;
627         if ((t <= 0x1f) && ((mask >> (t & ~0x10)) & 1)) {
628             /* It's a hideable partition type */
629             if (i == part)
630                 t &= ~0x10;     /* unhide */
631             else
632                 t |= 0x10;      /* hide */
633         }
634         if (t != pt->ostype) {
635             write_back = true;
636             pt->ostype = t;
637         }
638     }
639
640     if (write_back)
641         return write_verify_sector(0, mbr);
642
643     return 0;                   /* ok */
644 }
645
646 static uint32_t get_file_lba(const char *filename)
647 {
648     com32sys_t inregs;
649     uint32_t lba;
650
651     /* Start with clean registers */
652     memset(&inregs, 0, sizeof(com32sys_t));
653
654     /* Put the filename in the bounce buffer */
655     strlcpy(__com32.cs_bounce, filename, __com32.cs_bounce_size);
656
657     /* Call comapi_open() which returns a structure pointer in SI
658      * to a structure whose first member happens to be the LBA.
659      */
660     inregs.eax.w[0] = 0x0006;
661     inregs.esi.w[0] = OFFS(__com32.cs_bounce);
662     inregs.es = SEG(__com32.cs_bounce);
663     __com32.cs_intcall(0x22, &inregs, &inregs);
664
665     if ((inregs.eflags.l & EFLAGS_CF) || inregs.esi.w[0] == 0) {
666         return 0;               /* Filename not found */
667     }
668
669     /* Since the first member is the LBA, we simply cast */
670     lba = *((uint32_t *) MK_PTR(inregs.ds, inregs.esi.w[0]));
671
672     /* Clean the registers for the next call */
673     memset(&inregs, 0, sizeof(com32sys_t));
674
675     /* Put the filename in the bounce buffer */
676     strlcpy(__com32.cs_bounce, filename, __com32.cs_bounce_size);
677
678     /* Call comapi_close() to free the structure */
679     inregs.eax.w[0] = 0x0008;
680     inregs.esi.w[0] = OFFS(__com32.cs_bounce);
681     inregs.es = SEG(__com32.cs_bounce);
682     __com32.cs_intcall(0x22, &inregs, &inregs);
683
684     return lba;
685 }
686
687 static void usage(void)
688 {
689     error("Usage:   chain.c32 hd<disk#> [<partition>] [options]\n"
690           "         chain.c32 fd<disk#> [options]\n"
691           "         chain.c32 mbr:<id> [<partition>] [options]\n"
692           "         chain.c32 boot [<partition>] [options]\n"
693           "Options: file=<loader>      load file, instead of boot sector\n"
694           "         isolinux=<loader>  load another version of ISOLINUX\n"
695           "         ntldr=<loader>     load Windows NTLDR, SETUPLDR.BIN or BOOTMGR\n"
696           "         cmldr=<loader>     load Recovery Console of Windows NT/2K/XP\n"
697           "         freedos=<loader>   load FreeDOS kernel.sys\n"
698           "         msdos=<loader>     load MS-DOS io.sys\n"
699           "         pcdos=<loader>     load PC-DOS ibmbio.com\n"
700           "         grub=<loader>      load GRUB stage2\n"
701           "         seg=<segment>      jump to <seg>:0000 instead of 0000:7C00\n"
702           "         swap               swap drive numbers, if bootdisk is not fd0/hd0\n"
703           "         hide               hide primary partitions, except selected partition\n"
704           "         sethidden          set the FAT/NTFS hidden sectors field\n");
705 }
706
707 int main(int argc, char *argv[])
708 {
709     char *mbr, *p;
710     struct part_entry *partinfo;
711     struct syslinux_rm_regs regs;
712     char *drivename, *partition;
713     int hd, drive, whichpart;
714     int i;
715     uint32_t file_lba = 0;
716     unsigned char *isolinux_bin;
717     uint32_t *checksum, *chkhead, *chktail;
718     struct data_area data[3];
719     int ndata = 0;
720     addr_t load_base;
721     static const char cmldr_signature[8] = "cmdcons";
722
723     openconsole(&dev_null_r, &dev_stdcon_w);
724
725     drivename = "boot";
726     partition = NULL;
727
728     /* Prepare the register set */
729     memset(&regs, 0, sizeof regs);
730
731     for (i = 1; i < argc; i++) {
732         if (!strncmp(argv[i], "file=", 5)) {
733             opt.loadfile = argv[i] + 5;
734         } else if (!strncmp(argv[i], "seg=", 4)) {
735             uint32_t segval = strtoul(argv[i] + 4, NULL, 0);
736             if (segval < 0x50 || segval > 0x9f000) {
737                 error("Invalid segment\n");
738                 goto bail;
739             }
740             opt.seg = segval;
741         } else if (!strncmp(argv[i], "isolinux=", 9)) {
742             opt.loadfile = argv[i] + 9;
743             opt.isolinux = true;
744         } else if (!strncmp(argv[i], "ntldr=", 6)) {
745             opt.seg = 0x2000;   /* NTLDR wants this address */
746             opt.loadfile = argv[i] + 6;
747             opt.sethidden = true;
748         } else if (!strncmp(argv[i], "cmldr=", 6)) {
749             opt.seg = 0x2000;   /* CMLDR wants this address */
750             opt.loadfile = argv[i] + 6;
751             opt.cmldr = true;
752             opt.sethidden = true;
753         } else if (!strncmp(argv[i], "freedos=", 8)) {
754             opt.seg = 0x60;     /* FREEDOS wants this address */
755             opt.loadfile = argv[i] + 8;
756             opt.sethidden = true;
757         } else if (!strncmp(argv[i], "msdos=", 6) ||
758                    !strncmp(argv[i], "pcdos=", 6)) {
759             opt.seg = 0x70;     /* MS-DOS 2.0+ wants this address */
760             opt.loadfile = argv[i] + 6;
761             opt.sethidden = true;
762         } else if (!strncmp(argv[i], "grub=", 5)) {
763             opt.seg = 0x800;    /* stage2 wants this address */
764             opt.loadfile = argv[i] + 5;
765             opt.grub = true;
766         } else if (!strcmp(argv[i], "swap")) {
767             opt.swap = true;
768         } else if (!strcmp(argv[i], "noswap")) {
769             opt.swap = false;
770         } else if (!strcmp(argv[i], "hide")) {
771             opt.hide = true;
772         } else if (!strcmp(argv[i], "nohide")) {
773             opt.hide = false;
774         } else if (!strcmp(argv[i], "keeppxe")) {
775             opt.keeppxe = 3;
776         } else if (!strcmp(argv[i], "sethidden")) {
777             opt.sethidden = true;
778         } else if (!strcmp(argv[i], "nosethidden")) {
779             opt.sethidden = false;
780         } else if (((argv[i][0] == 'h' || argv[i][0] == 'f')
781                     && argv[i][1] == 'd')
782                    || !strncmp(argv[i], "mbr:", 4)
783                    || !strncmp(argv[i], "mbr=", 4)
784                    || !strcmp(argv[i], "boot")
785                    || !strncmp(argv[i], "boot,", 5)) {
786             drivename = argv[i];
787             p = strchr(drivename, ',');
788             if (p) {
789                 *p = '\0';
790                 partition = p + 1;
791             } else if (argv[i + 1] && argv[i + 1][0] >= '0'
792                        && argv[i + 1][0] <= '9') {
793                 partition = argv[++i];
794             }
795         } else {
796             usage();
797             goto bail;
798         }
799     }
800
801     if (opt.seg) {
802         regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = opt.seg;
803     } else {
804         regs.ip = regs.esp.l = 0x7c00;
805     }
806
807     hd = 0;
808     if (!strncmp(drivename, "mbr", 3)) {
809         drive = find_disk(strtoul(drivename + 4, NULL, 0));
810         if (drive == -1) {
811             error("Unable to find requested MBR signature\n");
812             goto bail;
813         }
814     } else if ((drivename[0] == 'h' || drivename[0] == 'f') &&
815                drivename[1] == 'd') {
816         hd = drivename[0] == 'h';
817         drivename += 2;
818         drive = (hd ? 0x80 : 0) | strtoul(drivename, NULL, 0);
819     } else if (!strcmp(drivename, "boot")) {
820         const union syslinux_derivative_info *sdi;
821         sdi = syslinux_derivative_info();
822         if (sdi->c.filesystem == SYSLINUX_FS_PXELINUX)
823             drive = 0x80;       /* Boot drive not available */
824         else
825             drive = sdi->disk.drive_number;
826     } else {
827         error("Unparsable drive specification\n");
828         goto bail;
829     }
830
831     /* DOS kernels want the drive number in BL instead of DL.  Indulge them. */
832     regs.ebx.b[0] = regs.edx.b[0] = drive;
833
834     whichpart = 0;              /* Default */
835     if (partition)
836         whichpart = strtoul(partition, NULL, 0);
837
838     if (!(drive & 0x80) && whichpart) {
839         error("Warning: Partitions of floppy devices may not work\n");
840     }
841
842     /* 
843      * grldr of Grub4dos wants the partition number in DH:
844      * -1:   whole drive (default)
845      * 0-3:  primary partitions
846      * 4-*:  logical partitions
847      */
848     regs.edx.b[1] = whichpart - 1;
849
850     /* Get the disk geometry and disk access setup */
851     if (get_disk_params(drive)) {
852         error("Cannot get disk parameters\n");
853         goto bail;
854     }
855
856     /* Get MBR */
857     if (!(mbr = read_sectors(0, 1))) {
858         error("Cannot read Master Boot Record\n");
859         goto bail;
860     }
861
862     if (opt.hide) {
863         if (whichpart < 1 || whichpart > 4)
864             error("WARNING: hide specified without a non-primary partition\n");
865         if (hide_unhide(mbr, whichpart))
866             error("WARNING: failed to write MBR for 'hide'\n");
867     }
868
869     if (whichpart == 0) {
870         /* Boot the MBR */
871
872         partinfo = NULL;
873     } else if (whichpart <= 4) {
874         /* Boot a primary partition */
875
876         partinfo = &((struct part_entry *)(mbr + 0x1be))[whichpart - 1];
877         if (partinfo->ostype == 0) {
878             error("Invalid primary partition\n");
879             goto bail;
880         }
881     } else {
882         /* Boot a logical partition */
883
884         nextpart = 5;
885         partinfo = find_logical_partition(whichpart, mbr, NULL, NULL);
886
887         if (!partinfo || partinfo->ostype == 0) {
888             error("Requested logical partition not found\n");
889             goto bail;
890         }
891     }
892
893     /* Do the actual chainloading */
894     load_base = opt.seg ? (opt.seg << 4) : 0x7c00;
895
896     if (opt.loadfile) {
897         fputs("Loading the boot file...\n", stdout);
898         if (loadfile(opt.loadfile, &data[ndata].data, &data[ndata].size)) {
899             error("Failed to load the boot file\n");
900             goto bail;
901         }
902         data[ndata].base = load_base;
903         load_base = 0x7c00;     /* If we also load a boot sector */
904
905         /* Create boot info table: needed when you want to chainload
906            another version of ISOLINUX (or another bootlaoder that needs
907            the -boot-info-table switch of mkisofs)
908            (will only work when run from ISOLINUX) */
909         if (opt.isolinux) {
910             const union syslinux_derivative_info *sdi;
911             sdi = syslinux_derivative_info();
912
913             if (sdi->c.filesystem == SYSLINUX_FS_ISOLINUX) {
914                 /* Boot info table info (integers in little endian format)
915
916                    Offset Name         Size      Meaning
917                    8     bi_pvd       4 bytes   LBA of primary volume descriptor
918                    12     bi_file      4 bytes   LBA of boot file
919                    16     bi_length    4 bytes   Boot file length in bytes
920                    20     bi_csum      4 bytes   32-bit checksum
921                    24     bi_reserved  40 bytes  Reserved
922
923                    The 32-bit checksum is the sum of all the 32-bit words in the
924                    boot file starting at byte offset 64. All linear block
925                    addresses (LBAs) are given in CD sectors (normally 2048 bytes).
926
927                    LBA of primary volume descriptor should already be set to 16. 
928                  */
929
930                 isolinux_bin = (unsigned char *)data[ndata].data;
931
932                 /* Get LBA address of bootfile */
933                 file_lba = get_file_lba(opt.loadfile);
934
935                 if (file_lba == 0) {
936                     error("Failed to find LBA offset of the boot file\n");
937                     goto bail;
938                 }
939                 /* Set it */
940                 *((uint32_t *) & isolinux_bin[12]) = file_lba;
941
942                 /* Set boot file length */
943                 *((uint32_t *) & isolinux_bin[16]) = data[ndata].size;
944
945                 /* Calculate checksum */
946                 checksum = (uint32_t *) & isolinux_bin[20];
947                 chkhead = (uint32_t *) & isolinux_bin[64];
948                 chktail = (uint32_t *) & isolinux_bin[data[ndata].size & ~3];
949                 *checksum = 0;
950                 while (chkhead < chktail)
951                     *checksum += *chkhead++;
952
953                 /*
954                  * Deal with possible fractional dword at the end;
955                  * this *should* never happen...
956                  */
957                 if (data[ndata].size & 3) {
958                     uint32_t xword = 0;
959                     memcpy(&xword, chkhead, data[ndata].size & 3);
960                     *checksum += xword;
961                 }
962             } else {
963                 error
964                     ("The isolinux= option is only valid when run from ISOLINUX\n");
965                 goto bail;
966             }
967         }
968
969         if (opt.grub) {
970             regs.ip = 0x200;    /* jump 0x200 bytes into the loadfile */
971
972             /* 0xffffff00 seems to be GRUB ways to record that it's
973                "root" is the whole disk (and not a partition). */
974             *(uint32_t *) ((unsigned char *)data[ndata].data + 0x208) =
975                 0xffffff00ul;
976         }
977
978         ndata++;
979     }
980
981     if (!opt.loadfile || data[0].base >= 0x7c00 + SECTOR) {
982         /* Actually read the boot sector */
983         if (!partinfo) {
984             data[ndata].data = mbr;
985         } else if (!(data[ndata].data = read_sectors(partinfo->start_lba, 1))) {
986             error("Cannot read boot sector\n");
987             goto bail;
988         }
989         data[ndata].size = SECTOR;
990         data[ndata].base = load_base;
991
992         if (!opt.loadfile &&
993             *(uint16_t *) ((char *)data[ndata].data +
994                            data[ndata].size - 2) != 0xaa55) {
995             error
996                 ("Boot sector signature not found (unbootable disk/partition?)\n");
997             goto bail;
998         }
999
1000         /*
1001          * To boot the Recovery Console of Windows NT/2K/XP we need to write
1002          * the string "cmdcons\0" to memory location 0000:7C03.
1003          * Memory location 0000:7C00 contains the bootsector of the partition.
1004          */
1005         if (partinfo && opt.cmldr) {
1006             memcpy((char *)data[ndata].data + 3, cmldr_signature,
1007                    sizeof cmldr_signature);
1008         }
1009
1010         /*
1011          * Modify the hidden sectors (partition offset) copy in memory;
1012          * this modifies the field used by FAT and NTFS filesystems, and
1013          * possibly other boot loaders which use the same format.
1014          */
1015         if (partinfo && opt.sethidden) {
1016             *(uint32_t *) ((char *)data[ndata].data + 28) = partinfo->start_lba;
1017         }
1018
1019         ndata++;
1020     }
1021
1022     if (partinfo) {
1023         /* 0x7BE is the canonical place for the first partition entry. */
1024         data[ndata].data = partinfo;
1025         data[ndata].base = 0x7be;
1026         data[ndata].size = sizeof *partinfo;
1027         ndata++;
1028         regs.esi.w[0] = 0x7be;
1029     }
1030
1031     do_boot(data, ndata, &regs);
1032
1033 bail:
1034     return 255;
1035 }