Merge branch 'pathbased' of ssh://terminus.zytor.com/pub/git/syslinux/syslinux into...
[profile/ivi/syslinux.git] / extlinux / main.c
index 05b390f..6ce3b60 100644 (file)
@@ -1,7 +1,7 @@
 /* ----------------------------------------------------------------------- *
  *
  *   Copyright 1998-2008 H. Peter Anvin - All Rights Reserved
- *   Copyright 2009 Intel Corporation; author: H. Peter Anvin
+ *   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
@@ -14,7 +14,7 @@
 /*
  * extlinux.c
  *
- * Install the extlinux boot block on an ext2/3/4 and btrfs filesystem
+ * Install the extlinux boot block on an fat, ext2/3/4 and btrfs filesystem
  */
 
 #define  _GNU_SOURCE           /* Enable everything */
@@ -30,6 +30,7 @@ typedef uint64_t u64;
 #include <mntent.h>
 #endif
 #include <stdbool.h>
+#include <stddef.h>
 #include <stdlib.h>
 #include <string.h>
 #include <getopt.h>
@@ -40,16 +41,15 @@ typedef uint64_t u64;
 #include <sys/mount.h>
 #include <sys/vfs.h>
 
-#include <linux/fd.h>          /* Floppy geometry */
-#include <linux/hdreg.h>       /* Hard disk geometry */
-#define statfs _kernel_statfs  /* HACK to deal with broken 2.4 distros */
-#include <linux/fs.h>          /* FIGETBSZ, FIBMAP */
-#undef statfs
+#include "linuxioctl.h"
 
-#include "ext2_fs.h"
 #include "btrfs.h"
+#include "fat.h"
 #include "../version.h"
 #include "syslxint.h"
+#include "syslxcom.h" /* common functions shared with extlinux and syslinux */
+#include "setadv.h"
+#include "syslxopt.h" /* unified options */
 
 #ifdef DEBUG
 # define dprintf printf
@@ -57,76 +57,6 @@ typedef uint64_t u64;
 # define dprintf(...) ((void)0)
 #endif
 
-/* Global option handling */
-/* Global fs_type for handling ext2/3/4 vs btrfs */
-#define EXT2 1
-#define BTRFS 2
-int fs_type;
-
-const char *program;
-
-/* These are the options we can set and their values */
-struct my_options {
-    unsigned int sectors;
-    unsigned int heads;
-    int raid_mode;
-    int stupid_mode;
-    int reset_adv;
-    const char *set_once;
-} opt = {
-.sectors = 0,.heads = 0,.raid_mode = 0,.stupid_mode = 0,.reset_adv =
-       0,.set_once = NULL,};
-
-static void __attribute__ ((noreturn)) usage(int rv)
-{
-    fprintf(stderr,
-           "Usage: %s [options] directory\n"
-           "  --install    -i  Install over the current bootsector\n"
-           "  --update     -U  Update a previous EXTLINUX installation\n"
-           "  --zip        -z  Force zipdrive geometry (-H 64 -S 32)\n"
-           "  --sectors=#  -S  Force the number of sectors per track\n"
-           "  --heads=#    -H  Force number of heads\n"
-           "  --stupid     -s  Slow, safe and stupid mode\n"
-           "  --raid       -r  Fall back to the next device on boot failure\n"
-           "  --once=...   -o  Execute a command once upon boot\n"
-           "  --clear-once -O  Clear the boot-once command\n"
-           "  --reset-adv      Reset auxilliary data\n"
-           "\n"
-           "  Note: geometry is determined at boot time for devices which\n"
-           "  are considered hard disks by the BIOS.  Unfortunately, this is\n"
-           "  not possible for devices which are considered floppy disks,\n"
-           "  which includes zipdisks and LS-120 superfloppies.\n"
-           "\n"
-           "  The -z option is useful for USB devices which are considered\n"
-           "  hard disks by some BIOSes and zipdrives by other BIOSes.\n",
-           program);
-
-    exit(rv);
-}
-
-enum long_only_opt {
-    OPT_NONE,
-    OPT_RESET_ADV,
-};
-
-static const struct option long_options[] = {
-    {"install", 0, NULL, 'i'},
-    {"update", 0, NULL, 'U'},
-    {"zipdrive", 0, NULL, 'z'},
-    {"sectors", 1, NULL, 'S'},
-    {"stupid", 0, NULL, 's'},
-    {"heads", 1, NULL, 'H'},
-    {"raid-mode", 0, NULL, 'r'},
-    {"version", 0, NULL, 'v'},
-    {"help", 0, NULL, 'h'},
-    {"once", 1, NULL, 'o'},
-    {"clear-once", 0, NULL, 'O'},
-    {"reset-adv", 0, NULL, OPT_RESET_ADV},
-    {0, 0, 0, 0}
-};
-
-static const char short_options[] = "iUuzS:H:rvho:O";
-
 #if defined(__linux__) && !defined(BLKGETSIZE64)
 /* This takes a u64, but the size field says size_t.  Someone screwed big. */
 # define BLKGETSIZE64 _IOR(0x12,114,size_t)
@@ -139,11 +69,17 @@ static const char short_options[] = "iUuzS:H:rvho:O";
 /* the btrfs partition first 64K blank area is used to store boot sector and
    boot image, the boot sector is from 0~512, the boot image starts at 2K */
 #define BTRFS_EXTLINUX_OFFSET (2*1024)
+#define BTRFS_SUBVOL_OPT "subvol="
+#define BTRFS_SUBVOL_MAX 256   /* By btrfs specification */
+static char subvol[BTRFS_SUBVOL_MAX];
+
 /*
  * Boot block
  */
 extern unsigned char extlinux_bootsect[];
 extern unsigned int extlinux_bootsect_len;
+#undef  boot_block
+#undef  boot_block_len
 #define boot_block     extlinux_bootsect
 #define boot_block_len  extlinux_bootsect_len
 
@@ -152,111 +88,12 @@ extern unsigned int extlinux_bootsect_len;
  */
 extern unsigned char extlinux_image[];
 extern unsigned int extlinux_image_len;
+#undef  boot_image
+#undef  boot_image_len
 #define boot_image     extlinux_image
 #define boot_image_len  extlinux_image_len
 
-/*
- * Common abort function
- */
-void __attribute__ ((noreturn)) die(const char *msg)
-{
-    fputs(msg, stderr);
-    exit(1);
-}
-
-/*
- * 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;
-}
-
-/*
- * Produce file map
- */
-int sectmap(int fd, uint32_t * sectors, int nsectors)
-{
-    unsigned int blksize, blk, nblk;
-    unsigned int i;
-
-    /* Get block size */
-    if (ioctl(fd, FIGETBSZ, &blksize))
-       return -1;
-
-    /* Number of sectors per block */
-    blksize >>= SECTOR_SHIFT;
-
-    nblk = 0;
-    while (nsectors) {
-
-       blk = nblk++;
-       dprintf("querying block %u\n", blk);
-       if (ioctl(fd, FIBMAP, &blk))
-           return -1;
-
-       blk *= blksize;
-       for (i = 0; i < blksize; i++) {
-           if (!nsectors)
-               return 0;
-
-           dprintf("Sector: %10u\n", blk);
-           *sectors++ = blk++;
-           nsectors--;
-       }
-    }
-
-    return 0;
-}
+#define BTRFS_ADV_OFFSET (BTRFS_EXTLINUX_OFFSET + boot_image_len)
 
 /*
  * Get the size of a block device
@@ -348,6 +185,53 @@ int get_geometry(int devfd, uint64_t totalbytes, struct hd_geometry *geo)
 }
 
 /*
+ * Generate sector extents
+ */
+static void generate_extents(struct syslinux_extent *ex, int nptrs,
+                            const sector_t *sectp, int nsect)
+{
+    uint32_t addr = 0x7c00 + 2*SECTOR_SIZE;
+    uint32_t base;
+    sector_t sect, lba;
+    unsigned int len;
+
+    len = lba = base = 0;
+
+    memset(ex, 0, nptrs * sizeof *ex);
+
+    while (nsect) {
+       sect = *sectp++;
+
+       if (len && sect == lba + len &&
+           ((addr ^ (base + len * SECTOR_SIZE)) & 0xffff0000) == 0) {
+           /* We can add to the current extent */
+           len++;
+           goto next;
+       }
+
+       if (len) {
+           set_64(&ex->lba, lba);
+           set_16(&ex->len, len);
+           ex++;
+       }
+
+       base = addr;
+       lba  = sect;
+       len  = 1;
+
+    next:
+       addr += SECTOR_SIZE;
+       nsect--;
+    }
+
+    if (len) {
+       set_64(&ex->lba, lba);
+       set_16(&ex->len, len);
+       ex++;
+    }
+}
+
+/*
  * Query the device geometry and put it into the boot sector.
  * Map the file and put the map in the boot sector and file.
  * Stick the "current directory" inode number into the file.
@@ -358,16 +242,18 @@ int patch_file_and_bootblock(int fd, const char *dir, int devfd)
 {
     struct stat dirst, xdst;
     struct hd_geometry geo;
-    uint32_t *sectp;
+    sector_t *sectp;
     uint64_t totalbytes, totalsectors;
     int nsect;
     uint32_t *wp;
     struct boot_sector *bs;
     struct patch_area *patcharea;
+    struct syslinux_extent *ex;
     int i, dw, nptrs;
     uint32_t csum;
-    int secptroffset, diroffset, dirlen;
-    char *dirpath, *subpath;
+    int secptroffset, diroffset, dirlen, subvoloffset, subvollen;
+    char *dirpath, *subpath, *xdirpath, *xsubpath;
+    uint64_t *advptrs;
 
     dirpath = realpath(dir, NULL);
     if (!dirpath || stat(dir, &dirst)) {
@@ -383,10 +269,17 @@ int patch_file_and_bootblock(int fd, const char *dir, int devfd)
     }
 
     subpath = strchr(dirpath, '\0');
-    while (--subpath > dirpath) {
+    for (;;) {
        if (*subpath == '/') {
-           *subpath = '\0';
-           if (lstat(dirpath, &xdst) || dirst.st_dev != xdst.st_dev) {
+           if (subpath > dirpath) {
+               *subpath = '\0';
+               xsubpath = subpath+1;
+               xdirpath = dirpath;
+           } else {
+               xsubpath = subpath;
+               xdirpath = "/";
+           }
+           if (lstat(xdirpath, &xdst) || dirst.st_dev != xdst.st_dev) {
                subpath = strchr(subpath+1, '/');
                if (!subpath)
                    subpath = "/"; /* It's the root of the filesystem */
@@ -394,6 +287,11 @@ int patch_file_and_bootblock(int fd, const char *dir, int devfd)
            }
            *subpath = '/';
        }
+
+       if (subpath == dirpath)
+           break;
+
+       subpath--;
     }
 
     /* Now subpath should contain the path relative to the fs base */
@@ -440,8 +338,8 @@ int patch_file_and_bootblock(int fd, const char *dir, int devfd)
     dprintf("directory inode = %lu\n", (unsigned long)dirst.st_ino);
     nsect = (boot_image_len + SECTOR_SIZE - 1) >> SECTOR_SHIFT;
     nsect += 2;                        /* Two sectors for the ADV */
-    sectp = alloca(sizeof(uint32_t) * nsect);
-    if (fs_type == EXT2) {
+    sectp = alloca(sizeof(sector_t) * nsect);
+    if (fs_type == EXT2 || fs_type == VFAT) {
        if (sectmap(fd, sectp, nsect)) {
                perror("bmap");
                exit(1);
@@ -454,10 +352,7 @@ int patch_file_and_bootblock(int fd, const char *dir, int devfd)
     }
 
     /* First sector need pointer in boot sector */
-    set_32(&bs->NextSector, *sectp++);
-    /* Stupid mode? */
-    if (opt.stupid_mode)
-       set_16(&bs->MaxTransfer, 1);
+    set_64(&bs->NextSector, *sectp++);
 
     /* Search for LDLINUX_MAGIC to find the patch area */
     for (wp = (uint32_t *) boot_image; get_32(wp) != LDLINUX_MAGIC; wp++) ;
@@ -469,14 +364,29 @@ int patch_file_and_bootblock(int fd, const char *dir, int devfd)
     set_16(&patcharea->adv_sectors, 2);
     set_32(&patcharea->dwords, dw);
 
-    /* Set the sector pointers */
+    /* Stupid mode? */
+    if (opt.stupid_mode)
+       set_16(&patcharea->maxtransfer, 1);
+
+    /* Set the sector extents */
     secptroffset = get_16(&patcharea->secptroffset);
-    wp = (uint32_t *) ((char *)boot_image + secptroffset);
+    ex = (struct syslinux_extent *) ((char *)boot_image + secptroffset);
     nptrs = get_16(&patcharea->secptrcnt);
 
-    memset(wp, 0, nptrs * 4);
-    while (nsect--)
-       set_32(wp++, *sectp++);
+    if (nsect > nptrs) {
+       /* Not necessarily an error in this case, but a general problem */
+       fprintf(stderr, "Insufficient extent space, build error!\n");
+       exit(1);
+    }
+
+    /* -1 for the pointer in the boot sector, -2 for the two ADVs */
+    generate_extents(ex, nptrs, sectp, nsect-1-2);
+
+    /* ADV pointers */
+    advptrs = (uint64_t *)((char *)boot_image +
+                          get_16(&patcharea->advptroffset));
+    set_64(&advptrs[0], sectp[nsect-1-2]);
+    set_64(&advptrs[1], sectp[nsect-1-1]);
 
     /* Poke in the base directory path */
     diroffset = get_16(&patcharea->diroffset);
@@ -488,6 +398,15 @@ int patch_file_and_bootblock(int fd, const char *dir, int devfd)
     strncpy((char *)boot_image + diroffset, subpath, dirlen);
     free(dirpath);
 
+    /* write subvol info if we have */
+    subvoloffset = get_16(&patcharea->subvoloffset);
+    subvollen = get_16(&patcharea->subvollen);
+    if (subvollen <= strlen(subvol)) {
+       fprintf(stderr, "Subvol name too long... aborting install!\n");
+       exit(1);
+    }
+    strncpy((char *)boot_image + subvoloffset, subvol, subvollen);
+
     /* Now produce a checksum */
     set_32(&patcharea->checksum, 0);
 
@@ -497,159 +416,11 @@ int patch_file_and_bootblock(int fd, const char *dir, int devfd)
 
     set_32(&patcharea->checksum, csum);
 
-    return secptroffset + nptrs*4;
-}
-
-/*
- * Read the ADV from an existing instance, or initialize if invalid.
- * Returns -1 on fatal errors, 0 if ADV is okay, and 1 if no valid
- * ADV was found.
- */
-int read_adv(const char *path, int devfd)
-{
-    char *file;
-    int fd = -1;
-    struct stat st;
-    int err = 0;
-
-    if (fs_type == BTRFS) { /* btrfs "extlinux.sys" is in 64k blank area */
-       if (xpread(devfd, syslinux_adv, 2 * ADV_SIZE,
-               BTRFS_EXTLINUX_OFFSET + boot_image_len) != 2 * ADV_SIZE) {
-               perror("writing adv");
-               return 1;
-       }
-       return 0;
-    }
-    asprintf(&file, "%s%sextlinux.sys",
-            path, path[0] && path[strlen(path) - 1] == '/' ? "" : "/");
-
-    if (!file) {
-       perror(program);
-       return -1;
-    }
-
-    fd = open(file, O_RDONLY);
-    if (fd < 0) {
-       if (errno != ENOENT) {
-           err = -1;
-       } else {
-           syslinux_reset_adv(syslinux_adv);
-       }
-    } else if (fstat(fd, &st)) {
-       err = -1;
-    } else if (st.st_size < 2 * ADV_SIZE) {
-       /* Too small to be useful */
-       syslinux_reset_adv(syslinux_adv);
-       err = 0;                /* Nothing to read... */
-    } else if (xpread(fd, syslinux_adv, 2 * ADV_SIZE,
-                     st.st_size - 2 * ADV_SIZE) != 2 * ADV_SIZE) {
-       err = -1;
-    } else {
-       /* We got it... maybe? */
-       err = syslinux_validate_adv(syslinux_adv) ? 1 : 0;
-    }
-
-    if (err < 0)
-       perror(file);
-
-    if (fd >= 0)
-       close(fd);
-    if (file)
-       free(file);
-
-    return err;
-}
-
-/*
- * Update the ADV in an existing installation.
- */
-int write_adv(const char *path, int devfd)
-{
-    unsigned char advtmp[2 * ADV_SIZE];
-    char *file;
-    int fd = -1;
-    struct stat st, xst;
-    int err = 0;
-    int flags, nflags;
-
-    if (fs_type == BTRFS) { /* btrfs "extlinux.sys" is in 64k blank area */
-       if (xpwrite(devfd, syslinux_adv, 2 * ADV_SIZE,
-               BTRFS_EXTLINUX_OFFSET + boot_image_len) != 2 * ADV_SIZE) {
-               perror("writing adv");
-               return 1;
-       }
-       return 0;
-    }
-    asprintf(&file, "%s%sextlinux.sys",
-            path, path[0] && path[strlen(path) - 1] == '/' ? "" : "/");
-
-    if (!file) {
-       perror(program);
-       return -1;
-    }
-
-    fd = open(file, O_RDONLY);
-    if (fd < 0) {
-       err = -1;
-    } else if (fstat(fd, &st)) {
-       err = -1;
-    } else if (st.st_size < 2 * ADV_SIZE) {
-       /* Too small to be useful */
-       err = -2;
-    } else if (xpread(fd, advtmp, 2 * ADV_SIZE,
-                     st.st_size - 2 * ADV_SIZE) != 2 * ADV_SIZE) {
-       err = -1;
-    } else {
-       /* We got it... maybe? */
-       err = syslinux_validate_adv(advtmp) ? -2 : 0;
-       if (!err) {
-           /* Got a good one, write our own ADV here */
-           if (!ioctl(fd, EXT2_IOC_GETFLAGS, &flags)) {
-               nflags = flags & ~EXT2_IMMUTABLE_FL;
-               if (nflags != flags)
-                   ioctl(fd, EXT2_IOC_SETFLAGS, &nflags);
-           }
-           if (!(st.st_mode & S_IWUSR))
-               fchmod(fd, st.st_mode | S_IWUSR);
-
-           /* Need to re-open read-write */
-           close(fd);
-           fd = open(file, O_RDWR | O_SYNC);
-           if (fd < 0) {
-               err = -1;
-           } else if (fstat(fd, &xst) || xst.st_ino != st.st_ino ||
-                      xst.st_dev != st.st_dev || xst.st_size != st.st_size) {
-               fprintf(stderr, "%s: race condition on write\n", file);
-               err = -2;
-           }
-           /* Write our own version ... */
-           if (xpwrite(fd, syslinux_adv, 2 * ADV_SIZE,
-                       st.st_size - 2 * ADV_SIZE) != 2 * ADV_SIZE) {
-               err = -1;
-           }
-
-           sync();
-
-           if (!(st.st_mode & S_IWUSR))
-               fchmod(fd, st.st_mode);
-
-           if (nflags != flags)
-               ioctl(fd, EXT2_IOC_SETFLAGS, &flags);
-       }
-    }
-
-    if (err == -2)
-       fprintf(stderr, "%s: cannot write auxilliary data (need --update)?\n",
-               file);
-    else if (err == -1)
-       perror(file);
-
-    if (fd >= 0)
-       close(fd);
-    if (file)
-       free(file);
-
-    return err;
+    /*
+     * Assume all bytes modified.  This can be optimized at the expense
+     * of keeping track of what the highest modified address ever was.
+     */
+    return dw << 2;
 }
 
 /*
@@ -666,6 +437,13 @@ int modify_adv(void)
            rv = -1;
        }
     }
+    if (opt.menu_save) {
+        if (syslinux_setadv(ADV_MENUSAVE, strlen(opt.menu_save), opt.menu_save)) {
+           fprintf(stderr, "%s: not enough space for menu-save label\n",
+                   program);
+           rv = -1;
+        }
+    }
 
     return rv;
 }
@@ -678,6 +456,7 @@ int install_bootblock(int fd, const char *device)
 {
     struct ext2_super_block sb;
     struct btrfs_super_block sb2;
+    struct boot_sector sb3;
     bool ok = false;
 
     if (fs_type == EXT2) {
@@ -695,25 +474,43 @@ int install_bootblock(int fd, const char *device)
        }
        if (sb2.magic == *(u64 *)BTRFS_MAGIC)
                ok = true;
+    } else if (fs_type == VFAT) {
+       if (xpread(fd, &sb3, sizeof sb3, 0) != sizeof sb3) {
+               perror("reading fat superblock");
+               return 1;
+       }
+       if (sb3.bsResSectors && sb3.bsFATs &&
+           (strstr(sb3.bs16.FileSysType, "FAT") ||
+            strstr(sb3.bs32.FileSysType, "FAT")))
+               ok = true;
     }
     if (!ok) {
-       fprintf(stderr, "no ext2/3/4 or btrfs superblock found on %s\n",
+       fprintf(stderr, "no fat, ext2/3/4 or btrfs superblock found on %s\n",
                        device);
        return 1;
     }
-    if (xpwrite(fd, boot_block, boot_block_len, 0) != boot_block_len) {
-       perror("writing bootblock");
-       return 1;
+    if (fs_type == VFAT) {
+       struct boot_sector *bs = (struct boot_sector *)extlinux_bootsect;
+        if (xpwrite(fd, &bs->bsHead, bsHeadLen, 0) != bsHeadLen ||
+           xpwrite(fd, &bs->bsCode, bsCodeLen,
+                   offsetof(struct boot_sector, bsCode)) != bsCodeLen) {
+           perror("writing fat bootblock");
+           return 1;
+       }
+    } else {
+       if (xpwrite(fd, boot_block, boot_block_len, 0) != boot_block_len) {
+           perror("writing bootblock");
+           return 1;
+       }
     }
 
     return 0;
 }
 
-int ext2_install_file(const char *path, int devfd, struct stat *rst)
+int ext2_fat_install_file(const char *path, int devfd, struct stat *rst)
 {
     char *file;
-    int fd = -1, dirfd = -1, flags;
-    struct stat st;
+    int fd = -1, dirfd = -1;
     int modbytes;
 
     asprintf(&file, "%s%sextlinux.sys",
@@ -736,14 +533,7 @@ int ext2_install_file(const char *path, int devfd, struct stat *rst)
            goto bail;
        }
     } else {
-       /* If file exist, remove the immutable flag and set u+w mode */
-       if (!ioctl(fd, EXT2_IOC_GETFLAGS, &flags)) {
-           flags &= ~EXT2_IMMUTABLE_FL;
-           ioctl(fd, EXT2_IOC_SETFLAGS, &flags);
-       }
-       if (!fstat(fd, &st)) {
-           fchmod(fd, st.st_mode | S_IWUSR);
-       }
+       clear_attributes(fd);
     }
     close(fd);
 
@@ -774,13 +564,7 @@ int ext2_install_file(const char *path, int devfd, struct stat *rst)
 
     /* Attempt to set immutable flag and remove all write access */
     /* Only set immutable flag if file is owned by root */
-    if (!fstat(fd, &st)) {
-       fchmod(fd, st.st_mode & (S_IRUSR | S_IRGRP | S_IROTH));
-       if (st.st_uid == 0 && !ioctl(fd, EXT2_IOC_GETFLAGS, &flags)) {
-           flags |= EXT2_IMMUTABLE_FL;
-           ioctl(fd, EXT2_IOC_SETFLAGS, &flags);
-       }
-    }
+    set_attributes(fd);
 
     if (fstat(fd, rst)) {
        perror(file);
@@ -827,8 +611,8 @@ int btrfs_install_file(const char *path, int devfd, struct stat *rst)
 
 int install_file(const char *path, int devfd, struct stat *rst)
 {
-       if (fs_type == EXT2)
-               return ext2_install_file(path, devfd, rst);
+       if (fs_type == EXT2 || fs_type == VFAT)
+               return ext2_fat_install_file(path, devfd, rst);
        else if (fs_type == BTRFS)
                return btrfs_install_file(path, devfd, rst);
        return 1;
@@ -888,8 +672,22 @@ static const char *find_device(const char *mtab_file, dev_t dev)
        case BTRFS:
                if (!strcmp(mnt->mnt_type, "btrfs") &&
                    !stat(mnt->mnt_dir, &dst) &&
-                   dst.st_dev == dev)
-                   done = true;
+                   dst.st_dev == dev) {
+                   char *opt = strstr(mnt->mnt_opts, BTRFS_SUBVOL_OPT);
+
+                   if (opt) {
+                       if (!subvol[0]) {
+                           char *tmp;
+
+                           strcpy(subvol, opt + sizeof(BTRFS_SUBVOL_OPT) - 1);
+                           tmp = strchr(subvol, 32);
+                           if (tmp)
+                               *tmp = '\0';
+                       }
+                       break; /* should break and let upper layer try again */
+                   } else
+                       done = true;
+               }
                break;
        case EXT2:
                if ((!strcmp(mnt->mnt_type, "ext2") ||
@@ -900,6 +698,15 @@ static const char *find_device(const char *mtab_file, dev_t dev)
                    done = true;
                    break;
                }
+       case VFAT:
+               if ((!strcmp(mnt->mnt_type, "vfat")) &&
+                   !stat(mnt->mnt_fsname, &dst) &&
+                   dst.st_rdev == dev) {
+                   done = true;
+                   break;
+               }
+       case NONE:
+           break;
        }
        if (done) {
                devname = strdup(mnt->mnt_fsname);
@@ -943,10 +750,24 @@ static const char *get_devname(const char *path)
 
 #else
 
-    devname = find_device("/proc/mounts", st.st_dev);
+    /* check /etc/mtab first, since btrfs subvol info is only in here */
+    devname = find_device("/etc/mtab", st.st_dev);
+    if (subvol[0] && !devname) { /* we just find it is a btrfs subvol */
+       char parent[256];
+       char *tmp;
+
+       strcpy(parent, path);
+       tmp = strrchr(parent, '/');
+       if (tmp) {
+           *tmp = '\0';
+           fprintf(stderr, "%s is subvol, try its parent dir %s\n", path, parent);
+           devname = get_devname(parent);
+       } else
+           devname = NULL;
+    }
     if (!devname) {
-       /* Didn't find it in /proc/mounts, try /etc/mtab */
-       devname = find_device("/etc/mtab", st.st_dev);
+       /* Didn't find it in /etc/mtab, try /proc/mounts */
+       devname = find_device("/proc/mounts", st.st_dev);
     }
     if (!devname) {
        fprintf(stderr, "%s: cannot find device for path %s\n", program, path);
@@ -978,9 +799,11 @@ static int open_device(const char *path, struct stat *st, const char **_devname)
        fs_type = EXT2;
     else if (sfs.f_type == BTRFS_SUPER_MAGIC)
        fs_type = BTRFS;
+    else if (sfs.f_type == MSDOS_SUPER_MAGIC)
+       fs_type = VFAT;
 
     if (!fs_type) {
-       fprintf(stderr, "%s: not an ext2/3/4 or btrfs filesystem: %s\n",
+       fprintf(stderr, "%s: not a fat, ext2/3/4 or btrfs filesystem: %s\n",
                program, path);
        return -1;
     }
@@ -1005,6 +828,32 @@ static int open_device(const char *path, struct stat *st, const char **_devname)
     return devfd;
 }
 
+static int ext_read_adv(const char *path, const char *cfg, int devfd)
+{
+    if (fs_type == BTRFS) { /* btrfs "extlinux.sys" is in 64k blank area */
+       if (xpread(devfd, syslinux_adv, 2 * ADV_SIZE,
+               BTRFS_ADV_OFFSET) != 2 * ADV_SIZE) {
+               perror("btrfs writing adv");
+               return 1;
+       }
+       return 0;
+    }
+    return read_adv(path, cfg);
+}
+
+static int ext_write_adv(const char *path, const char *cfg, int devfd)
+{
+    if (fs_type == BTRFS) { /* btrfs "extlinux.sys" is in 64k blank area */
+       if (xpwrite(devfd, syslinux_adv, 2 * ADV_SIZE,
+               BTRFS_ADV_OFFSET) != 2 * ADV_SIZE) {
+               perror("writing adv");
+               return 1;
+       }
+       return 0;
+    }
+    return write_adv(path, cfg);
+}
+
 int install_loader(const char *path, int update_only)
 {
     struct stat st, fst;
@@ -1025,7 +874,7 @@ int install_loader(const char *path, int update_only)
     /* Read a pre-existing ADV, if already installed */
     if (opt.reset_adv)
        syslinux_reset_adv(syslinux_adv);
-    else if (read_adv(path, devfd) < 0) {
+    else if (ext_read_adv(path, "extlinux.sys", devfd) < 0) {
        close(devfd);
        return 1;
     }
@@ -1067,7 +916,7 @@ int modify_existing_adv(const char *path)
 
     if (opt.reset_adv)
        syslinux_reset_adv(syslinux_adv);
-    else if (read_adv(path, devfd) < 0) {
+    else if (ext_read_adv(path, "extlinux.sys", devfd) < 0) {
        close(devfd);
        return 1;
     }
@@ -1075,7 +924,7 @@ int modify_existing_adv(const char *path)
        close(devfd);
        return 1;
     }
-    if (write_adv(path, devfd) < 0) {
+    if (ext_write_adv(path, "extlinux.sys", devfd) < 0) {
        close(devfd);
        return 1;
     }
@@ -1085,83 +934,17 @@ int modify_existing_adv(const char *path)
 
 int main(int argc, char *argv[])
 {
-    int o;
-    const char *directory;
-    int update_only = -1;
-
-    program = argv[0];
-
-    while ((o = getopt_long(argc, argv, short_options,
-                           long_options, NULL)) != EOF) {
-       switch (o) {
-       case 'z':
-           opt.heads = 64;
-           opt.sectors = 32;
-           break;
-       case 'S':
-           opt.sectors = strtoul(optarg, NULL, 0);
-           if (opt.sectors < 1 || opt.sectors > 63) {
-               fprintf(stderr,
-                       "%s: invalid number of sectors: %u (must be 1-63)\n",
-                       program, opt.sectors);
-               exit(EX_USAGE);
-           }
-           break;
-       case 'H':
-           opt.heads = strtoul(optarg, NULL, 0);
-           if (opt.heads < 1 || opt.heads > 256) {
-               fprintf(stderr,
-                       "%s: invalid number of heads: %u (must be 1-256)\n",
-                       program, opt.heads);
-               exit(EX_USAGE);
-           }
-           break;
-       case 'r':
-           opt.raid_mode = 1;
-           break;
-       case 's':
-           opt.stupid_mode = 1;
-           break;
-       case 'i':
-           update_only = 0;
-           break;
-       case 'u':
-       case 'U':
-           update_only = 1;
-           break;
-       case 'h':
-           usage(0);
-           break;
-       case 'o':
-           opt.set_once = optarg;
-           break;
-       case 'O':
-           opt.set_once = "";
-           break;
-       case OPT_RESET_ADV:
-           opt.reset_adv = 1;
-           break;
-       case 'v':
-           fputs("extlinux " VERSION_STR
-                 "  Copyright 1994-" YEAR_STR " H. Peter Anvin \n", stderr);
-           exit(0);
-       default:
-           usage(EX_USAGE);
-       }
-    }
-
-    directory = argv[optind];
+    parse_options(argc, argv, MODE_EXTLINUX);
 
-    if (!directory)
-       usage(EX_USAGE);
+    if (!opt.directory)
+       usage(EX_USAGE, 0);
 
-    if (update_only == -1) {
-       if (opt.reset_adv || opt.set_once) {
-           return modify_existing_adv(directory);
-       } else {
-           usage(EX_USAGE);
-       }
+    if (opt.update_only == -1) {
+       if (opt.reset_adv || opt.set_once || opt.menu_save)
+           return modify_existing_adv(opt.directory);
+       else
+           usage(EX_USAGE, MODE_EXTLINUX);
     }
 
-    return install_loader(directory, update_only);
+    return install_loader(opt.directory, opt.update_only);
 }