1 /* ----------------------------------------------------------------------- *
3 * Copyright 1998-2004 H. Peter Anvin - All Rights Reserved
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.
11 * ----------------------------------------------------------------------- */
14 * syslinux.c - Linux installer program for SYSLINUX
28 const char *program
= "syslinux"; /* Name of program */
32 # define dprintf printf
34 # define dprintf(...) ((void)0)
37 void __attribute__((noreturn
)) usage(void)
39 puts("Usage: syslinux [-sfma] <drive>: [bootsecfile]\n");
43 void unlock_device(int);
45 void __attribute__((noreturn
)) die(const char *msg
)
55 * read/write wrapper functions
57 int creat(const char *filename
, int mode
)
62 dprintf("creat(\"%s\", 0x%x)\n", filename
, mode
);
65 asm volatile("int $0x21 ; setc %0"
66 : "=abcdm" (err
), "+a" (rv
)
67 : "c" (mode
), "d" (filename
));
69 dprintf("rv = %d\n", rv
);
70 die("cannot open ldlinux.sys");
80 dprintf("close(%d)\n", fd
);
82 asm volatile("int $0x21"
86 /* The only error MS-DOS returns for close is EBADF,
87 and we really don't care... */
90 ssize_t
write_file(int fd
, const void *buf
, size_t count
)
96 dprintf("write_file(%d,%p,%u)\n", fd
, buf
, count
);
100 asm volatile("int $0x21 ; setc %0"
101 : "=abcdm" (err
), "+a" (rv
)
102 : "b" (fd
), "c" (count
), "d" (buf
));
103 if ( err
|| rv
== 0 )
104 die("file write error");
113 static inline __attribute__((const)) uint16_t data_segment(void)
117 asm("movw %%ds,%0" : "=rm" (ds
));
122 uint32_t startsector
;
124 uint16_t bufoffs
, bufseg
;
125 } __attribute__((packed
));
127 void write_device(int drive
, const void *buf
, size_t nsecs
, unsigned int sector
)
132 dprintf("write_device(%d,%p,%u,%u)\n", drive
, buf
, nsecs
, sector
);
134 dio
.startsector
= sector
;
136 dio
.bufoffs
= (uintptr_t)buf
;
137 dio
.bufseg
= data_segment();
139 asm volatile("int $0x26 ; setc %0 ; popfw"
141 : "a" (drive
-1), "b" (&dio
), "c" (-1), "d" (buf
), "m" (dio
));
144 die("sector write error");
147 void read_device(int drive
, const void *buf
, size_t nsecs
, unsigned int sector
)
152 dprintf("read_device(%d,%p,%u,%u)\n", drive
, buf
, nsecs
, sector
);
154 dio
.startsector
= sector
;
156 dio
.bufoffs
= (uintptr_t)buf
;
157 dio
.bufseg
= data_segment();
159 asm volatile("int $0x25 ; setc %0 ; popfw"
161 : "a" (drive
-1), "b" (&dio
), "c" (-1), "d" (buf
), "m" (dio
));
164 die("sector read error");
167 /* Both traditional DOS and FAT32 DOS return this structure, but
168 FAT32 return a lot more data, so make sure we have plenty of space */
169 struct deviceparams
{
175 uint16_t bytespersec
;
179 uint16_t rootdirents
;
183 uint16_t secpertrack
;
186 uint32_t hugesectors
;
187 uint8_t lotsofpadding
[224];
188 } __attribute__((packed
));
190 uint32_t get_partition_offset(int drive
)
194 struct deviceparams dp
;
196 dp
.specfunc
= 1; /* Get current information */
199 asm volatile("int $0x21 ; setc %0"
200 : "=abcdm" (err
), "+a" (rv
), "=m" (dp
)
201 : "b" (drive
), "c" (0x0860), "d" (&dp
));
204 return dp
.hiddensecs
;
207 asm volatile("int $0x21 ; setc %0"
208 : "=abcdm" (err
), "+a" (rv
), "=m" (dp
)
209 : "b" (drive
), "c" (0x4860), "d" (&dp
));
212 return dp
.hiddensecs
;
214 die("could not find partition start offset");
221 uint16_t firstsector
;
223 uint16_t bufferoffset
;
225 } __attribute__((packed
));
227 static struct rwblock mbr
= {
231 .firstsector
= 0, /* MS-DOS, unlike the BIOS, zero-base sectors */
237 void write_mbr(int drive
, const void *buf
)
242 dprintf("write_mbr(%d,%p)\n", drive
, buf
);
244 mbr
.bufferoffset
= (uintptr_t)buf
;
245 mbr
.bufferseg
= data_segment();
248 asm volatile("int $0x21 ; setc %0"
249 : "=abcdm" (err
), "+a" (rv
)
250 : "c" (0x0841), "d" (&mbr
), "b" (drive
), "m" (mbr
));
256 asm volatile("int $0x21 ; setc %0"
257 : "=abcdm" (err
), "+a" (rv
)
258 : "c" (0x4841), "d" (&mbr
), "b" (drive
), "m" (mbr
));
261 die("mbr write error");
264 void read_mbr(int drive
, const void *buf
)
269 dprintf("read_mbr(%d,%p)\n", drive
, buf
);
271 mbr
.bufferoffset
= (uintptr_t)buf
;
272 mbr
.bufferseg
= data_segment();
275 asm volatile("int $0x21 ; setc %0"
276 : "=abcdm" (err
), "+a" (rv
)
277 : "c" (0x0861), "d" (&mbr
), "b" (drive
), "m" (mbr
));
283 asm volatile("int $0x21 ; setc %0"
284 : "=abcdm" (err
), "+a" (rv
)
285 : "c" (0x4861), "d" (&mbr
), "b" (drive
), "m" (mbr
));
288 die("mbr read error");
291 /* This call can legitimately fail, and we don't care, so ignore error return */
292 void set_attributes(const char *file
, int attributes
)
294 uint16_t rv
= 0x4301;
296 dprintf("set_attributes(\"%s\", 0x%02x)\n", file
, attributes
);
298 asm volatile("int $0x21"
300 : "c" (attributes
), "d" (file
));
304 * Version of the read_device function suitable for libfat
306 int libfat_xpread(intptr_t pp
, void *buf
, size_t secsize
, libfat_sector_t sector
)
308 read_device(pp
, buf
, 1, sector
);
312 static inline void get_dos_version(void)
314 uint16_t ver
= 0x3001;
315 asm("int $0x21 ; xchgb %%ah,%%al" : "+a" (ver
) : : "ebx", "ecx");
317 dprintf("DOS version %d.%d\n", (dos_version
>> 8), dos_version
& 0xff);
320 /* The locking interface relies on static variables. A massive hack :( */
321 static uint16_t lock_level
;
323 static inline void set_lock_device(uint8_t device
)
328 void lock_device(int level
)
334 if ( dos_version
< 0x0700 )
335 return; /* Win9x/NT only */
338 /* DOS 7.10 = Win95 OSR2 = first version with FAT32 */
339 lock_call
= (dos_version
>= 0x0710) ? 0x484A : 0x084A;
341 lock_call
= 0x084A; /* MSDN says this is OK for all filesystems */
344 while ( (lock_level
>> 8) < level
) {
345 uint16_t new_level
= lock_level
+ 0x0100;
346 dprintf("Trying lock %04x...\n", new_level
);
348 asm volatile("int $0x21 ; setc %0"
349 : "=abcdm" (err
), "+a" (rv
)
350 : "b" (new_level
), "c" (lock_call
), "d"(0x0001));
352 /* rv == 0x0001 means this call is not supported, if so we
353 assume locking isn't needed (e.g. Win9x in DOS-only mode) */
357 die("could not lock device");
360 lock_level
= new_level
;
365 void unlock_device(int level
)
369 uint16_t unlock_call
;
371 if ( dos_version
< 0x0700 )
372 return; /* Win9x/NT only */
375 /* DOS 7.10 = Win95 OSR2 = first version with FAT32 */
376 unlock_call
= (dos_version
>= 0x0710) ? 0x486A : 0x086A;
378 unlock_call
= 0x086A; /* MSDN says this is OK for all filesystems */
381 while ( (lock_level
>> 8) > level
) {
382 uint16_t new_level
= lock_level
- 0x0100;
384 asm volatile("int $0x21 ; setc %0"
385 : "=abcdm" (err
), "+a" (rv
)
386 : "b" (new_level
), "c" (unlock_call
));
387 lock_level
= new_level
;
393 * This function does any desired MBR manipulation; called with the device lock held.
396 uint8_t active
; /* Active flag */
397 uint8_t bhead
; /* Begin head */
398 uint8_t bsector
; /* Begin sector */
399 uint8_t bcylinder
; /* Begin cylinder */
400 uint8_t filesystem
; /* Filesystem value */
401 uint8_t ehead
; /* End head */
402 uint8_t esector
; /* End sector */
403 uint8_t ecylinder
; /* End cylinder */
404 uint32_t startlba
; /* Start sector LBA */
405 uint32_t sectors
; /* Length in sectors */
406 } __attribute__((packed
));
408 static void adjust_mbr(int device
, int writembr
, int set_active
)
410 static unsigned char sectbuf
[512];
413 if ( !writembr
&& !set_active
)
414 return; /* Nothing to do */
416 read_mbr(device
, sectbuf
);
419 memcpy(sectbuf
, syslinux_mbr
, syslinux_mbr_len
);
420 *(uint16_t *)(sectbuf
+510) = 0xaa55;
424 uint32_t offset
= get_partition_offset(device
);
425 struct mbr_entry
*me
= (struct mbr_entry
*)(sectbuf
+446);
428 for ( i
= 0 ; i
< 4 ; i
++ ) {
429 if ( me
->startlba
== offset
) {
439 die("partition not found");
440 } else if ( found
> 1 ) {
441 die("multiple aliased partitions found");
445 write_mbr(device
, sectbuf
);
448 int main(int argc
, char *argv
[])
450 static unsigned char sectbuf
[512];
452 static char ldlinux_name
[] = "@:\\LDLINUX.SYS";
454 int force
= 0; /* -f (force) option */
455 struct libfat_filesystem
*fs
;
456 libfat_sector_t s
, *secp
, sectors
[65]; /* 65 is maximum possible */
457 int32_t ldlinux_cluster
;
459 const char *device
= NULL
, *bootsecfile
= NULL
;
462 int writembr
= 0; /* -m (write MBR) option */
463 int set_active
= 0; /* -a (set partition active) option */
465 dprintf("argv = %p\n", argv
);
466 for ( i
= 0 ; i
<= argc
; i
++ )
467 dprintf("argv[%d] = %p = \"%s\"\n", i
, argv
[i
], argv
[i
]);
469 (void)argc
; /* Unused */
473 for ( argp
= argv
+1 ; *argp
; argp
++ ) {
474 if ( **argp
== '-' ) {
481 case 's': /* Use "safe, slow and stupid" code */
482 syslinux_make_stupid();
484 case 'f': /* Force install */
487 case 'm': /* Write MBR */
490 case 'a': /* Set partition active */
512 * Figure out which drive we're talking to
514 dev_fd
= (device
[0] & ~0x20) - 0x40;
515 if ( dev_fd
< 1 || dev_fd
> 26 || device
[1] != ':' || device
[2] )
518 set_lock_device(dev_fd
);
520 lock_device(2); /* Make sure we can lock the device */
521 read_device(dev_fd
, sectbuf
, 1, 0);
525 * Check to see that what we got was indeed an MS-DOS boot sector/superblock
527 if( (errmsg
= syslinux_check_bootsect(sectbuf
)) ) {
534 ldlinux_name
[0] = dev_fd
| 0x40;
536 set_attributes(ldlinux_name
, 0);
537 fd
= creat(ldlinux_name
, 0x07); /* SYSTEM HIDDEN READONLY */
538 write_file(fd
, syslinux_ldlinux
, syslinux_ldlinux_len
);
542 * Now, use libfat to create a block map. This probably
543 * should be changed to use ioctl(...,FIBMAP,...) since
544 * this is supposed to be a simple, privileged version
548 fs
= libfat_open(libfat_xpread
, dev_fd
);
549 ldlinux_cluster
= libfat_searchdir(fs
, 0, "LDLINUX SYS", NULL
);
552 s
= libfat_clustertosector(fs
, ldlinux_cluster
);
553 while ( s
&& nsectors
< 65 ) {
556 s
= libfat_nextsector(fs
, s
);
561 * Patch ldlinux.sys and the boot sector
563 syslinux_patch(sectors
, nsectors
);
566 * Write the now-patched first sector of ldlinux.sys
569 write_device(dev_fd
, syslinux_ldlinux
, 1, sectors
[0]);
572 * Muck with the MBR, if desired, while we hold the lock
574 adjust_mbr(dev_fd
, writembr
, set_active
);
577 * To finish up, write the boot sector
580 /* Read the superblock again since it might have changed while mounted */
581 read_device(dev_fd
, sectbuf
, 1, 0);
583 /* Copy the syslinux code into the boot sector */
584 syslinux_make_bootsect(sectbuf
);
586 /* Write new boot sector */
589 fd
= creat(bootsecfile
, 0x20); /* ARCHIVE */
590 write_file(fd
, sectbuf
, 512);
593 write_device(dev_fd
, sectbuf
, 1, 0);