c0cf3461fb425c3bdf37d8b3f5088453b4ac49d0
[profile/ivi/syslinux.git] / unix / syslinux.c
1 #ident "$Id$"
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  * This program ought to be portable.  I hope so, at least.
18  *
19  * This is an alternate version of the installer which doesn't require
20  * mtools, but requires root privilege.
21  */
22
23 #ifdef __KLIBC__
24 # define DO_DIRECT_MOUNT 1      /* Call mount(2) directly */
25 #else
26 # define DO_DIRECT_MOUNT 0      /* Call /bin/mount instead */
27 #endif
28
29 #define _GNU_SOURCE
30 #define _XOPEN_SOURCE 500       /* For pread() pwrite() */
31 #define _FILE_OFFSET_BITS 64
32 #include <alloca.h>
33 #include <errno.h>
34 #include <fcntl.h>
35 #include <paths.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 #include <inttypes.h>
41 #include <sys/stat.h>
42 #include <sys/types.h>
43 #include <sys/wait.h>
44 #include <sys/mount.h>
45
46 #include "syslinux.h"
47 #include "libfat.h"
48
49 #if DO_DIRECT_MOUNT
50
51 # include <linux/loop.h>
52
53 #else
54
55 # include <paths.h>
56 # ifndef _PATH_MOUNT
57 #  define _PATH_MOUNT "/bin/mount"
58 # endif
59 # ifndef _PATH_UMOUNT
60 #  define _PATH_UMOUNT "/bin/umount"
61 # endif
62
63 #endif
64
65 const char *program;            /* Name of program */
66 const char *device;             /* Device to install to */
67 pid_t mypid;
68 char *mntpath = NULL;           /* Path on which to mount */
69 off_t filesystem_offset = 0;    /* Filesystem offset */
70 #if DO_DIRECT_MOUNT
71 int loop_fd = -1;               /* Loop device */
72 #endif
73
74 void __attribute__((noreturn)) usage(void)
75 {
76   fprintf(stderr, "Usage: %s [-sf] [-o offset] device\n", program);
77   exit(1);
78 }
79
80 void __attribute__((noreturn)) die(const char *msg)
81 {
82   fprintf(stderr, "%s: %s\n", program, msg);
83
84 #if DO_DIRECT_MOUNT
85   if ( loop_fd != -1 ) {
86     ioctl(loop_fd, LOOP_CLR_FD, 0); /* Free loop device */
87     close(loop_fd);
88     loop_fd = -1;
89   }
90 #endif
91
92   if ( mntpath )
93     unlink(mntpath);
94
95   exit(1);
96 }
97
98 /*
99  * read/write wrapper functions
100  */
101 ssize_t xpread(int fd, void *buf, size_t count, off_t offset)
102 {
103   ssize_t rv;
104   ssize_t done = 0;
105
106   while ( count ) {
107     rv = pread(fd, buf, count, offset);
108     if ( rv == 0 ) {
109       die("short read");
110     } else if ( rv == -1 ) {
111       if ( errno == EINTR ) {
112         continue;
113       } else {
114         perror(program);
115         exit(1);
116       }
117     } else {
118       offset += rv;
119       done += rv;
120       count -= rv;
121     }
122   }
123
124   return done;
125 }
126
127 ssize_t xpwrite(int fd, void *buf, size_t count, off_t offset)
128 {
129   ssize_t rv;
130   ssize_t done = 0;
131
132   while ( count ) {
133     rv = pwrite(fd, buf, count, offset);
134     if ( rv == 0 ) {
135       die("short write");
136     } else if ( rv == -1 ) {
137       if ( errno == EINTR ) {
138         continue;
139       } else {
140         perror(program);
141         exit(1);
142       }
143     } else {
144       offset += rv;
145       done += rv;
146       count -= rv;
147     }
148   }
149
150   return done;
151 }
152
153 /*
154  * Version of the read function suitable for libfat
155  */
156 int libfat_xpread(intptr_t pp, void *buf, size_t secsize, libfat_sector_t sector)
157 {
158   off_t offset = (off_t)sector * secsize + filesystem_offset;
159   return xpread(pp, buf, secsize, offset);
160 }
161
162 int main(int argc, char *argv[])
163 {
164   static unsigned char sectbuf[512];
165   unsigned char *dp;
166   const unsigned char *cdp;
167   int dev_fd, fd;
168   struct stat st;
169   int nb, left;
170   int err = 0;
171   pid_t f, w;
172   int status;
173   char mntname[64], devfdname[64];
174   char *ldlinux_name, **argp, *opt;
175   int force = 0;                /* -f (force) option */
176   struct libfat_filesystem *fs;
177   libfat_sector_t s, *secp, sectors[65]; /* 65 is maximum possible */
178   int32_t ldlinux_cluster;
179   int nsectors;
180   const char *errmsg;
181
182   (void)argc;                   /* Unused */
183
184   program = argv[0];
185   mypid = getpid();
186   
187   device = NULL;
188
189   umask(077);
190
191   for ( argp = argv+1 ; *argp ; argp++ ) {
192     if ( **argp == '-' ) {
193       opt = *argp + 1;
194       if ( !*opt )
195         usage();
196
197       while ( *opt ) {
198         if ( *opt == 's' ) {
199           syslinux_make_stupid();       /* Use "safe, slow and stupid" code */
200         } else if ( *opt == 'f' ) {
201           force = 1;            /* Force install */
202         } else if ( *opt == 'o' && argp[1] ) {
203           filesystem_offset = (off_t)strtoull(*++argp, NULL, 0); /* Byte offset */
204         } else {
205           usage();
206         }
207         opt++;
208       }
209     } else {
210       if ( device )
211         usage();
212       device = *argp;
213     }
214   }
215
216   if ( !device )
217     usage();
218
219   /*
220    * First make sure we can open the device at all, and that we have
221    * read/write permission.
222    */
223   dev_fd = open(device, O_RDWR);
224   if ( dev_fd < 0 || fstat(dev_fd, &st) < 0 ) {
225     perror(device);
226     exit(1);
227   }
228
229   if ( !force && !S_ISBLK(st.st_mode) && !S_ISREG(st.st_mode) ) {
230     die("not a block device or regular file (use -f to override)");
231   }
232
233   if ( !force && filesystem_offset && !S_ISREG(st.st_mode) ) {
234     die("not a regular file and an offset specified (use -f to override)");
235   }
236
237   xpread(dev_fd, sectbuf, 512, filesystem_offset);
238   fsync(dev_fd);
239
240   /*
241    * Check to see that what we got was indeed an MS-DOS boot sector/superblock
242    */
243   if( (errmsg = syslinux_check_bootsect(sectbuf)) ) {
244     fprintf(stderr, "%s: %s\n", device, errmsg);
245     exit(1);
246   }
247
248   /*
249    * Now mount the device.
250    */
251   if ( geteuid() ) {
252     die("This program needs root privilege");
253   } else {
254     int i = 0;
255     struct stat dst;
256     int rv;
257
258     /* We're root or at least setuid.
259        Make a temp dir and pass all the gunky options to mount. */
260
261     if ( chdir("/tmp") ) {
262       perror(program);
263       exit(1);
264     }
265
266 #define TMP_MODE (S_IXUSR|S_IWUSR|S_IXGRP|S_IWGRP|S_IWOTH|S_IXOTH|S_ISVTX)
267
268     if ( stat(".", &dst) || !S_ISDIR(dst.st_mode) ||
269          (dst.st_mode & TMP_MODE) != TMP_MODE ) {
270       die("possibly unsafe /tmp permissions");
271     }
272
273     for ( i = 0 ; ; i++ ) {
274       snprintf(mntname, sizeof mntname, "syslinux.mnt.%lu.%d",
275                (unsigned long)mypid, i);
276
277       if ( lstat(mntname, &dst) != -1 || errno != ENOENT )
278         continue;
279
280       rv = mkdir(mntname, 0000);
281
282       if ( rv == -1 ) {
283         if ( errno == EEXIST || errno == EINTR )
284           continue;
285         perror(program);
286         exit(1);
287       }
288
289       if ( lstat(mntname, &dst) || dst.st_mode != (S_IFDIR|0000) ||
290            dst.st_uid != 0 ) {
291         die("someone is trying to symlink race us!");
292       }
293       break;                    /* OK, got something... */
294     }
295
296     mntpath = mntname;
297
298 #if DO_DIRECT_MOUNT
299     if ( S_ISREG(st.st_mode) ) {
300       /* It's file, need to mount it loopback */
301       unsigned int n = 0;
302       struct loop_info64 loopinfo;
303
304       for ( n = 0 ; loop_fd < 0 ; n++ ) {
305         snprintf(devfdname, sizeof devfdname, "/dev/loop%u", n);
306         loop_fd = open(devfdname, O_RDWR);
307         if ( loop_fd < 0 && errno == ENOENT ) {
308           die("no available loopback device!");
309         }
310         if ( ioctl(loop_fd, LOOP_SET_FD, (void *)dev_fd) ) {
311           close(loop_fd); loop_fd = -1;
312           if ( errno != EBUSY )
313             die("cannot set up loopback device");
314           else
315             continue;
316         }
317         
318         if ( ioctl(loop_fd, LOOP_GET_STATUS64, &loopinfo) ||
319              (loopinfo.lo_offset = filesystem_offset,
320               ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo)) )
321           die("cannot set up loopback device");
322       }
323     } else {
324       snprintf(devfdname, sizeof devfdname, "/proc/%lu/fd/%d",
325                (unsigned long)mypid, dev_fd);
326     }
327
328     if ( mount(devfdname, mntpath, "msdos",
329                MS_NOEXEC|MS_NOSUID, "umask=077,quiet") )
330       die("could not mount filesystem");
331
332 #else
333
334     snprintf(devfdname, sizeof devfdname, "/proc/%lu/fd/%d",
335              (unsigned long)mypid, dev_fd);
336
337     f = fork();
338     if ( f < 0 ) {
339       perror(program);
340       rmdir(mntpath);
341       exit(1);
342     } else if ( f == 0 ) {
343       char mnt_opts[128];
344       if ( S_ISREG(st.st_mode) ) {
345         snprintf(mnt_opts, sizeof mnt_opts, "rw,nodev,noexec,loop,offset=%llu,umask=077,quiet",
346                  (unsigned long long)filesystem_offset);
347       } else {
348         snprintf(mnt_opts, sizeof mnt_opts, "rw,nodev,noexec,umask=077,quiet");
349       }
350       execl(_PATH_MOUNT, _PATH_MOUNT, "-t", "msdos", "-o", mnt_opts,\
351             devfdname, mntpath, NULL);
352       _exit(255);               /* execl failed */
353     }
354
355     w = waitpid(f, &status, 0);
356     if ( w != f || status ) {
357       rmdir(mntpath);
358       exit(1);                  /* Mount failed */
359     }
360     
361 #endif
362   }
363   
364   ldlinux_name = alloca(strlen(mntpath)+13);
365   if ( !ldlinux_name ) {
366     perror(program);
367     err = 1;
368     goto umount;
369   }
370   sprintf(ldlinux_name, "%s/ldlinux.sys", mntpath);
371
372   unlink(ldlinux_name);
373   fd = open(ldlinux_name, O_WRONLY|O_CREAT|O_TRUNC, 0444);
374   if ( fd < 0 ) {
375     perror(device);
376     err = 1;
377     goto umount;
378   }
379
380   cdp = syslinux_ldlinux;
381   left = syslinux_ldlinux_len;
382   while ( left ) {
383     nb = write(fd, cdp, left);
384     if ( nb == -1 && errno == EINTR )
385       continue;
386     else if ( nb <= 0 ) {
387       perror(device);
388       err = 1;
389       goto umount;
390     }
391
392     dp += nb;
393     left -= nb;
394   }
395
396   /*
397    * I don't understand why I need this.  Does the DOS filesystems
398    * not honour the mode passed to open()?
399    */
400   fchmod(fd, 0400);
401
402   close(fd);
403
404   sync();
405
406 umount:
407 #if DO_DIRECT_MOUNT
408
409   if ( umount2(mntpath, 0) )
410     die("could not umount path");
411
412   if ( loop_fd != -1 ) {
413     ioctl(loop_fd, LOOP_CLR_FD, 0); /* Free loop device */
414     close(loop_fd);
415     loop_fd = -1;
416   }
417
418 #else
419
420   f = fork();
421   if ( f < 0 ) {
422     perror("fork");
423     exit(1);
424   } else if ( f == 0 ) {
425     execl(_PATH_UMOUNT, _PATH_UMOUNT, mntpath, NULL);
426   }
427
428   w = waitpid(f, &status, 0);
429   if ( w != f || status ) {
430     exit(1);
431   }
432
433 #endif
434
435   sync();
436   rmdir(mntpath);
437
438   if ( err )
439     exit(err);
440
441   /*
442    * Now, use libfat to create a block map.  This probably
443    * should be changed to use ioctl(...,FIBMAP,...) since
444    * this is supposed to be a simple, privileged version
445    * of the installer.
446    */
447   fs = libfat_open(libfat_xpread, dev_fd);
448   ldlinux_cluster = libfat_searchdir(fs, 0, "LDLINUX SYS", NULL);
449   secp = sectors;
450   nsectors = 0;
451   s = libfat_clustertosector(fs, ldlinux_cluster);
452   while ( s && nsectors < 65 ) {
453     *secp++ = s;
454     nsectors++;
455     s = libfat_nextsector(fs, s);
456   }
457   libfat_close(fs);
458
459   /*
460    * Patch ldlinux.sys and the boot sector
461    */
462   syslinux_patch(sectors, nsectors);
463
464   /*
465    * Write the now-patched first sector of ldlinux.sys
466    */
467   xpwrite(dev_fd, syslinux_ldlinux, 512, filesystem_offset + ((off_t)sectors[0] << 9));
468
469   /*
470    * To finish up, write the boot sector
471    */
472
473   /* Read the superblock again since it might have changed while mounted */
474   xpread(dev_fd, sectbuf, 512, filesystem_offset);
475
476   /* Copy the syslinux code into the boot sector */
477   syslinux_make_bootsect(sectbuf);
478
479   /* Write new boot sector */
480   xpwrite(dev_fd, sectbuf, 512, filesystem_offset);
481
482   close(dev_fd);
483   sync();
484
485   /* Done! */
486
487   return 0;
488 }
489