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