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