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