Run Nindent on dos/syslinux.c
[people/sha0/syslinux.git] / dos / syslinux.c
1 /* ----------------------------------------------------------------------- *
2  *
3  *   Copyright 1998-2008 H. Peter Anvin - All Rights Reserved
4  *
5  *   This program is free software; you can redistribute it and/or modify
6  *   it under the terms of the GNU General Public License as published by
7  *   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
8  *   Boston MA 02111-1307, USA; either version 2 of the License, or
9  *   (at your option) any later version; incorporated herein by reference.
10  *
11  * ----------------------------------------------------------------------- */
12
13 /*
14  * syslinux.c - Linux installer program for SYSLINUX
15  *
16  * Hacked up for DOS.
17  */
18
19 #include <errno.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <stdlib.h>
23 #include "mystuff.h"
24
25 #include "syslinux.h"
26 #include "libfat.h"
27
28 const char *program = "syslinux";       /* Name of program */
29 uint16_t dos_version;
30
31 #ifdef DEBUG
32 # define dprintf printf
33 #else
34 # define dprintf(...) ((void)0)
35 #endif
36
37 void __attribute__ ((noreturn)) usage(void)
38 {
39     puts("Usage: syslinux [-sfmar][-d directory] <drive>: [bootsecfile]\n");
40     exit(1);
41 }
42
43 void unlock_device(int);
44
45 void __attribute__ ((noreturn)) die(const char *msg)
46 {
47     unlock_device(0);
48     puts("syslinux: ");
49     puts(msg);
50     putchar('\n');
51     exit(1);
52 }
53
54 void warning(const char *msg)
55 {
56     puts("syslinux: warning: ");
57     puts(msg);
58     putchar('\n');
59 }
60
61 /*
62  * read/write wrapper functions
63  */
64 int creat(const char *filename, int mode)
65 {
66     uint16_t rv;
67     uint8_t err;
68
69     dprintf("creat(\"%s\", 0x%x)\n", filename, mode);
70
71     rv = 0x3C00;
72     asm volatile ("int $0x21 ; setc %0":"=bcdm" (err), "+a"(rv)
73                   :"c"(mode), "d"(filename));
74     if (err) {
75         dprintf("rv = %d\n", rv);
76         die("cannot open ldlinux.sys");
77     }
78
79     return rv;
80 }
81
82 void close(int fd)
83 {
84     uint16_t rv = 0x3E00;
85
86     dprintf("close(%d)\n", fd);
87
88     asm volatile ("int $0x21":"+a" (rv)
89                   :"b"(fd));
90
91     /* The only error MS-DOS returns for close is EBADF,
92        and we really don't care... */
93 }
94
95 int rename(const char *oldname, const char *newname)
96 {
97     uint16_t rv = 0x5600;       /* Also support 43FFh? */
98     uint8_t err;
99
100     dprintf("rename(\"%s\", \"%s\")\n", oldname, newname);
101
102     asm volatile ("int $0x21 ; setc %0":"=bcdm" (err), "+a"(rv)
103                   :"d"(oldname), "D"(newname));
104
105     if (err) {
106         dprintf("rv = %d\n", rv);
107         warning("cannot move ldlinux.sys");
108         return rv;
109     }
110
111     return 0;
112 }
113
114 ssize_t write_file(int fd, const void *buf, size_t count)
115 {
116     uint16_t rv;
117     ssize_t done = 0;
118     uint8_t err;
119
120     dprintf("write_file(%d,%p,%u)\n", fd, buf, count);
121
122     while (count) {
123         rv = 0x4000;
124         asm volatile ("int $0x21 ; setc %0":"=abcdm" (err), "+a"(rv)
125                       :"b"(fd), "c"(count), "d"(buf));
126         if (err || rv == 0)
127             die("file write error");
128
129         done += rv;
130         count -= rv;
131     }
132
133     return done;
134 }
135
136 static inline __attribute__ ((const))
137 uint16_t data_segment(void)
138 {
139     uint16_t ds;
140
141 asm("movw %%ds,%0":"=rm"(ds));
142     return ds;
143 }
144
145 struct diskio {
146     uint32_t startsector;
147     uint16_t sectors;
148     uint16_t bufoffs, bufseg;
149 } __attribute__ ((packed));
150
151 void write_device(int drive, const void *buf, size_t nsecs, unsigned int sector)
152 {
153     uint8_t err;
154     struct diskio dio;
155
156     dprintf("write_device(%d,%p,%u,%u)\n", drive, buf, nsecs, sector);
157
158     dio.startsector = sector;
159     dio.sectors = nsecs;
160     dio.bufoffs = (uintptr_t) buf;
161     dio.bufseg = data_segment();
162
163     asm volatile ("int $0x26 ; setc %0 ; popfw":"=abcdm" (err)
164                   :"a"(drive - 1), "b"(&dio), "c"(-1), "d"(buf), "m"(dio));
165
166     if (err)
167         die("sector write error");
168 }
169
170 void read_device(int drive, const void *buf, size_t nsecs, unsigned int sector)
171 {
172     uint8_t err;
173     struct diskio dio;
174
175     dprintf("read_device(%d,%p,%u,%u)\n", drive, buf, nsecs, sector);
176
177     dio.startsector = sector;
178     dio.sectors = nsecs;
179     dio.bufoffs = (uintptr_t) buf;
180     dio.bufseg = data_segment();
181
182     asm volatile ("int $0x25 ; setc %0 ; popfw":"=abcdm" (err)
183                   :"a"(drive - 1), "b"(&dio), "c"(-1), "d"(buf), "m"(dio));
184
185     if (err)
186         die("sector read error");
187 }
188
189 /* Both traditional DOS and FAT32 DOS return this structure, but
190    FAT32 return a lot more data, so make sure we have plenty of space */
191 struct deviceparams {
192     uint8_t specfunc;
193     uint8_t devtype;
194     uint16_t devattr;
195     uint16_t cylinders;
196     uint8_t mediatype;
197     uint16_t bytespersec;
198     uint8_t secperclust;
199     uint16_t ressectors;
200     uint8_t fats;
201     uint16_t rootdirents;
202     uint16_t sectors;
203     uint8_t media;
204     uint16_t fatsecs;
205     uint16_t secpertrack;
206     uint16_t heads;
207     uint32_t hiddensecs;
208     uint32_t hugesectors;
209     uint8_t lotsofpadding[224];
210 } __attribute__ ((packed));
211
212 uint32_t get_partition_offset(int drive)
213 {
214     uint8_t err;
215     uint16_t rv;
216     struct deviceparams dp;
217
218     dp.specfunc = 1;            /* Get current information */
219
220     rv = 0x440d;
221     asm volatile ("int $0x21 ; setc %0":"=abcdm" (err), "+a"(rv), "=m"(dp)
222                   :"b"(drive), "c"(0x0860), "d"(&dp));
223
224     if (!err)
225         return dp.hiddensecs;
226
227     rv = 0x440d;
228     asm volatile ("int $0x21 ; setc %0":"=abcdm" (err), "+a"(rv), "=m"(dp)
229                   :"b"(drive), "c"(0x4860), "d"(&dp));
230
231     if (!err)
232         return dp.hiddensecs;
233
234     die("could not find partition start offset");
235 }
236
237 struct rwblock {
238     uint8_t special;
239     uint16_t head;
240     uint16_t cylinder;
241     uint16_t firstsector;
242     uint16_t sectors;
243     uint16_t bufferoffset;
244     uint16_t bufferseg;
245 } __attribute__ ((packed));
246
247 static struct rwblock mbr = {
248     .special = 0,
249     .head = 0,
250     .cylinder = 0,
251     .firstsector = 0,           /* MS-DOS, unlike the BIOS, zero-base sectors */
252     .sectors = 1,
253     .bufferoffset = 0,
254     .bufferseg = 0
255 };
256
257 void write_mbr(int drive, const void *buf)
258 {
259     uint16_t rv;
260     uint8_t err;
261
262     dprintf("write_mbr(%d,%p)\n", drive, buf);
263
264     mbr.bufferoffset = (uintptr_t) buf;
265     mbr.bufferseg = data_segment();
266
267     rv = 0x440d;
268     asm volatile ("int $0x21 ; setc %0":"=abcdm" (err), "+a"(rv)
269                   :"c"(0x0841), "d"(&mbr), "b"(drive), "m"(mbr));
270
271     if (!err)
272         return;
273
274     rv = 0x440d;
275     asm volatile ("int $0x21 ; setc %0":"=abcdm" (err), "+a"(rv)
276                   :"c"(0x4841), "d"(&mbr), "b"(drive), "m"(mbr));
277
278     if (err)
279         die("mbr write error");
280 }
281
282 void read_mbr(int drive, const void *buf)
283 {
284     uint16_t rv;
285     uint8_t err;
286
287     dprintf("read_mbr(%d,%p)\n", drive, buf);
288
289     mbr.bufferoffset = (uintptr_t) buf;
290     mbr.bufferseg = data_segment();
291
292     rv = 0x440d;
293     asm volatile ("int $0x21 ; setc %0":"=abcdm" (err), "+a"(rv)
294                   :"c"(0x0861), "d"(&mbr), "b"(drive), "m"(mbr));
295
296     if (!err)
297         return;
298
299     rv = 0x440d;
300     asm volatile ("int $0x21 ; setc %0":"=abcdm" (err), "+a"(rv)
301                   :"c"(0x4861), "d"(&mbr), "b"(drive), "m"(mbr));
302
303     if (err)
304         die("mbr read error");
305 }
306
307 /* This call can legitimately fail, and we don't care, so ignore error return */
308 void set_attributes(const char *file, int attributes)
309 {
310     uint16_t rv = 0x4301;
311
312     dprintf("set_attributes(\"%s\", 0x%02x)\n", file, attributes);
313
314     asm volatile ("int $0x21":"+a" (rv)
315                   :"c"(attributes), "d"(file));
316 }
317
318 /*
319  * Version of the read_device function suitable for libfat
320  */
321 int libfat_xpread(intptr_t pp, void *buf, size_t secsize,
322                   libfat_sector_t sector)
323 {
324     read_device(pp, buf, 1, sector);
325     return secsize;
326 }
327
328 static inline void get_dos_version(void)
329 {
330     uint16_t ver = 0x3001;
331 asm("int $0x21 ; xchgb %%ah,%%al": "+a"(ver): :"ebx", "ecx");
332     dos_version = ver;
333     dprintf("DOS version %d.%d\n", (dos_version >> 8), dos_version & 0xff);
334 }
335
336 /* The locking interface relies on static variables.  A massive hack :( */
337 static uint16_t lock_level;
338
339 static inline void set_lock_device(uint8_t device)
340 {
341     lock_level = device;
342 }
343
344 void lock_device(int level)
345 {
346     uint16_t rv;
347     uint8_t err;
348     uint16_t lock_call;
349
350     if (dos_version < 0x0700)
351         return;                 /* Win9x/NT only */
352
353 #if 0
354     /* DOS 7.10 = Win95 OSR2 = first version with FAT32 */
355     lock_call = (dos_version >= 0x0710) ? 0x484A : 0x084A;
356 #else
357     lock_call = 0x084A;         /* MSDN says this is OK for all filesystems */
358 #endif
359
360     while ((lock_level >> 8) < level) {
361         uint16_t new_level = lock_level + 0x0100;
362         dprintf("Trying lock %04x...\n", new_level);
363         rv = 0x444d;
364         asm volatile ("int $0x21 ; setc %0":"=abcdm" (err), "+a"(rv)
365                       :"b"(new_level), "c"(lock_call), "d"(0x0001));
366         if (err) {
367             /* rv == 0x0001 means this call is not supported, if so we
368                assume locking isn't needed (e.g. Win9x in DOS-only mode) */
369             if (rv == 0x0001)
370                 return;
371             else
372                 die("could not lock device");
373         }
374
375         lock_level = new_level;
376     }
377     return;
378 }
379
380 void unlock_device(int level)
381 {
382     uint16_t rv;
383     uint8_t err;
384     uint16_t unlock_call;
385
386     if (dos_version < 0x0700)
387         return;                 /* Win9x/NT only */
388
389 #if 0
390     /* DOS 7.10 = Win95 OSR2 = first version with FAT32 */
391     unlock_call = (dos_version >= 0x0710) ? 0x486A : 0x086A;
392 #else
393     unlock_call = 0x086A;       /* MSDN says this is OK for all filesystems */
394 #endif
395
396     while ((lock_level >> 8) > level) {
397         uint16_t new_level = lock_level - 0x0100;
398         rv = 0x440d;
399         asm volatile ("int $0x21 ; setc %0":"=abcdm" (err), "+a"(rv)
400                       :"b"(new_level), "c"(unlock_call));
401         lock_level = new_level;
402     }
403 }
404
405 /*
406  * This function does any desired MBR manipulation; called with the device lock held.
407  */
408 struct mbr_entry {
409     uint8_t active;             /* Active flag */
410     uint8_t bhead;              /* Begin head */
411     uint8_t bsector;            /* Begin sector */
412     uint8_t bcylinder;          /* Begin cylinder */
413     uint8_t filesystem;         /* Filesystem value */
414     uint8_t ehead;              /* End head */
415     uint8_t esector;            /* End sector */
416     uint8_t ecylinder;          /* End cylinder */
417     uint32_t startlba;          /* Start sector LBA */
418     uint32_t sectors;           /* Length in sectors */
419 } __attribute__ ((packed));
420
421 static void adjust_mbr(int device, int writembr, int set_active)
422 {
423     static unsigned char sectbuf[512];
424     int i;
425
426     if (!writembr && !set_active)
427         return;                 /* Nothing to do */
428
429     read_mbr(device, sectbuf);
430
431     if (writembr) {
432         memcpy(sectbuf, syslinux_mbr, syslinux_mbr_len);
433         *(uint16_t *) (sectbuf + 510) = 0xaa55;
434     }
435
436     if (set_active) {
437         uint32_t offset = get_partition_offset(device);
438         struct mbr_entry *me = (struct mbr_entry *)(sectbuf + 446);
439         int found = 0;
440
441         for (i = 0; i < 4; i++) {
442             if (me->startlba == offset) {
443                 me->active = 0x80;
444                 found++;
445             } else {
446                 me->active = 0;
447             }
448             me++;
449         }
450
451         if (found < 1) {
452             die("partition not found");
453         } else if (found > 1) {
454             die("multiple aliased partitions found");
455         }
456     }
457
458     write_mbr(device, sectbuf);
459 }
460
461 int main(int argc, char *argv[])
462 {
463     static unsigned char sectbuf[512];
464     int dev_fd, fd;
465     static char ldlinux_name[] = "@:\\ldlinux.sys";
466     char **argp, *opt;
467     int force = 0;              /* -f (force) option */
468     struct libfat_filesystem *fs;
469     libfat_sector_t s, *secp, sectors[65];      /* 65 is maximum possible */
470     int32_t ldlinux_cluster;
471     int nsectors;
472     const char *device = NULL, *bootsecfile = NULL;
473     const char *errmsg;
474     int i;
475     int writembr = 0;           /* -m (write MBR) option */
476     int set_active = 0;         /* -a (set partition active) option */
477     const char *subdir = NULL;
478     int stupid = 0;
479     int raid_mode = 0;
480
481     dprintf("argv = %p\n", argv);
482     for (i = 0; i <= argc; i++)
483         dprintf("argv[%d] = %p = \"%s\"\n", i, argv[i], argv[i]);
484
485     (void)argc;                 /* Unused */
486
487     get_dos_version();
488
489     for (argp = argv + 1; *argp; argp++) {
490         if (**argp == '-') {
491             opt = *argp + 1;
492             if (!*opt)
493                 usage();
494
495             while (*opt) {
496                 switch (*opt) {
497                 case 's':       /* Use "safe, slow and stupid" code */
498                     stupid = 1;
499                     break;
500                 case 'r':       /* RAID mode */
501                     raid_mode = 1;
502                     break;
503                 case 'f':       /* Force install */
504                     force = 1;
505                     break;
506                 case 'm':       /* Write MBR */
507                     writembr = 1;
508                     break;
509                 case 'a':       /* Set partition active */
510                     set_active = 1;
511                     break;
512                 case 'd':
513                     if (argp[1])
514                         subdir = *++argp;
515                     break;
516                 default:
517                     usage();
518                 }
519                 opt++;
520             }
521         } else {
522             if (bootsecfile)
523                 usage();
524             else if (device)
525                 bootsecfile = *argp;
526             else
527                 device = *argp;
528         }
529     }
530
531     if (!device)
532         usage();
533
534     /*
535      * Figure out which drive we're talking to
536      */
537     dev_fd = (device[0] & ~0x20) - 0x40;
538     if (dev_fd < 1 || dev_fd > 26 || device[1] != ':' || device[2])
539         usage();
540
541     set_lock_device(dev_fd);
542
543     lock_device(2);             /* Make sure we can lock the device */
544     read_device(dev_fd, sectbuf, 1, 0);
545     unlock_device(1);
546
547     /*
548      * Check to see that what we got was indeed an MS-DOS boot sector/superblock
549      */
550     if ((errmsg = syslinux_check_bootsect(sectbuf))) {
551         unlock_device(0);
552         puts(errmsg);
553         putchar('\n');
554         exit(1);
555     }
556
557     ldlinux_name[0] = dev_fd | 0x40;
558
559     set_attributes(ldlinux_name, 0);
560     fd = creat(ldlinux_name, 0x07);     /* SYSTEM HIDDEN READONLY */
561     write_file(fd, syslinux_ldlinux, syslinux_ldlinux_len);
562     close(fd);
563
564     /*
565      * Now, use libfat to create a block map.  This probably
566      * should be changed to use ioctl(...,FIBMAP,...) since
567      * this is supposed to be a simple, privileged version
568      * of the installer.
569      */
570     lock_device(2);
571     fs = libfat_open(libfat_xpread, dev_fd);
572     ldlinux_cluster = libfat_searchdir(fs, 0, "LDLINUX SYS", NULL);
573     secp = sectors;
574     nsectors = 0;
575     s = libfat_clustertosector(fs, ldlinux_cluster);
576     while (s && nsectors < 65) {
577         *secp++ = s;
578         nsectors++;
579         s = libfat_nextsector(fs, s);
580     }
581     libfat_close(fs);
582
583     /*
584      * If requested, move ldlinux.sys
585      */
586     if (subdir) {
587         char new_ldlinux_name[160];
588         char *cp = new_ldlinux_name + 3;
589         const char *sd;
590         int slash = 1;
591
592         new_ldlinux_name[0] = dev_fd | 0x40;
593         new_ldlinux_name[1] = ':';
594         new_ldlinux_name[2] = '\\';
595
596         for (sd = subdir; *sd; sd++) {
597             char c = *sd;
598
599             if (c == '/' || c == '\\') {
600                 if (slash)
601                     continue;
602                 c = '\\';
603                 slash = 1;
604             } else {
605                 slash = 0;
606             }
607
608             *cp++ = c;
609         }
610
611         /* Skip if subdirectory == root */
612         if (cp > new_ldlinux_name + 3) {
613             if (!slash)
614                 *cp++ = '\\';
615
616             memcpy(cp, "ldlinux.sys", 12);
617
618             set_attributes(ldlinux_name, 0);
619             if (rename(ldlinux_name, new_ldlinux_name))
620                 set_attributes(ldlinux_name, 0x07);
621             else
622                 set_attributes(new_ldlinux_name, 0x07);
623         }
624     }
625
626     /*
627      * Patch ldlinux.sys and the boot sector
628      */
629     syslinux_patch(sectors, nsectors, stupid, raid_mode);
630
631     /*
632      * Write the now-patched first sector of ldlinux.sys
633      */
634     lock_device(3);
635     write_device(dev_fd, syslinux_ldlinux, 1, sectors[0]);
636
637     /*
638      * Muck with the MBR, if desired, while we hold the lock
639      */
640     adjust_mbr(dev_fd, writembr, set_active);
641
642     /*
643      * To finish up, write the boot sector
644      */
645
646     /* Read the superblock again since it might have changed while mounted */
647     read_device(dev_fd, sectbuf, 1, 0);
648
649     /* Copy the syslinux code into the boot sector */
650     syslinux_make_bootsect(sectbuf);
651
652     /* Write new boot sector */
653     if (bootsecfile) {
654         unlock_device(0);
655         fd = creat(bootsecfile, 0x20);  /* ARCHIVE */
656         write_file(fd, sectbuf, 512);
657         close(fd);
658     } else {
659         write_device(dev_fd, sectbuf, 1, 0);
660         unlock_device(0);
661     }
662
663     /* Done! */
664
665     return 0;
666 }