Move files out of root into core, dos, and utils
[profile/ivi/syslinux.git] / linux / syslinux.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  * 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 [-sfr][-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   const char *subdir = NULL;
328   uint32_t sectors[65]; /* 65 is maximum possible */
329   int nsectors = 0;
330   const char *errmsg;
331   int mnt_cookie;
332
333   int force = 0;                /* -f (force) option */
334   int stupid = 0;               /* -s (stupid) option */
335   int raid_mode = 0;            /* -r (RAID) option */
336
337   (void)argc;                   /* Unused */
338
339   program = argv[0];
340   mypid = getpid();
341
342   device = NULL;
343
344   umask(077);
345
346   for ( argp = argv+1 ; *argp ; argp++ ) {
347     if ( **argp == '-' ) {
348       opt = *argp + 1;
349       if ( !*opt )
350         usage();
351
352       while ( *opt ) {
353         if ( *opt == 's' ) {
354           stupid = 1;
355         } else if ( *opt == 'r' ) {
356           raid_mode = 1;
357         } else if ( *opt == 'f' ) {
358           force = 1;            /* Force install */
359         } else if ( *opt == 'd' && argp[1] ) {
360           subdir = *++argp;
361         } else if ( *opt == 'o' && argp[1] ) {
362           /* Byte offset */
363           filesystem_offset = (off_t)strtoull(*++argp, NULL, 0);
364         } else {
365           usage();
366         }
367         opt++;
368       }
369     } else {
370       if ( device )
371         usage();
372       device = *argp;
373     }
374   }
375
376   if ( !device )
377     usage();
378
379   /*
380    * First make sure we can open the device at all, and that we have
381    * read/write permission.
382    */
383   dev_fd = open(device, O_RDWR);
384   if ( dev_fd < 0 || fstat(dev_fd, &st) < 0 ) {
385     perror(device);
386     exit(1);
387   }
388
389   if ( !S_ISBLK(st.st_mode) && !S_ISREG(st.st_mode) && !S_ISCHR(st.st_mode) ) {
390     die("not a device or regular file");
391   }
392
393   if ( filesystem_offset && S_ISBLK(st.st_mode) ) {
394     die("can't combine an offset with a block device");
395   }
396
397   xpread(dev_fd, sectbuf, SECTOR_SIZE, filesystem_offset);
398   fsync(dev_fd);
399
400   /*
401    * Check to see that what we got was indeed an MS-DOS boot sector/superblock
402    */
403   if( (errmsg = syslinux_check_bootsect(sectbuf)) ) {
404     fprintf(stderr, "%s: %s\n", device, errmsg);
405     exit(1);
406   }
407
408   /*
409    * Now mount the device.
410    */
411   if ( geteuid() ) {
412     die("This program needs root privilege");
413   } else {
414     int i = 0;
415     struct stat dst;
416     int rv;
417
418     /* We're root or at least setuid.
419        Make a temp dir and pass all the gunky options to mount. */
420
421     if ( chdir(_PATH_TMP) ) {
422       perror(program);
423       exit(1);
424     }
425
426 #define TMP_MODE (S_IXUSR|S_IWUSR|S_IXGRP|S_IWGRP|S_IWOTH|S_IXOTH|S_ISVTX)
427
428     if ( stat(".", &dst) || !S_ISDIR(dst.st_mode) ||
429          (dst.st_mode & TMP_MODE) != TMP_MODE ) {
430       die("possibly unsafe " _PATH_TMP " permissions");
431     }
432
433     for ( i = 0 ; ; i++ ) {
434       snprintf(mntname, sizeof mntname, "syslinux.mnt.%lu.%d",
435                (unsigned long)mypid, i);
436
437       if ( lstat(mntname, &dst) != -1 || errno != ENOENT )
438         continue;
439
440       rv = mkdir(mntname, 0000);
441
442       if ( rv == -1 ) {
443         if ( errno == EEXIST || errno == EINTR )
444           continue;
445         perror(program);
446         exit(1);
447       }
448
449       if ( lstat(mntname, &dst) || dst.st_mode != (S_IFDIR|0000) ||
450            dst.st_uid != 0 ) {
451         die("someone is trying to symlink race us!");
452       }
453       break;                    /* OK, got something... */
454     }
455
456     mntpath = mntname;
457   }
458
459   if (do_mount(dev_fd, &mnt_cookie, mntpath, "vfat") &&
460       do_mount(dev_fd, &mnt_cookie, mntpath, "msdos")) {
461     rmdir(mntpath);
462     die("mount failed");
463   }
464
465   ldlinux_name = alloca(strlen(mntpath)+14+
466                         (subdir ? strlen(subdir)+2 : 0));
467   if ( !ldlinux_name ) {
468     perror(program);
469     err = 1;
470     goto umount;
471   }
472   sprintf(ldlinux_name, "%s%s%s//ldlinux.sys",
473           mntpath, subdir ? "//" : "", subdir ? subdir : "");
474
475   if ((fd = open(ldlinux_name, O_RDONLY)) >= 0) {
476     uint32_t zero_attr = 0;
477     ioctl(fd, FAT_IOCTL_SET_ATTRIBUTES, &zero_attr);
478     close(fd);
479   }
480
481   unlink(ldlinux_name);
482   fd = open(ldlinux_name, O_WRONLY|O_CREAT|O_TRUNC, 0444);
483   if ( fd < 0 ) {
484     perror(device);
485     err = 1;
486     goto umount;
487   }
488
489   cdp = syslinux_ldlinux;
490   left = syslinux_ldlinux_len;
491   while ( left ) {
492     nb = write(fd, cdp, left);
493     if ( nb == -1 && errno == EINTR )
494       continue;
495     else if ( nb <= 0 ) {
496       perror(device);
497       err = 1;
498       goto umount;
499     }
500
501     dp += nb;
502     left -= nb;
503   }
504
505   fsync(fd);
506   /*
507    * Set the attributes
508    */
509   {
510     uint32_t attr = 0x07;       /* Hidden+System+Readonly */
511     ioctl(fd, FAT_IOCTL_SET_ATTRIBUTES, &attr);
512   }
513
514   /*
515    * Create a block map.
516    */
517   nsectors = make_block_map(sectors, syslinux_ldlinux_len, dev_fd, fd);
518
519   close(fd);
520   sync();
521
522 umount:
523   do_umount(mntpath, mnt_cookie);
524   sync();
525   rmdir(mntpath);
526
527   if ( err )
528     exit(err);
529
530   /*
531    * Patch ldlinux.sys and the boot sector
532    */
533   syslinux_patch(sectors, nsectors, stupid, raid_mode);
534
535   /*
536    * Write the now-patched first sector of ldlinux.sys
537    */
538   xpwrite(dev_fd, syslinux_ldlinux, SECTOR_SIZE,
539           filesystem_offset+((off_t)sectors[0] << SECTOR_BITS));
540
541   /*
542    * To finish up, write the boot sector
543    */
544
545   /* Read the superblock again since it might have changed while mounted */
546   xpread(dev_fd, sectbuf, SECTOR_SIZE, filesystem_offset);
547
548   /* Copy the syslinux code into the boot sector */
549   syslinux_make_bootsect(sectbuf);
550
551   /* Write new boot sector */
552   xpwrite(dev_fd, sectbuf, SECTOR_SIZE, filesystem_offset);
553
554   close(dev_fd);
555   sync();
556
557   /* Done! */
558
559   return 0;
560 }