whichsys.c32: execute specific command, based on Syslinux bootloader variant
[profile/ivi/syslinux.git] / linux / syslinux.c
index 0232377..9462138 100644 (file)
@@ -1,6 +1,7 @@
 /* ----------------------------------------------------------------------- *
  *
  *   Copyright 1998-2008 H. Peter Anvin - All Rights Reserved
+ *   Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin
  *
  *   This program is free software; you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License as published by
 #include <sys/wait.h>
 #include <sys/mount.h>
 
-#include <sys/ioctl.h>
-#include <linux/fs.h>          /* FIGETBSZ, FIBMAP */
-#include <linux/msdos_fs.h>    /* FAT_IOCTL_SET_ATTRIBUTES */
-#ifndef FAT_IOCTL_SET_ATTRIBUTES
-# define FAT_IOCTL_SET_ATTRIBUTES _IOW('r', 0x11, uint32_t)
-#endif
-#undef SECTOR_SIZE
-#undef SECTOR_SHIFT
+#include "linuxioctl.h"
 
 #include <paths.h>
 #ifndef _PATH_MOUNT
 # include <linux/loop.h>
 #endif
 
-const char *program;           /* Name of program */
-const char *device;            /* Device to install to */
+#include <getopt.h>
+#include <sysexits.h>
+#include "syslxcom.h"
+#include "setadv.h"
+#include "syslxopt.h" /* unified options */
+
+extern const char *program;    /* Name of program */
+
 pid_t mypid;
 char *mntpath = NULL;          /* Path on which to mount */
-off_t filesystem_offset = 0;   /* Filesystem offset */
+
 #if DO_DIRECT_MOUNT
 int loop_fd = -1;              /* Loop device */
 #endif
 
-void __attribute__ ((noreturn)) usage(void)
-{
-    fprintf(stderr, "Usage: %s [-sfr][-d directory][-o offset] device\n",
-           program);
-    exit(1);
-}
-
 void __attribute__ ((noreturn)) die(const char *msg)
 {
     fprintf(stderr, "%s: %s\n", program, msg);
@@ -107,98 +100,6 @@ void __attribute__ ((noreturn)) die(const char *msg)
 }
 
 /*
- * read/write wrapper functions
- */
-ssize_t xpread(int fd, void *buf, size_t count, off_t offset)
-{
-    char *bufp = (char *)buf;
-    ssize_t rv;
-    ssize_t done = 0;
-
-    while (count) {
-       rv = pread(fd, bufp, count, offset);
-       if (rv == 0) {
-           die("short read");
-       } else if (rv == -1) {
-           if (errno == EINTR) {
-               continue;
-           } else {
-               die(strerror(errno));
-           }
-       } else {
-           bufp += rv;
-           offset += rv;
-           done += rv;
-           count -= rv;
-       }
-    }
-
-    return done;
-}
-
-ssize_t xpwrite(int fd, const void *buf, size_t count, off_t offset)
-{
-    const char *bufp = (const char *)buf;
-    ssize_t rv;
-    ssize_t done = 0;
-
-    while (count) {
-       rv = pwrite(fd, bufp, count, offset);
-       if (rv == 0) {
-           die("short write");
-       } else if (rv == -1) {
-           if (errno == EINTR) {
-               continue;
-           } else {
-               die(strerror(errno));
-           }
-       } else {
-           bufp += rv;
-           offset += rv;
-           done += rv;
-           count -= rv;
-       }
-    }
-
-    return done;
-}
-
-/*
- * Create a block map for ldlinux.sys
- */
-int make_block_map(uint32_t * sectors, int len, int dev_fd, int fd)
-{
-    int nsectors = 0;
-    int blocksize, nblock, block;
-    int i;
-
-    (void)dev_fd;
-
-    if (ioctl(fd, FIGETBSZ, &blocksize) < 0)
-       die("ioctl FIGETBSZ failed");
-
-    blocksize >>= SECTOR_SHIFT;        /* sectors/block */
-
-    nblock = 0;
-    while (len > 0) {
-       block = nblock++;
-       if (ioctl(fd, FIBMAP, &block) < 0)
-           die("ioctl FIBMAP failed");
-
-       for (i = 0; i < blocksize; i++) {
-           if (len <= 0)
-               break;
-
-           *sectors++ = (block * blocksize) + i;
-           nsectors++;
-           len -= (1 << SECTOR_SHIFT);
-       }
-    }
-
-    return nsectors;
-}
-
-/*
  * Mount routine
  */
 int do_mount(int dev_fd, int *cookie, const char *mntpath, const char *fstype)
@@ -234,7 +135,7 @@ int do_mount(int dev_fd, int *cookie, const char *mntpath, const char *fstype)
                }
 
                if (ioctl(loop_fd, LOOP_GET_STATUS64, &loopinfo) ||
-                   (loopinfo.lo_offset = filesystem_offset,
+                   (loopinfo.lo_offset = opt.offset,
                     ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo)))
                    die("cannot set up loopback device");
            }
@@ -265,7 +166,7 @@ int do_mount(int dev_fd, int *cookie, const char *mntpath, const char *fstype)
            if (!S_ISBLK(st.st_mode)) {
                snprintf(mnt_opts, sizeof mnt_opts,
                         "rw,nodev,noexec,loop,offset=%llu,umask=077,quiet",
-                        (unsigned long long)filesystem_offset);
+                        (unsigned long long)opt.offset);
            } else {
                snprintf(mnt_opts, sizeof mnt_opts,
                         "rw,nodev,noexec,umask=077,quiet");
@@ -317,79 +218,71 @@ void do_umount(const char *mntpath, int cookie)
 #endif
 }
 
+/*
+ * Modify the ADV of an existing installation
+ */
+int modify_existing_adv(const char *path)
+{
+    if (opt.reset_adv)
+       syslinux_reset_adv(syslinux_adv);
+    else if (read_adv(path, "ldlinux.sys") < 0)
+       return 1;
+
+    if (modify_adv() < 0)
+       return 1;
+
+    if (write_adv(path, "ldlinux.sys") < 0)
+       return 1;
+
+    return 0;
+}
+
 int main(int argc, char *argv[])
 {
     static unsigned char sectbuf[SECTOR_SIZE];
-    unsigned char *dp;
-    const unsigned char *cdp;
     int dev_fd, fd;
     struct stat st;
-    int nb, left;
     int err = 0;
     char mntname[128];
-    char *ldlinux_name, **argp, *opt;
-    const char *subdir = NULL;
-    uint32_t *sectors;
-    int ldlinux_sectors;
-    int nsectors = 0;
+    char *ldlinux_name;
+    char *ldlinux_path;
+    char *subdir;
+    sector_t *sectors = NULL;
+    int ldlinux_sectors = (boot_image_len + SECTOR_SIZE - 1) >> SECTOR_SHIFT;
     const char *errmsg;
     int mnt_cookie;
     int patch_sectors;
     int i;
 
-    int force = 0;             /* -f (force) option */
-    int stupid = 0;            /* -s (stupid) option */
-    int raid_mode = 0;         /* -r (RAID) option */
-
-    (void)argc;                        /* Unused */
-
-    program = argv[0];
     mypid = getpid();
-
-    device = NULL;
-
     umask(077);
-
-    for (argp = argv + 1; *argp; argp++) {
-       if (**argp == '-') {
-           opt = *argp + 1;
-           if (!*opt)
-               usage();
-
-           while (*opt) {
-               if (*opt == 's') {
-                   stupid = 1;
-               } else if (*opt == 'r') {
-                   raid_mode = 1;
-               } else if (*opt == 'f') {
-                   force = 1;  /* Force install */
-               } else if (*opt == 'd' && argp[1]) {
-                   subdir = *++argp;
-               } else if (*opt == 'o' && argp[1]) {
-                   /* Byte offset */
-                   filesystem_offset = (off_t) strtoull(*++argp, NULL, 0);
-               } else {
-                   usage();
-               }
-               opt++;
-           }
-       } else {
-           if (device)
-               usage();
-           device = *argp;
+    parse_options(argc, argv, MODE_SYSLINUX);
+
+    /* Note: subdir is guaranteed to start and end in / */
+    if (opt.directory && opt.directory[0]) {
+       int len = strlen(opt.directory);
+       int rv = asprintf(&subdir, "%s%s%s",
+                         opt.directory[0] == '/' ? "" : "/",
+                         opt.directory,
+                         opt.directory[len-1] == '/' ? "" : "/");
+       if (rv < 0 || !subdir) {
+           perror(program);
+           exit(1);
        }
+    } else {
+       subdir = "/";
     }
 
-    if (!device)
-       usage();
+    if (!opt.device)
+       usage(EX_USAGE, MODE_SYSLINUX);
 
     /*
      * First make sure we can open the device at all, and that we have
      * read/write permission.
      */
-    dev_fd = open(device, O_RDWR);
+    dev_fd = open(opt.device, O_RDWR);
     if (dev_fd < 0 || fstat(dev_fd, &st) < 0) {
-       perror(device);
+       perror(opt.device);
        exit(1);
     }
 
@@ -397,18 +290,19 @@ int main(int argc, char *argv[])
        die("not a device or regular file");
     }
 
-    if (filesystem_offset && S_ISBLK(st.st_mode)) {
+    if (opt.offset && S_ISBLK(st.st_mode)) {
        die("can't combine an offset with a block device");
     }
 
-    xpread(dev_fd, sectbuf, SECTOR_SIZE, filesystem_offset);
+    fs_type = VFAT;
+    xpread(dev_fd, sectbuf, SECTOR_SIZE, opt.offset);
     fsync(dev_fd);
 
     /*
      * Check to see that what we got was indeed an MS-DOS boot sector/superblock
      */
     if ((errmsg = syslinux_check_bootsect(sectbuf))) {
-       fprintf(stderr, "%s: %s\n", device, errmsg);
+       fprintf(stderr, "%s: %s\n", opt.device, errmsg);
        exit(1);
     }
 
@@ -426,7 +320,8 @@ int main(int argc, char *argv[])
           Make a temp dir and pass all the gunky options to mount. */
 
        if (chdir(_PATH_TMP)) {
-           perror(program);
+           fprintf(stderr, "%s: Cannot access the %s directory.\n",
+                   program, _PATH_TMP);
            exit(1);
        }
 #define TMP_MODE (S_IXUSR|S_IWUSR|S_IXGRP|S_IWGRP|S_IWOTH|S_IXOTH|S_ISVTX)
@@ -468,15 +363,38 @@ int main(int argc, char *argv[])
        die("mount failed");
     }
 
-    ldlinux_name = alloca(strlen(mntpath) + 14 +
-                         (subdir ? strlen(subdir) + 2 : 0));
+    ldlinux_path = alloca(strlen(mntpath) + strlen(subdir) + 1);
+    sprintf(ldlinux_path, "%s%s", mntpath, subdir);
+
+    ldlinux_name = alloca(strlen(ldlinux_path) + 14);
     if (!ldlinux_name) {
        perror(program);
        err = 1;
        goto umount;
     }
-    sprintf(ldlinux_name, "%s%s%s//ldlinux.sys",
-           mntpath, subdir ? "//" : "", subdir ? subdir : "");
+    sprintf(ldlinux_name, "%sldlinux.sys", ldlinux_path);
+
+    /* update ADV only ? */
+    if (opt.update_only == -1) {
+       if (opt.reset_adv || opt.set_once) {
+           modify_existing_adv(ldlinux_path);
+           do_umount(mntpath, mnt_cookie);
+           sync();
+           rmdir(mntpath);
+           exit(0);
+       } else {
+           fprintf(stderr, "%s: please specify --install or --update for the future\n", argv[0]);
+           opt.update_only = 0;
+       }
+    }
+
+    /* Read a pre-existing ADV, if already installed */
+    if (opt.reset_adv)
+       syslinux_reset_adv(syslinux_adv);
+    else if (read_adv(ldlinux_path, "ldlinux.sys") < 0)
+       syslinux_reset_adv(syslinux_adv);
+    if (modify_adv() < 0)
+       exit(1);
 
     if ((fd = open(ldlinux_name, O_RDONLY)) >= 0) {
        uint32_t zero_attr = 0;
@@ -487,25 +405,17 @@ int main(int argc, char *argv[])
     unlink(ldlinux_name);
     fd = open(ldlinux_name, O_WRONLY | O_CREAT | O_TRUNC, 0444);
     if (fd < 0) {
-       perror(device);
+       perror(opt.device);
        err = 1;
        goto umount;
     }
 
-    cdp = syslinux_ldlinux;
-    left = syslinux_ldlinux_len;
-    while (left) {
-       nb = write(fd, cdp, left);
-       if (nb == -1 && errno == EINTR)
-           continue;
-       else if (nb <= 0) {
-           perror(device);
-           err = 1;
-           goto umount;
-       }
-
-       dp += nb;
-       left -= nb;
+    /* Write it the first time */
+    if (xpwrite(fd, boot_image, boot_image_len, 0) != (int)boot_image_len ||
+       xpwrite(fd, syslinux_adv, 2 * ADV_SIZE,
+               boot_image_len) != 2 * ADV_SIZE) {
+       fprintf(stderr, "%s: write failure on %s\n", program, ldlinux_name);
+       exit(1);
     }
 
     fsync(fd);
@@ -520,10 +430,12 @@ int main(int argc, char *argv[])
     /*
      * Create a block map.
      */
-    ldlinux_sectors = (syslinux_ldlinux_len + SECTOR_SIZE - 1) >> SECTOR_SHIFT;
+    ldlinux_sectors += 2; /* 2 ADV sectors */
     sectors = calloc(ldlinux_sectors, sizeof *sectors);
-    nsectors = make_block_map(sectors, syslinux_ldlinux_len, dev_fd, fd);
-
+    if (sectmap(fd, sectors, ldlinux_sectors)) {
+       perror("bmap");
+       exit(1);
+    }
     close(fd);
     sync();
 
@@ -538,15 +450,16 @@ umount:
     /*
      * Patch ldlinux.sys and the boot sector
      */
-    i = syslinux_patch(sectors, nsectors, stupid, raid_mode);
+    i = syslinux_patch(sectors, ldlinux_sectors, opt.stupid_mode,
+                      opt.raid_mode, subdir, NULL);
     patch_sectors = (i + SECTOR_SIZE - 1) >> SECTOR_SHIFT;
 
     /*
      * Write the now-patched first sectors of ldlinux.sys
      */
     for (i = 0; i < patch_sectors; i++) {
-       xpwrite(dev_fd, syslinux_ldlinux + i * SECTOR_SIZE, SECTOR_SIZE,
-               filesystem_offset + ((off_t) sectors[i] << SECTOR_SHIFT));
+       xpwrite(dev_fd, boot_image + i * SECTOR_SIZE, SECTOR_SIZE,
+               opt.offset + ((off_t) sectors[i] << SECTOR_SHIFT));
     }
 
     /*
@@ -554,13 +467,13 @@ umount:
      */
 
     /* Read the superblock again since it might have changed while mounted */
-    xpread(dev_fd, sectbuf, SECTOR_SIZE, filesystem_offset);
+    xpread(dev_fd, sectbuf, SECTOR_SIZE, opt.offset);
 
     /* Copy the syslinux code into the boot sector */
     syslinux_make_bootsect(sectbuf);
 
     /* Write new boot sector */
-    xpwrite(dev_fd, sectbuf, SECTOR_SIZE, filesystem_offset);
+    xpwrite(dev_fd, sectbuf, SECTOR_SIZE, opt.offset);
 
     close(dev_fd);
     sync();