Bug fix to syslinux.c; documentation update
[profile/ivi/syslinux.git] / syslinux.c
1 #ident "$Id$"
2 /* ----------------------------------------------------------------------- *
3  *   
4  *   Copyright 1998 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., 675 Mass Ave, Cambridge MA 02139,
9  *   USA; either version 2 of the License, or (at your option) any later
10  *   version; incorporated herein by reference.
11  *
12  * ----------------------------------------------------------------------- */
13
14 /*
15  * syslinux.c - Linux installer program for SYSLINUX
16  *
17  * This program ought to be portable.  I hope so, at least.
18  *
19  * HPA note: this program needs too much privilege.  We should probably
20  * access the filesystem directly like mtools does so we don't have to
21  * mount the disk.  Either that or if Linux gets an fmount() system call
22  * we probably could do the mounting ourselves, and make this program
23  * setuid safe.
24  *
25  * Also, sync() between accessing the raw device and mount/umount seems
26  * to be necessary; I get data corruption otherwise.
27  */
28
29 #include <alloca.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <mntent.h>
33 #include <paths.h>
34 #include <stdio.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <sys/stat.h>
38 #include <sys/types.h>
39 #include <sys/wait.h>
40
41 #ifndef _PATH_MOUNT
42 #define _PATH_MOUNT "/bin/mount"
43 #endif
44
45 #ifndef _PATH_UMOUNT
46 #define _PATH_UMOUNT "/bin/umount"
47 #endif
48
49 extern unsigned char bootsect[];
50 extern unsigned int  bootsect_len;
51
52 extern unsigned char ldlinux[];
53 extern unsigned int  ldlinux_len;
54
55 char *program;                  /* Name of program */
56 char *device;                   /* Device to install to */
57 uid_t euid;                     /* Our current euid */
58
59 enum bs_offsets {
60   bsJump          = 0x00,
61   bsOemName       = 0x03,
62   bsBytesPerSec   = 0x0b,
63   bsSecPerClust   = 0x0d,
64   bsResSectors    = 0x0e,
65   bsFATs          = 0x10,
66   bsRootDirEnts   = 0x11,
67   bsSectors       = 0x13,
68   bsMedia         = 0x15,
69   bsFATsecs       = 0x16,
70   bsSecPerTrack   = 0x18,
71   bsHeads         = 0x1a,
72   bsHiddenSecs    = 0x1c,
73   bsHugeSectors   = 0x20,
74   bsDriveNumber   = 0x24,
75   bsReserved1     = 0x25,
76   bsBootSignature = 0x26,
77   bsVolumeID      = 0x27,
78   bsVolumeLabel   = 0x2b,
79   bsFileSysType   = 0x36,
80   bsCode          = 0x3e,
81   bsSignature     = 0x1fe
82 };
83
84 #define bsCopyStart bsBytesPerSec
85 #define bsCopyLen   (bsCode-bsBytesPerSec)
86
87 /*
88  * Access functions for littleendian numbers, possibly misaligned.
89  */
90 static u_int16_t get_16(unsigned char *p)
91 {
92   return (u_int16_t)p[0] + ((u_int16_t)p[1] << 8);
93 }
94
95 static u_int32_t get_32(unsigned char *p)
96 {
97   return (u_int32_t)p[0] + ((u_int32_t)p[1] << 8) +
98     ((u_int32_t)p[2] << 16) + ((u_int32_t)p[3] << 24);
99 }
100
101 int main(int argc, char *argv[])
102 {
103   static unsigned char sectbuf[512];
104   unsigned char *dp;
105   const unsigned char *cdp;
106   int dev_fd, fd;
107   struct stat st;
108   int nb, left, veryold;
109   unsigned int sectors, clusters;
110   int err = 0;
111   pid_t f, w;
112   int status;
113   char *mntpath = NULL, mntname[64];
114   char *ldlinux_name;
115   int my_umask;
116
117   program = argv[0];
118
119   if ( argc != 2 || argv[1][0] == '-' ) {
120     fprintf(stderr, "Usage: %s device\n", program);
121     exit(1);
122   }
123
124   device = argv[1];
125
126   /*
127    * First make sure we can open the device at all, and that we have
128    * read/write permission.
129    */
130   dev_fd = open(device, O_RDWR);
131   if ( dev_fd < 0 || fstat(dev_fd, &st) < 0 ) {
132     perror(device);
133     exit(1);
134   }
135
136   if ( !S_ISBLK(st.st_mode) && !S_ISREG(st.st_mode) ) {
137     fprintf(stderr, "%s: not a block device or regular file\n", device);
138     exit(1);
139   }
140
141   left = 512;
142   dp = sectbuf;
143   while ( left ) {
144     nb = read(dev_fd, dp, left);
145     if ( nb == -1 && errno == EINTR )
146       continue;
147     if ( nb < 0 ) {
148       perror(device);
149       exit(1);
150     } else if ( nb == 0 ) {
151       fprintf(stderr, "%s: no boot sector\n", device);
152       exit(1);
153     }
154     dp += nb;
155     left -= nb;
156   }
157   close(dev_fd);
158
159   sync();
160   
161   /*
162    * Check to see that what we got was indeed an MS-DOS boot sector/superblock
163    */
164
165   if ( sectbuf[bsBootSignature] == 0x29 ) {
166     /* It's DOS, and it has all the new nice fields */
167
168     veryold = 0;
169
170     sectors = get_16(sectbuf+bsSectors);
171     sectors = sectors ? sectors : get_32(sectbuf+bsHugeSectors);
172     clusters = sectors / sectbuf[bsSecPerClust];
173
174     if ( !memcmp(sectbuf+bsFileSysType, "FAT12   ", 8) ) {
175       if ( clusters > 4086 ) {
176         fprintf(stderr, "%s: ERROR: FAT12 but claims more than 4086 clusters\n",
177                 device);
178         exit(1);
179       }
180     } else if ( !memcmp(sectbuf+bsFileSysType, "FAT16   ", 8) ) {
181       if ( clusters <= 4086 ) {
182         fprintf(stderr, "%s: ERROR: FAT16 but claims less than 4086 clusters\n",
183                 device);
184         exit(1);
185       }
186     } else {
187       fprintf(stderr, "%s: filesystem type \"%8.8s\" not supported\n",
188               device, sectbuf+bsFileSysType);
189       exit(1);
190     }
191   } else {
192     veryold = 1;
193
194     if ( sectbuf[bsSecPerClust] & (sectbuf[bsSecPerClust] - 1) ||
195          sectbuf[bsSecPerClust] == 0 ) {
196       fprintf(stderr, "%s: This doesn't look like a FAT filesystem\n",
197               device);
198     }
199
200     sectors = get_16(sectbuf+bsSectors);
201     sectors = sectors ? sectors : get_32(sectbuf+bsHugeSectors);
202     clusters = sectors / sectbuf[bsSecPerClust];
203     
204     if ( clusters > 4086 ) {
205       fprintf(stderr, "%s: Only FAT12 filesystems supported\n", device);
206       exit(1);
207     }
208   }
209
210   if ( get_16(sectbuf+bsBytesPerSec) != 512 ) {
211     fprintf(stderr, "%s: Sector sizes other than 512 not supported\n",
212             device);
213     exit(1);
214   }
215   if ( sectbuf[bsSecPerClust] > 32 ) {
216     fprintf(stderr, "%s: Cluster sizes larger than 16K not supported\n",
217             device);
218   }
219
220   /*
221    * Now mount the device.  If we are non-root we need to find an fstab
222    * entry for this device which has the user flag and the appropriate
223    * options set.
224    */
225   if ( (euid = geteuid()) ) {
226     FILE *fstab;
227     struct mntent *mnt;
228
229     if ( !(fstab = setmntent(MNTTAB, "r")) ) {
230       fprintf(stderr, "%s: cannot open " MNTTAB "\n", program);
231     }
232     
233     while ( (mnt = getmntent(fstab)) ) {
234       if ( !strcmp(device, mnt->mnt_fsname) &&
235            ( !strcmp(mnt->mnt_type, "msdos") ||
236              !strcmp(mnt->mnt_type, "umsdos") ||
237              !strcmp(mnt->mnt_type, "vfat") ||
238              !strcmp(mnt->mnt_type, "uvfat") ||
239              !strcmp(mnt->mnt_type, "auto") ) &&
240            hasmntopt(mnt, "user") &&
241            !hasmntopt(mnt, "ro") &&
242            mnt->mnt_dir[0] == '/' &&
243            !!hasmntopt(mnt, "loop") == !!S_ISREG(st.st_mode)) {
244         /* Okay, this is an fstab entry we should be able to live with. */
245
246         mntpath = mnt->mnt_dir;
247         break;
248       }
249     }
250     endmntent(fstab);
251
252     if ( !mntpath ) {
253       fprintf(stderr, "%s: not root and no appropriate entry for %s in "
254               MNTTAB "\n", program, device);
255       exit(1);
256     }
257   
258     f = fork();
259     if ( f < 0 ) {
260       perror(program);
261       exit(1);
262     } else if ( f == 0 ) {
263       execl(_PATH_MOUNT, _PATH_MOUNT, mntpath, NULL);
264       _exit(255);               /* If execl failed, trouble... */
265     }
266   } else {
267     int i = 0;
268
269     /* We're root.  Make a temp dir and pass all the gunky options to mount. */
270
271     do {
272       sprintf(mntname, "/tmp/syslinux.mnt.%lu.%d", (unsigned long)getpid(), i++);
273     } while ( (errno = 0, mkdir(mntname, 0700) == -1) &&
274               (errno == EEXIST || errno == EINTR) );
275     if ( errno ) {
276       perror(program);
277       exit(1);
278     }
279     
280     mntpath = mntname;
281
282     f = fork();
283     if ( f < 0 ) {
284       perror(program);
285       rmdir(mntpath);
286       exit(1);
287     } else if ( f == 0 ) {
288       if ( S_ISREG(st.st_mode) )
289         execl(_PATH_MOUNT, _PATH_MOUNT, "-t", "msdos", "-o", "loop", "-w",
290               device, mntpath, NULL);
291       else
292         execl(_PATH_MOUNT, _PATH_MOUNT, "-t", "msdos", "-w", device, mntpath,
293               NULL);
294       _exit(255);               /* execl failed */
295     }
296   }
297
298   w = waitpid(f, &status, 0);
299   if ( w != f || status ) {
300     if ( !euid )
301       rmdir(mntpath);
302     exit(1);                    /* Mount failed */
303   }
304
305   ldlinux_name = alloca(strlen(mntpath)+13);
306   if ( !ldlinux_name ) {
307     perror("malloc");
308     err = 1;
309     goto umount;
310   }
311   sprintf(ldlinux_name, "%s/ldlinux.sys", mntpath);
312
313   unlink(ldlinux_name);
314   fd = open(ldlinux_name, O_WRONLY|O_CREAT|O_TRUNC, 0444);
315   if ( fd < 0 ) {
316     perror(device);
317     err = 1;
318     goto umount;
319   }
320
321   cdp = ldlinux;
322   left = ldlinux_len;
323   while ( left ) {
324     nb = write(fd, cdp, left);
325     if ( nb == -1 && errno == EINTR )
326       continue;
327     else if ( nb <= 0 ) {
328       perror(device);
329       err = 1;
330       goto umount;
331     }
332
333     dp += nb;
334     left -= nb;
335   }
336
337   /*
338    * I don't understand why I need this.  Does the DOS filesystems
339    * not honour the mode passed to open()?
340    */
341   my_umask = umask(0777);
342   umask(my_umask);
343   fchmod(fd, 0444 & ~my_umask);
344
345   close(fd);
346
347 umount:
348   f = fork();
349   if ( f < 0 ) {
350     perror("fork");
351     exit(1);
352   } else if ( f == 0 ) {
353     execl(_PATH_UMOUNT, _PATH_UMOUNT, mntpath, NULL);
354   }
355
356   w = waitpid(f, &status, 0);
357   if ( w != f || status ) {
358     exit(1);
359   }
360
361   sync();
362
363   if ( !euid )
364     rmdir(mntpath);
365
366   if ( err )
367     exit(err);
368
369   /*
370    * To finish up, write the boot sector
371    */
372
373   dev_fd = open(device, O_RDWR);
374   if ( dev_fd < 0 ) {
375     perror(device);
376     exit(1);
377   }
378
379   /* Copy the old superblock into the new boot sector */
380   memcpy(bootsect+bsCopyStart, sectbuf+bsCopyStart, bsCopyLen);
381   
382   dp = bootsect;
383   left = bootsect_len;
384   while ( left ) {
385     nb = write(dev_fd, dp, left);
386     if ( nb == -1 && errno == EINTR )
387       continue;
388     else if ( nb <= 0 ) {
389       perror(device);
390       exit(1);
391     }
392     
393     dp += nb;
394     left -= nb;
395   }
396   close(dev_fd);
397
398   /* Done! */
399
400   return 0;
401 }