Major Makefile cleanups; gcc 4.3.0 compatiblity
[profile/ivi/syslinux.git] / extlinux / main.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  * extlinux.c
15  *
16  * Install the extlinux boot block on an ext2/3 filesystem
17  */
18
19 #define  _GNU_SOURCE            /* Enable everything */
20 #include <inttypes.h>
21 /* This is needed to deal with the kernel headers imported into glibc 3.3.3. */
22 typedef uint64_t u64;
23 #include <alloca.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <stdio.h>
27 #include <unistd.h>
28 #ifndef __KLIBC__
29 #include <mntent.h>
30 #endif
31 #include <stdlib.h>
32 #include <string.h>
33 #include <getopt.h>
34 #include <sysexits.h>
35 #include <sys/ioctl.h>
36 #include <sys/stat.h>
37 #include <sys/types.h>
38 #include <sys/mount.h>
39 #include <sys/vfs.h>
40
41 #include <linux/fd.h>           /* Floppy geometry */
42 #include <linux/hdreg.h>        /* Hard disk geometry */
43 #define statfs _kernel_statfs   /* HACK to deal with broken 2.4 distros */
44 #include <linux/fs.h>           /* FIGETBSZ, FIBMAP */
45 #undef statfs
46
47 #include "ext2_fs.h"
48 #include "../version.h"
49 #include "syslxint.h"
50
51 #ifdef DEBUG
52 # define dprintf printf
53 #else
54 # define dprintf(...) ((void)0)
55 #endif
56
57 /* Global option handling */
58
59 const char *program;
60
61 /* These are the options we can set and their values */
62 struct my_options {
63   unsigned int sectors;
64   unsigned int heads;
65   int raid_mode;
66   int reset_adv;
67   const char *set_once;
68 } opt = {
69   .sectors = 0,
70   .heads = 0,
71   .raid_mode = 0,
72   .reset_adv = 0,
73   .set_once = NULL,
74 };
75
76 static void __attribute__((noreturn)) usage(int rv)
77 {
78   fprintf(stderr,
79           "Usage: %s [options] directory\n"
80           "  --install    -i  Install over the current bootsector\n"
81           "  --update     -U  Update a previous EXTLINUX installation\n"
82           "  --zip        -z  Force zipdrive geometry (-H 64 -S 32)\n"
83           "  --sectors=#  -S  Force the number of sectors per track\n"
84           "  --heads=#    -H  Force number of heads\n"
85           "  --raid       -r  Fall back to the next device on boot failure\n"
86           "  --once=...   -o  Execute a command once upon boot\n"
87           "  --clear-once -O  Clear the boot-once command\n"
88           "  --reset-adv      Reset auxilliary data\n"
89           "\n"
90           "  Note: geometry is determined at boot time for devices which\n"
91           "  are considered hard disks by the BIOS.  Unfortunately, this is\n"
92           "  not possible for devices which are considered floppy disks,\n"
93           "  which includes zipdisks and LS-120 superfloppies.\n"
94           "\n"
95           "  The -z option is useful for USB devices which are considered\n"
96           "  hard disks by some BIOSes and zipdrives by other BIOSes.\n",
97           program);
98
99   exit(rv);
100 }
101
102 enum long_only_opt {
103   OPT_NONE,
104   OPT_RESET_ADV,
105 };
106
107 static const struct option long_options[] = {
108   { "install",    0, NULL, 'i' },
109   { "update",     0, NULL, 'U' },
110   { "zipdrive",   0, NULL, 'z' },
111   { "sectors",    1, NULL, 'S' },
112   { "heads",      1, NULL, 'H' },
113   { "raid-mode",  0, NULL, 'r' },
114   { "version",    0, NULL, 'v' },
115   { "help",       0, NULL, 'h' },
116   { "once",       1, NULL, 'o' },
117   { "clear-once", 0, NULL, 'O' },
118   { "reset-adv",  0, NULL, OPT_RESET_ADV },
119   { 0, 0, 0, 0 }
120 };
121
122 static const char short_options[] = "iUuzS:H:rvho:O";
123
124 #if defined(__linux__) && !defined(BLKGETSIZE64)
125 /* This takes a u64, but the size field says size_t.  Someone screwed big. */
126 # define BLKGETSIZE64 _IOR(0x12,114,size_t)
127 #endif
128
129 #define LDLINUX_MAGIC   0x3eb202fe
130
131 enum bs_offsets {
132   bsJump            = 0x00,
133   bsOemName         = 0x03,
134   bsBytesPerSec     = 0x0b,
135   bsSecPerClust     = 0x0d,
136   bsResSectors      = 0x0e,
137   bsFATs            = 0x10,
138   bsRootDirEnts     = 0x11,
139   bsSectors         = 0x13,
140   bsMedia           = 0x15,
141   bsFATsecs         = 0x16,
142   bsSecPerTrack     = 0x18,
143   bsHeads           = 0x1a,
144   bsHiddenSecs      = 0x1c,
145   bsHugeSectors     = 0x20,
146
147   /* FAT12/16 only */
148   bs16DriveNumber   = 0x24,
149   bs16Reserved1     = 0x25,
150   bs16BootSignature = 0x26,
151   bs16VolumeID      = 0x27,
152   bs16VolumeLabel   = 0x2b,
153   bs16FileSysType   = 0x36,
154   bs16Code          = 0x3e,
155
156   /* FAT32 only */
157   bs32FATSz32       = 36,
158   bs32ExtFlags      = 40,
159   bs32FSVer         = 42,
160   bs32RootClus      = 44,
161   bs32FSInfo        = 48,
162   bs32BkBootSec     = 50,
163   bs32Reserved      = 52,
164   bs32DriveNumber   = 64,
165   bs32Reserved1     = 65,
166   bs32BootSignature = 66,
167   bs32VolumeID      = 67,
168   bs32VolumeLabel   = 71,
169   bs32FileSysType   = 82,
170   bs32Code          = 90,
171
172   bsSignature     = 0x1fe
173 };
174
175 #define bsHead      bsJump
176 #define bsHeadLen   (bsOemName-bsHead)
177 #define bsCode      bs32Code    /* The common safe choice */
178 #define bsCodeLen   (bsSignature-bs32Code)
179
180 #ifndef EXT2_SUPER_OFFSET
181 #define EXT2_SUPER_OFFSET 1024
182 #endif
183
184 const char *program;
185
186 /*
187  * Boot block
188  */
189 extern unsigned char extlinux_bootsect[];
190 extern unsigned int  extlinux_bootsect_len;
191 #define boot_block      extlinux_bootsect
192 #define boot_block_len  extlinux_bootsect_len
193
194 /*
195  * Image file
196  */
197 extern unsigned char extlinux_image[];
198 extern unsigned int  extlinux_image_len;
199 #define boot_image      extlinux_image
200 #define boot_image_len  extlinux_image_len
201
202 /*
203  * Common abort function
204  */
205 void __attribute__((noreturn)) die(const char *msg)
206 {
207   fputs(msg, stderr);
208   exit(1);
209 }
210
211 /*
212  * read/write wrapper functions
213  */
214 ssize_t xpread(int fd, void *buf, size_t count, off_t offset)
215 {
216   char *bufp = (char *)buf;
217   ssize_t rv;
218   ssize_t done = 0;
219
220   while ( count ) {
221     rv = pread(fd, bufp, count, offset);
222     if ( rv == 0 ) {
223       die("short read");
224     } else if ( rv == -1 ) {
225       if ( errno == EINTR ) {
226         continue;
227       } else {
228         die(strerror(errno));
229       }
230     } else {
231       bufp += rv;
232       offset += rv;
233       done += rv;
234       count -= rv;
235     }
236   }
237
238   return done;
239 }
240
241 ssize_t xpwrite(int fd, const void *buf, size_t count, off_t offset)
242 {
243   const char *bufp = (const char *)buf;
244   ssize_t rv;
245   ssize_t done = 0;
246
247   while ( count ) {
248     rv = pwrite(fd, bufp, count, offset);
249     if ( rv == 0 ) {
250       die("short write");
251     } else if ( rv == -1 ) {
252       if ( errno == EINTR ) {
253         continue;
254       } else {
255         die(strerror(errno));
256       }
257     } else {
258       bufp += rv;
259       offset += rv;
260       done += rv;
261       count -= rv;
262     }
263   }
264
265   return done;
266 }
267
268 /*
269  * Produce file map
270  */
271 int
272 sectmap(int fd, uint32_t *sectors, int nsectors)
273 {
274   unsigned int blksize, blk, nblk;
275   unsigned int i;
276
277   /* Get block size */
278   if ( ioctl(fd, FIGETBSZ, &blksize) )
279     return -1;
280
281   /* Number of sectors per block */
282   blksize >>= SECTOR_SHIFT;
283
284   nblk = 0;
285   while ( nsectors ) {
286
287     blk = nblk++;
288     dprintf("querying block %u\n", blk);
289     if ( ioctl(fd, FIBMAP, &blk) )
290       return -1;
291
292     blk *= blksize;
293     for ( i = 0 ; i < blksize ; i++ ) {
294       if ( !nsectors )
295         return 0;
296
297       dprintf("Sector: %10u\n", blk);
298       *sectors++ = blk++;
299       nsectors--;
300     }
301   }
302
303   return 0;
304 }
305
306 /*
307  * Get the size of a block device
308  */
309 uint64_t get_size(int devfd)
310 {
311   uint64_t bytes;
312   uint32_t sects;
313   struct stat st;
314
315 #ifdef BLKGETSIZE64
316   if ( !ioctl(devfd, BLKGETSIZE64, &bytes) )
317     return bytes;
318 #endif
319   if ( !ioctl(devfd, BLKGETSIZE, &sects) )
320     return (uint64_t)sects << 9;
321   else if ( !fstat(devfd, &st) && st.st_size )
322     return st.st_size;
323   else
324     return 0;
325 }
326
327
328 /*
329  * Get device geometry and partition offset
330  */
331 struct geometry_table {
332   uint64_t bytes;
333   struct hd_geometry g;
334 };
335
336 /* Standard floppy disk geometries, plus LS-120.  Zipdisk geometry
337    (x/64/32) is the final fallback.  I don't know what LS-240 has
338    as its geometry, since I don't have one and don't know anyone that does,
339    and Google wasn't helpful... */
340 static const struct geometry_table standard_geometries[] = {
341   {    360*1024, {  2,  9,  40, 0 } },
342   {    720*1024, {  2,  9,  80, 0 } },
343   {   1200*1024, {  2, 15,  80, 0 } },
344   {   1440*1024, {  2, 18,  80, 0 } },
345   {   1680*1024, {  2, 21,  80, 0 } },
346   {   1722*1024, {  2, 21,  80, 0 } },
347   {   2880*1024, {  2, 36,  80, 0 } },
348   {   3840*1024, {  2, 48,  80, 0 } },
349   { 123264*1024, {  8, 32, 963, 0 } }, /* LS120 */
350   { 0, {0,0,0,0} }
351 };
352
353 int
354 get_geometry(int devfd, uint64_t totalbytes, struct hd_geometry *geo)
355 {
356   struct floppy_struct fd_str;
357   const struct geometry_table *gp;
358
359   memset(geo, 0, sizeof *geo);
360
361   if ( !ioctl(devfd, HDIO_GETGEO, &geo) ) {
362     return 0;
363   } else if ( !ioctl(devfd, FDGETPRM, &fd_str) ) {
364     geo->heads     = fd_str.head;
365     geo->sectors   = fd_str.sect;
366     geo->cylinders = fd_str.track;
367     geo->start     = 0;
368     return 0;
369   }
370
371   /* Didn't work.  Let's see if this is one of the standard geometries */
372   for ( gp = standard_geometries ; gp->bytes ; gp++ ) {
373     if ( gp->bytes == totalbytes ) {
374       memcpy(geo, &gp->g, sizeof *geo);
375       return 0;
376     }
377   }
378
379   /* Didn't work either... assign a geometry of 64 heads, 32 sectors; this is
380      what zipdisks use, so this would help if someone has a USB key that
381      they're booting in USB-ZIP mode. */
382
383   geo->heads     = opt.heads ?: 64;
384   geo->sectors   = opt.sectors ?: 32;
385   geo->cylinders = totalbytes/(geo->heads*geo->sectors << SECTOR_SHIFT);
386   geo->start     = 0;
387
388   if ( !opt.sectors && !opt.heads )
389     fprintf(stderr, "Warning: unable to obtain device geometry (defaulting to %d heads, %d sectors)\n"
390             "         (on hard disks, this is usually harmless.)\n",
391             geo->heads, geo->sectors);
392
393   return 1;
394 }
395
396 /*
397  * Query the device geometry and put it into the boot sector.
398  * Map the file and put the map in the boot sector and file.
399  * Stick the "current directory" inode number into the file.
400  */
401 void
402 patch_file_and_bootblock(int fd, int dirfd, int devfd)
403 {
404   struct stat dirst;
405   struct hd_geometry geo;
406   uint32_t *sectp;
407   uint64_t totalbytes, totalsectors;
408   int nsect;
409   unsigned char *p, *patcharea;
410   int i, dw;
411   uint32_t csum;
412
413   if ( fstat(dirfd, &dirst) ) {
414     perror("fstat dirfd");
415     exit(255);                  /* This should never happen */
416   }
417
418   totalbytes = get_size(devfd);
419   get_geometry(devfd, totalbytes, &geo);
420
421   if ( opt.heads )
422     geo.heads = opt.heads;
423   if ( opt.sectors )
424     geo.sectors = opt.sectors;
425
426   /* Patch this into a fake FAT superblock.  This isn't because
427      FAT is a good format in any way, it's because it lets the
428      early bootstrap share code with the FAT version. */
429   dprintf("heads = %u, sect = %u\n", geo.heads, geo.sectors);
430
431   totalsectors = totalbytes >> SECTOR_SHIFT;
432   if ( totalsectors >= 65536 ) {
433     set_16(boot_block+bsSectors, 0);
434   } else {
435     set_16(boot_block+bsSectors, totalsectors);
436   }
437   set_32(boot_block+bsHugeSectors, totalsectors);
438
439   set_16(boot_block+bsBytesPerSec, SECTOR_SIZE);
440   set_16(boot_block+bsSecPerTrack, geo.sectors);
441   set_16(boot_block+bsHeads, geo.heads);
442   set_32(boot_block+bsHiddenSecs, geo.start);
443
444   /* If we're in RAID mode then patch the appropriate instruction;
445      either way write the proper boot signature */
446   i = get_16(boot_block+0x1FE);
447   if (opt.raid_mode)
448     set_16(boot_block+i, 0x18CD);       /* INT 18h */
449
450   set_16(boot_block+0x1FE, 0xAA55);
451
452   /* Construct the boot file */
453
454   dprintf("directory inode = %lu\n", (unsigned long) dirst.st_ino);
455   nsect = (boot_image_len+SECTOR_SIZE-1) >> SECTOR_SHIFT;
456   nsect += 2;                   /* Two sectors for the ADV */
457   sectp = alloca(sizeof(uint32_t)*nsect);
458   if ( sectmap(fd, sectp, nsect) ) {
459     perror("bmap");
460     exit(1);
461   }
462
463   /* First sector need pointer in boot sector */
464   set_32(boot_block+0x1F8, *sectp++);
465   nsect--;
466
467   /* Search for LDLINUX_MAGIC to find the patch area */
468   for ( p = boot_image ; get_32(p) != LDLINUX_MAGIC ; p += 4 );
469   patcharea = p+8;
470
471   /* Set up the totals */
472   dw = boot_image_len >> 2;     /* COMPLETE dwords, excluding ADV */
473   set_16(patcharea, dw);
474   set_16(patcharea+2, nsect);   /* Not including the first sector, but
475                                    including the ADV */
476   set_32(patcharea+8, dirst.st_ino); /* "Current" directory */
477
478   /* Set the sector pointers */
479   p = patcharea+12;
480
481   memset(p, 0, 64*4);
482   while ( nsect-- ) {
483     set_32(p, *sectp++);
484     p += 4;
485   }
486
487   /* Now produce a checksum */
488   set_32(patcharea+4, 0);
489
490   csum = LDLINUX_MAGIC;
491   for ( i = 0, p = boot_image ; i < dw ; i++, p += 4 )
492     csum -= get_32(p);          /* Negative checksum */
493
494   set_32(patcharea+4, csum);
495 }
496
497 /*
498  * Read the ADV from an existing instance, or initialize if invalid.
499  * Returns -1 on fatal errors, 0 if ADV is okay, and 1 if no valid
500  * ADV was found.
501  */
502 int
503 read_adv(const char *path)
504 {
505   char *file;
506   int fd = -1;
507   struct stat st;
508   int err = 0;
509
510   asprintf(&file, "%s%sextlinux.sys",
511            path,
512            path[0] && path[strlen(path)-1] == '/' ? "" : "/");
513
514   if ( !file ) {
515     perror(program);
516     return -1;
517   }
518
519   fd = open(file, O_RDONLY);
520   if ( fd < 0 ) {
521     if ( errno != ENOENT ) {
522       err = -1;
523     } else {
524       syslinux_reset_adv(syslinux_adv);
525     }
526   } else if (fstat(fd, &st)) {
527     err = -1;
528   } else if (st.st_size < 2*ADV_SIZE) {
529     /* Too small to be useful */
530     syslinux_reset_adv(syslinux_adv);
531     err = 0;                    /* Nothing to read... */
532   } else if (xpread(fd, syslinux_adv, 2*ADV_SIZE,
533                     st.st_size-2*ADV_SIZE) != 2*ADV_SIZE) {
534     err = -1;
535   } else {
536     /* We got it... maybe? */
537     err = syslinux_validate_adv(syslinux_adv) ? 1 : 0;
538   }
539
540   if (err < 0)
541     perror(file);
542
543   if (fd >= 0)
544     close(fd);
545   if (file)
546     free(file);
547
548   return err;
549 }
550
551 /*
552  * Update the ADV in an existing installation.
553  */
554 int
555 write_adv(const char *path)
556 {
557   unsigned char advtmp[2*ADV_SIZE];
558   char *file;
559   int fd = -1;
560   struct stat st, xst;
561   int err = 0;
562   int flags, nflags;
563
564   asprintf(&file, "%s%sextlinux.sys",
565            path,
566            path[0] && path[strlen(path)-1] == '/' ? "" : "/");
567
568   if ( !file ) {
569     perror(program);
570     return -1;
571   }
572
573   fd = open(file, O_RDONLY);
574   if ( fd < 0 ) {
575     err = -1;
576   } else if (fstat(fd, &st)) {
577     err = -1;
578   } else if (st.st_size < 2*ADV_SIZE) {
579     /* Too small to be useful */
580     err = -2;
581   } else if (xpread(fd, advtmp, 2*ADV_SIZE,
582                     st.st_size-2*ADV_SIZE) != 2*ADV_SIZE) {
583     err = -1;
584   } else {
585     /* We got it... maybe? */
586     err = syslinux_validate_adv(advtmp) ? -2 : 0;
587     if (!err) {
588       /* Got a good one, write our own ADV here */
589       if (!ioctl(fd, EXT2_IOC_GETFLAGS, &flags)) {
590         nflags = flags & ~EXT2_IMMUTABLE_FL;
591         if (nflags != flags)
592           ioctl(fd, EXT2_IOC_SETFLAGS, &nflags);
593       }
594       if (!(st.st_mode & S_IWUSR))
595         fchmod(fd, st.st_mode | S_IWUSR);
596
597       /* Need to re-open read-write */
598       close(fd);
599       fd = open(file, O_RDWR|O_SYNC);
600       if (fd < 0) {
601         err = -1;
602       } else if (fstat(fd, &xst) || xst.st_ino != st.st_ino ||
603                  xst.st_dev != st.st_dev || xst.st_size != st.st_size) {
604         fprintf(stderr, "%s: race condition on write\n", file);
605         err = -2;
606       }
607       /* Write our own version ... */
608       if (xpwrite(fd, syslinux_adv, 2*ADV_SIZE,
609                   st.st_size-2*ADV_SIZE) != 2*ADV_SIZE) {
610         err = -1;
611       }
612
613       sync();
614
615       if (!(st.st_mode & S_IWUSR))
616         fchmod(fd, st.st_mode);
617
618       if (nflags != flags)
619         ioctl(fd, EXT2_IOC_SETFLAGS, &flags);
620     }
621   }
622
623   if (err == -2)
624     fprintf(stderr, "%s: cannot write auxilliary data (need --update)?\n",
625             file);
626   else if (err == -1)
627     perror(file);
628
629   if (fd >= 0)
630     close(fd);
631   if (file)
632     free(file);
633
634   return err;
635 }
636
637 /*
638  * Make any user-specified ADV modifications
639  */
640 int modify_adv(void)
641 {
642   int rv = 0;
643
644   if (opt.set_once) {
645     if (syslinux_setadv(ADV_BOOTONCE, strlen(opt.set_once), opt.set_once)) {
646       fprintf(stderr, "%s: not enough space for boot-once command\n", program);
647       rv = -1;
648     }
649   }
650
651   return rv;
652 }
653
654 /*
655  * Install the boot block on the specified device.
656  * Must be run AFTER install_file()!
657  */
658 int
659 install_bootblock(int fd, const char *device)
660 {
661   struct ext2_super_block sb;
662
663   if ( xpread(fd, &sb, sizeof sb, EXT2_SUPER_OFFSET) != sizeof sb ) {
664     perror("reading superblock");
665     return 1;
666   }
667
668   if ( sb.s_magic != EXT2_SUPER_MAGIC ) {
669     fprintf(stderr, "no ext2/ext3 superblock found on %s\n", device);
670     return 1;
671   }
672
673   if ( xpwrite(fd, boot_block, boot_block_len, 0) != boot_block_len ) {
674     perror("writing bootblock");
675     return 1;
676   }
677
678   return 0;
679 }
680
681 int
682 install_file(const char *path, int devfd, struct stat *rst)
683 {
684   char *file;
685   int fd = -1, dirfd = -1, flags;
686   struct stat st;
687
688   asprintf(&file, "%s%sextlinux.sys",
689            path,
690            path[0] && path[strlen(path)-1] == '/' ? "" : "/");
691   if ( !file ) {
692     perror(program);
693     return 1;
694   }
695
696   dirfd = open(path, O_RDONLY|O_DIRECTORY);
697   if ( dirfd < 0 ) {
698     perror(path);
699     goto bail;
700   }
701
702   fd = open(file, O_RDONLY);
703   if ( fd < 0 ) {
704     if ( errno != ENOENT ) {
705       perror(file);
706       goto bail;
707     }
708   } else {
709     /* If file exist, remove the immutable flag and set u+w mode */
710     if ( !ioctl(fd, EXT2_IOC_GETFLAGS, &flags) ) {
711       flags &= ~EXT2_IMMUTABLE_FL;
712       ioctl(fd, EXT2_IOC_SETFLAGS, &flags);
713     }
714     if ( !fstat(fd, &st) ) {
715       fchmod(fd, st.st_mode | S_IWUSR);
716     }
717   }
718   close(fd);
719
720   fd = open(file, O_WRONLY|O_TRUNC|O_CREAT|O_SYNC, S_IRUSR|S_IRGRP|S_IROTH);
721   if ( fd < 0 ) {
722     perror(file);
723     goto bail;
724   }
725
726   /* Write it the first time */
727   if ( xpwrite(fd, boot_image, boot_image_len, 0) != boot_image_len ||
728        xpwrite(fd, syslinux_adv, 2*ADV_SIZE, boot_image_len) != 2*ADV_SIZE ) {
729     fprintf(stderr, "%s: write failure on %s\n", program, file);
730     goto bail;
731   }
732
733   /* Map the file, and patch the initial sector accordingly */
734   patch_file_and_bootblock(fd, dirfd, devfd);
735
736   /* Write the first sector again - this relies on the file being
737      overwritten in place! */
738   if ( xpwrite(fd, boot_image, SECTOR_SIZE, 0) != SECTOR_SIZE ) {
739     fprintf(stderr, "%s: write failure on %s\n", program, file);
740     goto bail;
741   }
742
743   /* Attempt to set immutable flag and remove all write access */
744   /* Only set immutable flag if file is owned by root */
745   if ( !fstat(fd, &st) ) {
746     fchmod(fd, st.st_mode & (S_IRUSR|S_IRGRP|S_IROTH));
747     if ( st.st_uid == 0 && !ioctl(fd, EXT2_IOC_GETFLAGS, &flags) ) {
748       flags |= EXT2_IMMUTABLE_FL;
749       ioctl(fd, EXT2_IOC_SETFLAGS, &flags);
750     }
751   }
752
753   if ( fstat(fd, rst) ) {
754     perror(file);
755     goto bail;
756   }
757
758   close(dirfd);
759   close(fd);
760   return 0;
761
762  bail:
763   if ( dirfd >= 0 )
764     close(dirfd);
765   if ( fd >= 0 )
766     close(fd);
767
768   return 1;
769 }
770
771 /* EXTLINUX installs the string 'EXTLINUX' at offset 3 in the boot
772    sector; this is consistent with FAT filesystems. */
773 int
774 already_installed(int devfd)
775 {
776   char buffer[8];
777
778   xpread(devfd, buffer, 8, 3);
779   return !memcmp(buffer, "EXTLINUX", 8);
780 }
781
782
783 #ifdef __KLIBC__
784 static char devname_buf[64];
785
786 static void device_cleanup(void)
787 {
788   unlink(devname_buf);
789 }
790 #endif
791
792 /* Verify that a device fd and a pathname agree.
793    Return 0 on valid, -1 on error. */
794 static int validate_device(const char *path, int devfd)
795 {
796   struct stat pst, dst;
797
798   if (stat(path, &pst) || fstat(devfd, &dst))
799     return -1;
800
801   return (pst.st_dev == dst.st_rdev) ? 0 : -1;
802 }
803
804 #ifndef __KLIBC__
805 static const char *find_device(const char *mtab_file, dev_t dev)
806 {
807   struct mntent *mnt;
808   struct stat dst;
809   FILE *mtab;
810   const char *devname = NULL;
811
812   mtab = setmntent(mtab_file, "r");
813   if (!mtab)
814     return NULL;
815
816   while ( (mnt = getmntent(mtab)) ) {
817     if ( (!strcmp(mnt->mnt_type, "ext2") ||
818           !strcmp(mnt->mnt_type, "ext3")) &&
819          !stat(mnt->mnt_fsname, &dst) && dst.st_rdev == dev ) {
820       devname = strdup(mnt->mnt_fsname);
821       break;
822     }
823   }
824   endmntent(mtab);
825
826   return devname;
827 }
828 #endif
829
830 int
831 install_loader(const char *path, int update_only)
832 {
833   struct stat st, fst;
834   int devfd, rv;
835   const char *devname = NULL;
836   struct statfs sfs;
837
838   if ( stat(path, &st) || !S_ISDIR(st.st_mode) ) {
839     fprintf(stderr, "%s: Not a directory: %s\n", program, path);
840     return 1;
841   }
842
843   if ( statfs(path, &sfs) ) {
844     fprintf(stderr, "%s: statfs %s: %s\n", program, path, strerror(errno));
845     return 1;
846   }
847
848   if ( sfs.f_type != EXT2_SUPER_MAGIC ) {
849     fprintf(stderr, "%s: not an ext2/ext3 filesystem: %s\n", program, path);
850     return 1;
851   }
852
853   devfd = -1;
854
855 #ifdef __KLIBC__
856
857   /* klibc doesn't have getmntent and friends; instead, just create
858      a new device with the appropriate device type */
859   snprintf(devname_buf, sizeof devname_buf, "/tmp/dev-%u:%u",
860            major(st.st_dev), minor(st.st_dev));
861
862   if (mknod(devname_buf, S_IFBLK|0600, st.st_dev)) {
863     fprintf(stderr, "%s: cannot create device %s\n", program, devname);
864     return 1;
865   }
866
867   atexit(device_cleanup);       /* unlink the device node on exit */
868   devname = devname_buf;
869
870 #else
871
872   devname = find_device("/proc/mounts", st.st_dev);
873   if (!devname) {
874     /* Didn't find it in /proc/mounts, try /etc/mtab */
875     devname = find_device("/etc/mtab", st.st_dev);
876   }
877   if (!devname) {
878     fprintf(stderr, "%s: cannot find device for path %s\n", program, path);
879     return 1;
880   }
881
882   fprintf(stderr, "%s is device %s\n", path, devname);
883 #endif
884
885   if ( (devfd = open(devname, O_RDWR|O_SYNC)) < 0 ) {
886     fprintf(stderr, "%s: cannot open device %s\n", program, devname);
887     return 1;
888   }
889
890   /* Verify that the device we opened is the device intended */
891   if (validate_device(path, devfd)) {
892     fprintf(stderr, "%s: path %s doesn't match device %s\n",
893             program, path, devname);
894     return 1;
895   }
896
897   if ( update_only && !already_installed(devfd) ) {
898     fprintf(stderr, "%s: no previous extlinux boot sector found\n", program);
899     return 1;
900   }
901
902   /* Read a pre-existing ADV, if already installed */
903   if (opt.reset_adv)
904     syslinux_reset_adv(syslinux_adv);
905   else if (read_adv(path) < 0)
906     return 1;
907
908   if (modify_adv() < 0)
909     return 1;
910
911   /* Install extlinux.sys */
912   if (install_file(path, devfd, &fst))
913     return 1;
914
915   if ( fst.st_dev != st.st_dev ) {
916     fprintf(stderr, "%s: file system changed under us - aborting!\n",
917             program);
918     return 1;
919   }
920
921   sync();
922   rv = install_bootblock(devfd, devname);
923   close(devfd);
924   sync();
925
926   return rv;
927 }
928
929 /*
930  * Modify the ADV of an existing installation
931  */
932 int
933 modify_existing_adv(const char *path)
934 {
935   if (opt.reset_adv)
936     syslinux_reset_adv(syslinux_adv);
937   else if (read_adv(path) < 0)
938     return 1;
939
940   if (modify_adv() < 0)
941     return 1;
942
943   if (write_adv(path) < 0)
944     return 1;
945
946   return 0;
947 }
948
949 int
950 main(int argc, char *argv[])
951 {
952   int o;
953   const char *directory;
954   int update_only = -1;
955
956   program = argv[0];
957
958   while ( (o = getopt_long(argc, argv, short_options,
959                              long_options, NULL)) != EOF ) {
960     switch ( o ) {
961     case 'z':
962       opt.heads = 64;
963       opt.sectors = 32;
964       break;
965     case 'S':
966       opt.sectors = strtoul(optarg, NULL, 0);
967       if ( opt.sectors < 1 || opt.sectors > 63 ) {
968         fprintf(stderr, "%s: invalid number of sectors: %u (must be 1-63)\n",
969                 program, opt.sectors);
970         exit(EX_USAGE);
971       }
972       break;
973     case 'H':
974       opt.heads = strtoul(optarg, NULL, 0);
975       if ( opt.heads < 1 || opt.heads > 256 ) {
976         fprintf(stderr, "%s: invalid number of heads: %u (must be 1-256)\n",
977                 program, opt.heads);
978         exit(EX_USAGE);
979       }
980       break;
981     case 'r':
982       opt.raid_mode = 1;
983       break;
984     case 'i':
985       update_only = 0;
986       break;
987     case 'u':
988     case 'U':
989       update_only = 1;
990       break;
991     case 'h':
992       usage(0);
993       break;
994     case 'o':
995       opt.set_once = optarg;
996       break;
997     case 'O':
998       opt.set_once = "";
999       break;
1000     case OPT_RESET_ADV:
1001       opt.reset_adv = 1;
1002       break;
1003     case 'v':
1004       fputs("extlinux " VERSION_STR
1005             "  Copyright 1994-" YEAR_STR " H. Peter Anvin \n", stderr);
1006       exit(0);
1007     default:
1008       usage(EX_USAGE);
1009     }
1010   }
1011
1012   directory = argv[optind];
1013
1014   if ( !directory )
1015     usage(EX_USAGE);
1016
1017   if ( update_only == -1 ) {
1018     if (opt.reset_adv || opt.set_once) {
1019       return modify_existing_adv(directory);
1020     } else {
1021       usage(EX_USAGE);
1022     }
1023   }
1024
1025   return install_loader(directory, update_only);
1026 }