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