/* ----------------------------------------------------------------------- *
 *
 *   Copyright 1998-2008 H. Peter Anvin - All Rights Reserved
 *   Copyright 2009-2014 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
 *   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
 *   Boston MA 02111-1307, USA; either version 2 of the License, or
 *   (at your option) any later version; incorporated herein by reference.
 *
 * ----------------------------------------------------------------------- */

/*
 * extlinux.c
 *
 * Install the syslinux boot block on a fat, ntfs, ext2/3/4, btrfs, xfs,
 * and ufs1/2 filesystem.
 */

#define  _GNU_SOURCE		/* Enable everything */
#include <inttypes.h>
/* This is needed to deal with the kernel headers imported into glibc 3.3.3. */
#include <alloca.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <dirent.h>
#ifndef __KLIBC__
#include <mntent.h>
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <sysexits.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/vfs.h>

#include "linuxioctl.h"

#include "btrfs.h"
#include "fat.h"
#include "ntfs.h"
#include "xfs.h"
#include "xfs_types.h"
#include "xfs_sb.h"
#include "ufs.h"
#include "ufs_fs.h"
#include "misc.h"
#include "version.h"
#include "syslxint.h"
#include "syslxcom.h" /* common functions shared with extlinux and syslinux */
#include "syslxrw.h"
#include "syslxfs.h"
#include "setadv.h"
#include "syslxopt.h" /* unified options */
#include "mountinfo.h"

#ifdef DEBUG
# define dprintf printf
#else
# define dprintf(...) ((void)0)
#endif

#ifndef EXT2_SUPER_OFFSET
#define EXT2_SUPER_OFFSET 1024
#endif

/* Since we have unused 2048 bytes in the primary AG of an XFS partition,
 * we will use the first 0~512 bytes starting from 2048 for the Syslinux
 * boot sector.
 */
#define XFS_BOOTSECT_OFFSET	(4 << SECTOR_SHIFT)
#define XFS_SUPPORTED_BLOCKSIZE 4096 /* 4 KiB filesystem block size */

/*
 * btrfs has two discontiguous areas reserved for the boot loader.
 * Use the first one (Boot Area A) for the boot sector and the ADV,
 * and the second one for "ldlinux.sys".
 */
#define BTRFS_EXTLINUX_OFFSET	BTRFS_BOOT_AREA_B_OFFSET
#define BTRFS_EXTLINUX_SIZE	BTRFS_BOOT_AREA_B_SIZE
#define BTRFS_SUBVOL_MAX 256	/* By btrfs specification */
static char subvol[BTRFS_SUBVOL_MAX];

#define BTRFS_ADV_OFFSET (BTRFS_BOOT_AREA_A_OFFSET + BTRFS_BOOT_AREA_A_SIZE \
			  - 2*ADV_SIZE)

/*
 * Get the size of a block device
 */
static uint64_t get_size(int devfd)
{
    uint64_t bytes;
    uint32_t sects;
    struct stat st;

#ifdef BLKGETSIZE64
    if (!ioctl(devfd, BLKGETSIZE64, &bytes))
	return bytes;
#endif
    if (!ioctl(devfd, BLKGETSIZE, &sects))
	return (uint64_t) sects << 9;
    else if (!fstat(devfd, &st) && st.st_size)
	return st.st_size;
    else
	return 0;
}

/*
 * Get device geometry and partition offset
 */
struct geometry_table {
    uint64_t bytes;
    struct hd_geometry g;
};

static int sysfs_get_offset(int devfd, unsigned long *start)
{
    struct stat st;
    char sysfs_name[128];
    FILE *f;
    int rv;

    if (fstat(devfd, &st))
	return -1;

    if ((size_t)snprintf(sysfs_name, sizeof sysfs_name,
			 "/sys/dev/block/%u:%u/start",
			 major(st.st_rdev), minor(st.st_rdev))
	>= sizeof sysfs_name)
	return -1;

    f = fopen(sysfs_name, "r");
    if (!f)
	return -1;

    rv = fscanf(f, "%lu", start);
    fclose(f);

    return (rv == 1) ? 0 : -1;
}

/* Standard floppy disk geometries, plus LS-120.  Zipdisk geometry
   (x/64/32) is the final fallback.  I don't know what LS-240 has
   as its geometry, since I don't have one and don't know anyone that does,
   and Google wasn't helpful... */
static const struct geometry_table standard_geometries[] = {
    {360 * 1024, {2, 9, 40, 0}},
    {720 * 1024, {2, 9, 80, 0}},
    {1200 * 1024, {2, 15, 80, 0}},
    {1440 * 1024, {2, 18, 80, 0}},
    {1680 * 1024, {2, 21, 80, 0}},
    {1722 * 1024, {2, 21, 80, 0}},
    {2880 * 1024, {2, 36, 80, 0}},
    {3840 * 1024, {2, 48, 80, 0}},
    {123264 * 1024, {8, 32, 963, 0}},	/* LS120 */
    {0, {0, 0, 0, 0}}
};

int get_geometry(int devfd, uint64_t totalbytes, struct hd_geometry *geo)
{
    struct floppy_struct fd_str;
    struct loop_info li;
    struct loop_info64 li64;
    const struct geometry_table *gp;
    int rv = 0;

    memset(geo, 0, sizeof *geo);

    if (!ioctl(devfd, HDIO_GETGEO, geo)) {
	goto ok;
    } else if (!ioctl(devfd, FDGETPRM, &fd_str)) {
	geo->heads = fd_str.head;
	geo->sectors = fd_str.sect;
	geo->cylinders = fd_str.track;
	geo->start = 0;
	goto ok;
    }

    /* Didn't work.  Let's see if this is one of the standard geometries */
    for (gp = standard_geometries; gp->bytes; gp++) {
	if (gp->bytes == totalbytes) {
	    memcpy(geo, &gp->g, sizeof *geo);
	    goto ok;
	}
    }

    /* Didn't work either... assign a geometry of 64 heads, 32 sectors; this is
       what zipdisks use, so this would help if someone has a USB key that
       they're booting in USB-ZIP mode. */

    geo->heads = opt.heads ? : 64;
    geo->sectors = opt.sectors ? : 32;
    geo->cylinders = totalbytes / (geo->heads * geo->sectors << SECTOR_SHIFT);
    geo->start = 0;

    if (!opt.sectors && !opt.heads) {
	fprintf(stderr,
		"Warning: unable to obtain device geometry (defaulting to %d heads, %d sectors)\n"
		"         (on hard disks, this is usually harmless.)\n",
		geo->heads, geo->sectors);
	rv = 1;			/* Suboptimal result */
    }

ok:
    /* If this is a loopback device, try to set the start */
    if (!ioctl(devfd, LOOP_GET_STATUS64, &li64))
	geo->start = li64.lo_offset >> SECTOR_SHIFT;
    else if (!ioctl(devfd, LOOP_GET_STATUS, &li))
	geo->start = (unsigned int)li.lo_offset >> SECTOR_SHIFT;
    else if (!sysfs_get_offset(devfd, &geo->start)) {
	/* OK */
    }

    return rv;
}

/*
 * 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.
 *
 * Returns the number of modified bytes in the boot file.
 */
static int patch_file_and_bootblock(int fd, const char *dir, int devfd)
{
    struct stat dirst, xdst;
    struct hd_geometry geo;
    sector_t *sectp;
    uint64_t totalbytes, totalsectors;
    int nsect;
    struct fat_boot_sector *sbs;
    char *dirpath, *subpath, *xdirpath;
    int rv;

    dirpath = realpath(dir, NULL);
    if (!dirpath || stat(dir, &dirst)) {
	perror("accessing install directory");
	exit(255);		/* This should never happen */
    }

    if (lstat(dirpath, &xdst) ||
	dirst.st_ino != xdst.st_ino ||
	dirst.st_dev != xdst.st_dev) {
	perror("realpath returned nonsense");
	exit(255);
    }

    subpath = strchr(dirpath, '\0');
    for (;;) {
	if (*subpath == '/') {
	    if (subpath > dirpath) {
		*subpath = '\0';
		xdirpath = dirpath;
	    } else {
		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 */
		break;
	    }
	    *subpath = '/';
	}

	if (subpath == dirpath)
	    break;

	subpath--;
    }

    /* Now subpath should contain the path relative to the fs base */
    dprintf("subpath = %s\n", subpath);

    totalbytes = get_size(devfd);
    get_geometry(devfd, totalbytes, &geo);

    if (opt.heads)
	geo.heads = opt.heads;
    if (opt.sectors)
	geo.sectors = opt.sectors;

    /* Patch this into a fake FAT superblock.  This isn't because
       FAT is a good format in any way, it's because it lets the
       early bootstrap share code with the FAT version. */
    dprintf("heads = %u, sect = %u\n", geo.heads, geo.sectors);

    sbs = (struct fat_boot_sector *)syslinux_bootsect;

    totalsectors = totalbytes >> SECTOR_SHIFT;
    if (totalsectors >= 65536) {
	set_16(&sbs->bsSectors, 0);
    } else {
	set_16(&sbs->bsSectors, totalsectors);
    }
    set_32(&sbs->bsHugeSectors, totalsectors);

    set_16(&sbs->bsBytesPerSec, SECTOR_SIZE);
    set_16(&sbs->bsSecPerTrack, geo.sectors);
    set_16(&sbs->bsHeads, geo.heads);
    set_32(&sbs->bsHiddenSecs, geo.start);

    /* Construct the boot file map */

    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(sector_t) * nsect);
    if (fs_type == EXT2 || fs_type == VFAT || fs_type == NTFS ||
	fs_type == XFS || fs_type == UFS1 || fs_type == UFS2) {
	if (sectmap(fd, sectp, nsect)) {
		perror("bmap");
		exit(1);
	}
    } else if (fs_type == BTRFS) {
	int i;
	sector_t *sp = sectp;

	for (i = 0; i < nsect - 2; i++)
	    *sp++ = BTRFS_EXTLINUX_OFFSET/SECTOR_SIZE + i;
	for (i = 0; i < 2; i++)
	    *sp++ = BTRFS_ADV_OFFSET/SECTOR_SIZE + i;
    }

    /* Create the modified image in memory */
    rv = syslinux_patch(sectp, nsect, opt.stupid_mode,
			opt.raid_mode, subpath, subvol);

    free(dirpath);
    return rv;
}

static int ext_read_adv_offset(int devfd, off_t offset)
{
    const size_t adv_size = 2 * ADV_SIZE;

    if (xpread(devfd, syslinux_adv, adv_size, offset) != adv_size)
      return -1;

    return syslinux_validate_adv(syslinux_adv) ? 1 : 0;
}

static int ext_read_adv(const char *path, int devfd, const char **namep)
{
    int err;
    const char *name;

    if (fs_type == BTRFS) {
	/* btrfs "ldlinux.sys" is in 64k blank area */
	return ext_read_adv_offset(devfd, BTRFS_ADV_OFFSET);
    } else if (fs_type == XFS) {
	/* XFS "ldlinux.sys" is in the first 2048 bytes of the primary AG */
	return ext_read_adv_offset(devfd, boot_image_len);
    } else {
	err = read_adv(path, name = "ldlinux.sys");
	if (err == 2)		/* ldlinux.sys does not exist */
	    err = read_adv(path, name = "extlinux.sys");
	if (namep)
	    *namep = name;
	return err;
    }
}

static int ext_write_adv_offset(int devfd, off_t offset)
{
    const size_t adv_size = 2 * ADV_SIZE;

    if (xpwrite(devfd, syslinux_adv, adv_size, offset) != adv_size) {
	perror("writing adv");
	return 1;
    }

    return 0;
}

static int ext_write_adv(const char *path, const char *cfg, int devfd)
{
    if (fs_type == BTRFS) {
	/* btrfs "ldlinux.sys" is in 64k blank area */
	return ext_write_adv_offset(devfd, BTRFS_ADV_OFFSET);
    } else {
	return write_adv(path, cfg);
    }
}

/*
 * Install the boot block on the specified device.
 * Must be run AFTER install_file()!
 */
int install_bootblock(int fd, const char *device)
{
    struct ext2_super_block sb;
    struct btrfs_super_block sb2;
    struct fat_boot_sector sb3;
    struct ntfs_boot_sector sb4;
    xfs_sb_t sb5;
    struct ufs_super_block sb6;
    bool ok = false;

    if (fs_type == EXT2) {
	if (xpread(fd, &sb, sizeof sb, EXT2_SUPER_OFFSET) != sizeof sb) {
		perror("reading superblock");
		return 1;
	}

	if (sb.s_magic == EXT2_SUPER_MAGIC)
		ok = true;
    } else if (fs_type == BTRFS) {
	if (xpread(fd, &sb2, sizeof sb2, BTRFS_SUPER_INFO_OFFSET)
			!= sizeof sb2) {
		perror("reading superblock");
		return 1;
	}
	if (!memcmp(sb2.magic, BTRFS_MAGIC, BTRFS_MAGIC_L))
		ok = true;
    } else if (fs_type == VFAT) {
	if (xpread(fd, &sb3, sizeof sb3, 0) != sizeof sb3) {
		perror("reading fat superblock");
		return 1;
	}

	if (fat_check_sb_fields(&sb3))
		ok = true;
    } else if (fs_type == NTFS) {
        if (xpread(fd, &sb4, sizeof(sb4), 0) != sizeof(sb4)) {
            perror("reading ntfs superblock");
            return 1;
        }

        if (ntfs_check_sb_fields(&sb4))
             ok = true;
    } else if (fs_type == XFS) {
	if (xpread(fd, &sb5, sizeof sb5, 0) != sizeof sb5) {
	    perror("reading xfs superblock");
	    return 1;
	}

	if (sb5.sb_magicnum == *(u32 *)XFS_SB_MAGIC) {
	    if (be32_to_cpu(sb5.sb_blocksize) != XFS_SUPPORTED_BLOCKSIZE) {
		fprintf(stderr,
			"You need to have 4 KiB filesystem block size for"
			" being able to install Syslinux in your XFS"
			" partition (because there is not enough space in MBR to"
			" determine where Syslinux bootsector can be installed"
			" regardless of the filesystem block size)\n");
		return 1;
	    }

	    ok = true;
	}
    } else if (fs_type == UFS1 || fs_type == UFS2) {
	uint32_t sblock_off = (fs_type == UFS1) ?
	    SBLOCK_UFS1 : SBLOCK_UFS2;
	uint32_t ufs_smagic = (fs_type == UFS1) ?
	    UFS1_SUPER_MAGIC : UFS2_SUPER_MAGIC;

	if (xpread(fd, &sb6, sizeof sb6, sblock_off) != sizeof sb6) {
		perror("reading superblock");
		return 1;
	}

	if (sb6.fs_magic == ufs_smagic)
		ok = true;
    }

    if (!ok) {
	fprintf(stderr,
		"no fat, ntfs, ext2/3/4, btrfs, xfs"
		" or ufs1/2 superblock found on %s\n",
		device);
	return 1;
    }

    if (fs_type == VFAT) {
	struct fat_boot_sector *sbs = (struct fat_boot_sector *)syslinux_bootsect;
        if (xpwrite(fd, &sbs->FAT_bsHead, FAT_bsHeadLen, 0) != FAT_bsHeadLen ||
	    xpwrite(fd, &sbs->FAT_bsCode, FAT_bsCodeLen,
		    offsetof(struct fat_boot_sector, FAT_bsCode)) != FAT_bsCodeLen) {
	    perror("writing fat bootblock");
	    return 1;
	}
    } else if (fs_type == NTFS) {
        struct ntfs_boot_sector *sbs =
                (struct ntfs_boot_sector *)syslinux_bootsect;
        if (xpwrite(fd, &sbs->NTFS_bsHead,
                    NTFS_bsHeadLen, 0) != NTFS_bsHeadLen ||
                    xpwrite(fd, &sbs->NTFS_bsCode, NTFS_bsCodeLen,
                    offsetof(struct ntfs_boot_sector,
                    NTFS_bsCode)) != NTFS_bsCodeLen) {
            perror("writing ntfs bootblock");
            return 1;
        }
    } else if (fs_type == XFS) {
	if (xpwrite(fd, syslinux_bootsect, syslinux_bootsect_len,
		    XFS_BOOTSECT_OFFSET) != syslinux_bootsect_len) {
	    perror("writing xfs bootblock");
	    return 1;
	}
    } else {
	if (xpwrite(fd, syslinux_bootsect, syslinux_bootsect_len, 0)
	    != syslinux_bootsect_len) {
	    perror("writing bootblock");
	    return 1;
	}
    }

    return 0;
}

static int rewrite_boot_image(int devfd, const char *path, const char *filename)
{
    int fd;
    int ret;
    int modbytes;

    /* Let's create LDLINUX.SYS file again (if it already exists, of course) */
    fd = open(filename,  O_WRONLY | O_TRUNC | O_CREAT | O_SYNC,
	      S_IRUSR | S_IRGRP | S_IROTH);
    if (fd < 0) {
	perror(filename);
	return -1;
    }

    /* Write boot image data into LDLINUX.SYS file */
    ret = xpwrite(fd, (const char _force *)boot_image, boot_image_len, 0);
    if (ret != boot_image_len) {
	perror("writing bootblock");
	goto error;
    }

    /* Write ADV */
    if (ext_write_adv_offset(fd, boot_image_len)) {
	fprintf(stderr, "%s: write failure on %s\n", program, filename);
	goto error;
    }

    /* Map the file, and patch the initial sector accordingly */
    modbytes = patch_file_and_bootblock(fd, path, devfd);

    /* Write the patch area again - this relies on the file being overwritten
     * in place! */
    ret = xpwrite(fd, (const char _force *)boot_image, modbytes, 0);
    if (ret != modbytes) {
	fprintf(stderr, "%s: write failure on %s\n", program, filename);
	goto error;
    }

    return fd;

error:
    close(fd);

    return -1;
}

int ext2_fat_install_file(const char *path, int devfd, struct stat *rst)
{
    char *file, *oldfile, *c32file;
    int fd = -1, dirfd = -1;
    int r1, r2, r3;

    r1 = asprintf(&file, "%s%sldlinux.sys",
		  path, path[0] && path[strlen(path) - 1] == '/' ? "" : "/");
    r2 = asprintf(&oldfile, "%s%sextlinux.sys",
		  path, path[0] && path[strlen(path) - 1] == '/' ? "" : "/");
    r3 = asprintf(&c32file, "%s%sldlinux.c32",
		  path, path[0] && path[strlen(path) - 1] == '/' ? "" : "/");
    if (r1 < 0 || !file || r2 < 0 || !oldfile || r3 < 0 || !c32file) {
	perror(program);
	return 1;
    }

    dirfd = open(path, O_RDONLY | O_DIRECTORY);
    if (dirfd < 0) {
	perror(path);
	goto bail;
    }

    fd = open(file, O_RDONLY);
    if (fd < 0) {
	if (errno != ENOENT) {
	    perror(file);
	    goto bail;
	}
    } else {
	clear_attributes(fd);
    }
    close(fd);

    fd = rewrite_boot_image(devfd, path, file);
    if (fd < 0)
	goto bail;

    /* Attempt to set immutable flag and remove all write access */
    /* Only set immutable flag if file is owned by root */
    set_attributes(fd);

    if (fstat(fd, rst)) {
	perror(file);
	goto bail;
    }

    close(dirfd);
    close(fd);

    /* Look if we have the old filename */
    fd = open(oldfile, O_RDONLY);
    if (fd >= 0) {
	clear_attributes(fd);
	close(fd);
	unlink(oldfile);
    }

    fd = open(c32file, O_WRONLY | O_TRUNC | O_CREAT | O_SYNC,
	      S_IRUSR | S_IRGRP | S_IROTH);
    if (fd < 0) {
	perror(c32file);
	goto bail;
    }

    r3 = xpwrite(fd, (const char _force *)syslinux_ldlinuxc32,
		 syslinux_ldlinuxc32_len, 0);
    if (r3 != syslinux_ldlinuxc32_len) {
	fprintf(stderr, "%s: write failure on %s\n", program, c32file);
	goto bail;
    }

    free(file);
    free(oldfile);
    free(c32file);
    return 0;

bail:
    if (dirfd >= 0)
	close(dirfd);
    if (fd >= 0)
	close(fd);

    free(file);
    free(oldfile);
    free(c32file);
    return 1;
}

/* btrfs has to install ldlinux.sys to a boot area, which is not managed by
   btrfs tree, so actually this is not installed as a file, since the cow
   feature of btrfs would move the ldlinux.sys file everywhere. Older
   versions installed it to the first 64kiB (aka Boot Area A) but as of
   commit ID 37eef640 (before 6.03-pre12, before 6.03), it is now in Boot
   Area B (a 768kiB blank space at offset 256kiB). */
int btrfs_install_file(const char *path, int devfd, struct stat *rst)
{
    char *file;
    int fd, rv;

    patch_file_and_bootblock(-1, path, devfd);
    if (xpwrite(devfd, (const char _force *)boot_image,
		boot_image_len, BTRFS_EXTLINUX_OFFSET)
		!= boot_image_len) {
	perror("writing bootblock");
	return 1;
    }
    dprintf("write boot_image to 0x%x\n", BTRFS_EXTLINUX_OFFSET);
    if (ext_write_adv_offset(devfd, BTRFS_ADV_OFFSET)) {
	return 1;
    }
    dprintf("write adv to 0x%x\n", BTRFS_ADV_OFFSET);
    if (stat(path, rst)) {
	perror(path);
	return 1;
    }

    /*
     * Note that we *can* install ldinux.c32 as a regular file because
     * it doesn't need to be within the first 64K. The Syslinux core
     * has enough smarts to search the btrfs dirs and find this file.
     */
    rv = asprintf(&file, "%s%sldlinux.c32",
		  path, path[0] && path[strlen(path) - 1] == '/' ? "" : "/");
    if (rv < 0 || !file) {
	perror(program);
	return 1;
    }

    fd = open(file, O_WRONLY | O_TRUNC | O_CREAT | O_SYNC,
	      S_IRUSR | S_IRGRP | S_IROTH);
    if (fd < 0) {
	perror(file);
	free(file);
	return 1;
    }

    rv = xpwrite(fd, (const char _force *)syslinux_ldlinuxc32,
		 syslinux_ldlinuxc32_len, 0);
    if (rv != (int)syslinux_ldlinuxc32_len) {
	fprintf(stderr, "%s: write failure on %s\n", program, file);
	rv = 1;
    } else
	rv = 0;

    close(fd);
    free(file);
    return rv;
}

/*
 * Due to historical reasons (SGI IRIX's design of disk layouts), the first
 * sector in the primary AG on XFS filesystems contains the superblock, which is
 * a problem with bootloaders that rely on BIOSes (that load VBRs which are
 * located in the first sector of the partition).
 *
 * Thus, we need to handle this issue, otherwise Syslinux will damage the XFS's
 * superblock.
 */
static int xfs_install_file(const char *path, int devfd, struct stat *rst)
{
    static char file[PATH_MAX + 1];
    static char c32file[PATH_MAX + 1];
    int dirfd = -1;
    int fd = -1;
    int retval;

    snprintf(file, PATH_MAX + 1, "%s%sldlinux.sys", path,
	     path[0] && path[strlen(path) - 1] == '/' ? "" : "/");
    snprintf(c32file, PATH_MAX + 1, "%s%sldlinux.c32", path,
	     path[0] && path[strlen(path) - 1] == '/' ? "" : "/");

    dirfd = open(path, O_RDONLY | O_DIRECTORY);
    if (dirfd < 0) {
	perror(path);
	goto bail;
    }

    fd = open(file, O_RDONLY);
    if (fd < 0) {
	if (errno != ENOENT) {
	    perror(file);
	    goto bail;
	}
    } else {
	clear_attributes(fd);
    }

    close(fd);

    fd = rewrite_boot_image(devfd, path, file);
    if (fd < 0)
	goto bail;

    /* Attempt to set immutable flag and remove all write access */
    /* Only set immutable flag if file is owned by root */
    set_attributes(fd);

    if (fstat(fd, rst)) {
	perror(file);
	goto bail;
    }

    close(dirfd);
    close(fd);

    dirfd = -1;
    fd = -1;

    fd = open(c32file, O_WRONLY | O_TRUNC | O_CREAT | O_SYNC,
	      S_IRUSR | S_IRGRP | S_IROTH);
    if (fd < 0) {
	perror(c32file);
	goto bail;
    }

    retval = xpwrite(fd, (const char _force *)syslinux_ldlinuxc32,
		     syslinux_ldlinuxc32_len, 0);
    if (retval != (int)syslinux_ldlinuxc32_len) {
	fprintf(stderr, "%s: write failure on %s\n", program, file);
	goto bail;
    }

    close(fd);

    sync();

    return 0;

bail:
    if (dirfd >= 0)
	close(dirfd);

    if (fd >= 0)
	close(fd);

    return 1;
}

/*
 *  * test if path is a subvolume:
 *   * this function return
 *    * 0-> path exists but it is not a subvolume
 *     * 1-> path exists and it is a subvolume
 *      * -1 -> path is unaccessible
 *       */
static int test_issubvolume(char *path)
{

        struct stat     st;
        int             res;

        res = stat(path, &st);
        if(res < 0 )
                return -1;

        return (st.st_ino == 256) && S_ISDIR(st.st_mode);

}

/*
 * Get the default subvolume of a btrfs filesystem
 *   rootdir: btrfs root dir
 *   subvol:  this function will save the default subvolume name here
 */
static char * get_default_subvol(char * rootdir, char * subvol)
{
    struct btrfs_ioctl_search_args args;
    struct btrfs_ioctl_search_key *sk = &args.key;
    struct btrfs_ioctl_search_header *sh;
    int ret, i;
    int fd;
    struct btrfs_root_ref *ref;
    struct btrfs_dir_item *dir_item;
    unsigned long off = 0;
    int name_len;
    char *name;
    char dirname[4096];
    u64 defaultsubvolid = 0;

    ret = test_issubvolume(rootdir);
    if (ret == 1) {
        fd = open(rootdir, O_RDONLY);
        if (fd < 0) {
            fprintf(stderr, "ERROR: failed to open %s\n", rootdir);
        }
        ret = fd;
    }
    if (ret <= 0) {
        subvol[0] = '\0';
        return NULL;
    }

    memset(&args, 0, sizeof(args));

   /* search in the tree of tree roots */
   sk->tree_id = 1;

   /*
    * set the min and max to backref keys.  The search will
    * only send back this type of key now.
    */
   sk->max_type = BTRFS_DIR_ITEM_KEY;
   sk->min_type = BTRFS_DIR_ITEM_KEY;

   /*
    * set all the other params to the max, we'll take any objectid
    * and any trans
    */
   sk->min_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
   sk->max_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;

   sk->max_offset = (u64)-1;
   sk->min_offset = 0;
   sk->max_transid = (u64)-1;

   /* just a big number, doesn't matter much */
   sk->nr_items = 4096;

   while(1) {
       ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
       if (ret < 0) {
           fprintf(stderr, "ERROR: can't perform the search\n");
           subvol[0] = '\0';
           return NULL;
       }
       /* the ioctl returns the number of item it found in nr_items */
       if (sk->nr_items == 0) {
           break;
       }

       off = 0;

       /*
        * for each item, pull the key out of the header and then
        * read the root_ref item it contains
        */
       for (i = 0; i < sk->nr_items; i++) {
           sh = (struct btrfs_ioctl_search_header *)(args.buf + off);
           off += sizeof(*sh);
           if (sh->type == BTRFS_DIR_ITEM_KEY) {
               dir_item = (struct btrfs_dir_item *)(args.buf + off);
               name_len = dir_item->name_len;
               name = (char *)(dir_item + 1);


               /*add_root(&root_lookup, sh->objectid, sh->offset,
                        dir_id, name, name_len);*/
               strncpy(dirname, name, name_len);
               dirname[name_len] = '\0';
               if (strcmp(dirname, "default") == 0) {
                   defaultsubvolid = dir_item->location.objectid;
                   break;
               }
           }
           off += sh->len;

           /*
            * record the mins in sk so we can make sure the
            * next search doesn't repeat this root
            */
           sk->min_objectid = sh->objectid;
           sk->min_type = sh->type;
           sk->max_type = sh->type;
           sk->min_offset = sh->offset;
       }
       if (defaultsubvolid != 0)
           break;
       sk->nr_items = 4096;
       /* this iteration is done, step forward one root for the next
        * ioctl
        */
       if (sk->min_objectid < (u64)-1) {
           sk->min_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
           sk->max_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
           sk->max_type = BTRFS_ROOT_BACKREF_KEY;
           sk->min_type = BTRFS_ROOT_BACKREF_KEY;
           sk->min_offset = 0;
       } else
           break;
   }

   if (defaultsubvolid == 0) {
       subvol[0] = '\0';
       return NULL;
   }

   memset(&args, 0, sizeof(args));

   /* search in the tree of tree roots */
   sk->tree_id = 1;

   /*
    * set the min and max to backref keys.  The search will
    * only send back this type of key now.
    */
   sk->max_type = BTRFS_ROOT_BACKREF_KEY;
   sk->min_type = BTRFS_ROOT_BACKREF_KEY;

   /*
    * set all the other params to the max, we'll take any objectid
    * and any trans
    */
   sk->max_objectid = (u64)-1;
   sk->max_offset = (u64)-1;
   sk->max_transid = (u64)-1;

   /* just a big number, doesn't matter much */
   sk->nr_items = 4096;

   while(1) {
       ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
       if (ret < 0) {
           fprintf(stderr, "ERROR: can't perform the search\n");
           subvol[0] = '\0';
           return NULL;
       }
       /* the ioctl returns the number of item it found in nr_items */
       if (sk->nr_items == 0)
           break;

       off = 0;

       /*
        * for each item, pull the key out of the header and then
        * read the root_ref item it contains
        */
       for (i = 0; i < sk->nr_items; i++) {
           sh = (struct btrfs_ioctl_search_header *)(args.buf + off);
           off += sizeof(*sh);
           if (sh->type == BTRFS_ROOT_BACKREF_KEY) {
               ref = (struct btrfs_root_ref *)(args.buf + off);
               name_len = ref->name_len;
               name = (char *)(ref + 1);

               if (sh->objectid == defaultsubvolid) {
                   strncpy(subvol, name, name_len);
                   subvol[name_len] = '\0';
                   dprintf("The default subvolume: %s, ID: %llu\n",
			   subvol, sh->objectid);
                   break;
               }

           }

           off += sh->len;

           /*
            * record the mins in sk so we can make sure the
            * next search doesn't repeat this root
            */
           sk->min_objectid = sh->objectid;
           sk->min_type = sh->type;
           sk->min_offset = sh->offset;
       }
       if (subvol[0] != '\0')
           break;
       sk->nr_items = 4096;
       /* this iteration is done, step forward one root for the next
        * ioctl
        */
       if (sk->min_objectid < (u64)-1) {
           sk->min_objectid++;
           sk->min_type = BTRFS_ROOT_BACKREF_KEY;
           sk->min_offset = 0;
       } else
           break;
   }
   return subvol;
}

static int install_file(const char *path, int devfd, struct stat *rst)
{
    if (fs_type == EXT2 || fs_type == VFAT || fs_type == NTFS
	|| fs_type == UFS1 || fs_type == UFS2)
	return ext2_fat_install_file(path, devfd, rst);
    else if (fs_type == BTRFS)
	return btrfs_install_file(path, devfd, rst);
    else if (fs_type == XFS)
	return xfs_install_file(path, devfd, rst);

    return 1;
}

#ifdef __KLIBC__
static char devname_buf[64];

static void device_cleanup(void)
{
    unlink(devname_buf);
}
#endif

/* Verify that a device fd and a pathname agree.
   Return 0 on valid, -1 on error. */
static int validate_device_btrfs(int pathfd, int devfd);
static int validate_device(const char *path, int devfd)
{
    struct stat pst, dst;
    struct statfs sfs;
    int pfd;
    int rv = -1;

    pfd = open(path, O_RDONLY|O_DIRECTORY);
    if (pfd < 0)
	goto err;

    if (fstat(pfd, &pst) || fstat(devfd, &dst) || statfs(path, &sfs))
	goto err;

    /* btrfs st_dev is not matched with mnt st_rdev, it is a known issue */
    if (fs_type == BTRFS) {
	if (sfs.f_type == BTRFS_SUPER_MAGIC)
	    rv = validate_device_btrfs(pfd, devfd);
    } else {
	rv = (pst.st_dev == dst.st_rdev) ? 0 : -1;
    }

err:
    if (pfd >= 0)
	close(pfd);
    return rv;
}

#ifndef __KLIBC__
static char *find_device(const char *mtab_file, dev_t dev)
{
    struct mntent *mnt;
    struct stat dst;
    FILE *mtab;
    char *devname = NULL;
    bool done;

    mtab = setmntent(mtab_file, "r");
    if (!mtab)
	return NULL;

    done = false;
    while ((mnt = getmntent(mtab))) {
	/* btrfs st_dev is not matched with mnt st_rdev, it is a known issue */
	switch (fs_type) {
	case BTRFS:
	    if (!strcmp(mnt->mnt_type, "btrfs") &&
		!stat(mnt->mnt_dir, &dst) &&
		dst.st_dev == dev) {
		if (!subvol[0])
		    get_default_subvol(mnt->mnt_dir, subvol);
		done = true;
	    }
	    break;
	case EXT2:
	    if ((!strcmp(mnt->mnt_type, "ext2") ||
		 !strcmp(mnt->mnt_type, "ext3") ||
		 !strcmp(mnt->mnt_type, "ext4")) &&
		!stat(mnt->mnt_fsname, &dst) &&
		dst.st_rdev == 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 NTFS:
	    if ((!strcmp(mnt->mnt_type, "fuseblk") /* ntfs-3g */ ||
		 !strcmp(mnt->mnt_type, "ntfs")) &&
		!stat(mnt->mnt_fsname, &dst) &&
		dst.st_rdev == dev) {
		done = true;
		break;
	    }

	    break;
	case XFS:
	    if (!strcmp(mnt->mnt_type, "xfs") && !stat(mnt->mnt_fsname, &dst) &&
		dst.st_rdev == dev) {
		done = true;
		break;
	    }

	    break;
	case UFS1:
	case UFS2:
	    if (!strcmp(mnt->mnt_type, "ufs") && !stat(mnt->mnt_fsname, &dst) &&
		dst.st_rdev == dev) {
		done = true;
	    }

	    break;
	case NONE:
	    break;
	}

	if (done) {
	    devname = strdup(mnt->mnt_fsname);
	    break;
	}
    }

    endmntent(mtab);

    return devname;
}
#endif

/*
 * On newer Linux kernels we can use sysfs to get a backwards mapping
 * from device names to standard filenames
 */
static char *find_device_sysfs(dev_t dev)
{
    char sysname[64];
    char linkname[PATH_MAX];
    ssize_t llen;
    char *p, *q;
    char *buf = NULL;
    struct stat st;

    snprintf(sysname, sizeof sysname, "/sys/dev/block/%u:%u",
	     major(dev), minor(dev));

    llen = readlink(sysname, linkname, sizeof linkname);
    if (llen < 0 || llen >= sizeof linkname)
	goto err;

    linkname[llen] = '\0';

    p = strrchr(linkname, '/');
    p = p ? p+1 : linkname;	/* Leave basename */

    buf = q = malloc(strlen(p) + 6);
    if (!buf)
	goto err;

    memcpy(q, "/dev/", 5);
    q += 5;

    while (*p) {
	*q++ = (*p == '!') ? '/' : *p;
	p++;
    }

    *q = '\0';

    if (!stat(buf, &st) && st.st_dev == dev)
	return buf;		/* Found it! */

err:
    if (buf)
	free(buf);
    return NULL;
}

static const char *find_device_mountinfo(const char *path, dev_t dev)
{
    const struct mountinfo *m;
    struct stat st;

    m = find_mount(path, NULL);
    if (!m)
	return NULL;

    if (m->devpath[0] == '/' && m->dev == dev &&
	!stat(m->devpath, &st) && S_ISBLK(st.st_mode) && st.st_rdev == dev)
	return m->devpath;
    else
	return NULL;
}

static int validate_device_btrfs(int pfd, int dfd)
{
    struct btrfs_ioctl_fs_info_args fsinfo;
    static struct btrfs_ioctl_dev_info_args devinfo;
    struct btrfs_super_block sb2;

    if (ioctl(pfd, BTRFS_IOC_FS_INFO, &fsinfo))
	return -1;

    /* We do not support multi-device btrfs yet */
    if (fsinfo.num_devices != 1)
	return -1;

    /* The one device will have the max devid */
    memset(&devinfo, 0, sizeof devinfo);
    devinfo.devid = fsinfo.max_id;
    if (ioctl(pfd, BTRFS_IOC_DEV_INFO, &devinfo))
	return -1;

    if (devinfo.path[0] != '/')
	return -1;

    if (xpread(dfd, &sb2, sizeof sb2, BTRFS_SUPER_INFO_OFFSET) != sizeof sb2)
	return -1;

    if (memcmp(sb2.magic, BTRFS_MAGIC, BTRFS_MAGIC_L))
	return -1;

    if (memcmp(sb2.fsid, fsinfo.fsid, sizeof fsinfo.fsid))
	return -1;

    if (sb2.num_devices != 1)
	return -1;

    if (sb2.dev_item.devid != devinfo.devid)
	return -1;

    if (memcmp(sb2.dev_item.uuid, devinfo.uuid, sizeof devinfo.uuid))
	return -1;

    if (memcmp(sb2.dev_item.fsid, fsinfo.fsid, sizeof fsinfo.fsid))
	return -1;

    return 0;			/* It's good! */
}

static const char *find_device_btrfs(const char *path)
{
    int pfd, dfd;
    struct btrfs_ioctl_fs_info_args fsinfo;
    static struct btrfs_ioctl_dev_info_args devinfo;
    const char *rv = NULL;

    pfd = dfd = -1;

    pfd = open(path, O_RDONLY);
    if (pfd < 0)
	goto err;

    if (ioctl(pfd, BTRFS_IOC_FS_INFO, &fsinfo))
	goto err;

    /* We do not support multi-device btrfs yet */
    if (fsinfo.num_devices != 1)
	goto err;

    /* The one device will have the max devid */
    memset(&devinfo, 0, sizeof devinfo);
    devinfo.devid = fsinfo.max_id;
    if (ioctl(pfd, BTRFS_IOC_DEV_INFO, &devinfo))
	goto err;

    if (devinfo.path[0] != '/')
	goto err;

    dfd = open((const char *)devinfo.path, O_RDONLY);
    if (dfd < 0)
	goto err;

    if (!validate_device_btrfs(pfd, dfd))
	rv = (const char *)devinfo.path; /* It's good! */

err:
    if (pfd >= 0)
	close(pfd);
    if (dfd >= 0)
	close(dfd);
    return rv;
}

static char *dupname(const char *name)
{
    char *out = NULL;
    int len = 0;
    if (name)
        len = strlen(name);
    if (len > 0)
        out = strndup(name, len);
    return out;
}

static char *get_devname(const char *path)
{
    char *devname = NULL;
    struct stat st;
    struct statfs sfs;

    if (stat(path, &st) || !S_ISDIR(st.st_mode)) {
	fprintf(stderr, "%s: Not a directory: %s\n", program, path);
	return devname;
    }
    if (statfs(path, &sfs)) {
	fprintf(stderr, "%s: statfs %s: %s\n", program, path, strerror(errno));
	return devname;
    }

    if (opt.device)
	devname = strdup(opt.device);

    if (!devname){
	if (fs_type == BTRFS) {
	    /* For btrfs try to get the device name from btrfs itself */
	    devname = dupname(find_device_btrfs(path));
	}
    }

    if (!devname) {
	devname = dupname(find_device_mountinfo(path, st.st_dev));
    }

#ifdef __KLIBC__
    if (!devname) {
	devname = find_device_sysfs(st.st_dev);
    }
    if (!devname) {
	/* klibc doesn't have getmntent and friends; instead, just create
	   a new device with the appropriate device type */
	snprintf(devname_buf, sizeof devname_buf, "/tmp/dev-%u:%u",
		 major(st.st_dev), minor(st.st_dev));

	if (mknod(devname_buf, S_IFBLK | 0600, st.st_dev)) {
	    fprintf(stderr, "%s: cannot create device %s\n", program, devname);
	    return devname;
	}

	atexit(device_cleanup);	/* unlink the device node on exit */
	devname = dupname(devname_buf);
    }

#else
    if (!devname) {
	devname = find_device("/proc/mounts", st.st_dev);
    }
    if (!devname) {
	/* Didn't find it in /proc/mounts, try /etc/mtab */
        devname = find_device("/etc/mtab", st.st_dev);
    }
    if (!devname) {
	devname = find_device_sysfs(st.st_dev);

	fprintf(stderr, "%s: cannot find device for path %s\n", program, path);
	return devname;
    }

    fprintf(stderr, "%s is device %s\n", path, devname);

#endif
    return devname;
}

static int open_device(const char *path, struct stat *st, char **_devname)
{
    int devfd;
    char *devname = NULL;
    struct statfs sfs;

    if (st)
	if (stat(path, st) || !S_ISDIR(st->st_mode)) {
		fprintf(stderr, "%s: Not a directory: %s\n", program, path);
		return -1;
	}

    if (statfs(path, &sfs)) {
	fprintf(stderr, "%s: statfs %s: %s\n", program, path, strerror(errno));
	return -1;
    }

    if (sfs.f_type == EXT2_SUPER_MAGIC)
	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;
    else if (sfs.f_type == NTFS_SB_MAGIC ||
                sfs.f_type == FUSE_SUPER_MAGIC /* ntfs-3g */)
	fs_type = NTFS;
    else if (sfs.f_type == XFS_SUPER_MAGIC)
	fs_type = XFS;
    else if (sfs.f_type == UFS1_SUPER_MAGIC)
	fs_type = UFS1;
    else if (sfs.f_type == UFS2_SUPER_MAGIC)
	fs_type = UFS2;

    if (!fs_type) {
	fprintf(stderr,
		"%s: not a fat, ntfs, ext2/3/4, btrfs, xfs"
		" or ufs1/2 filesystem: %s\n",
		program, path);
	return -1;
    }

    devfd = -1;
    devname = get_devname(path);

    if ((devfd = open(devname, O_RDWR | O_SYNC)) < 0) {
	fprintf(stderr, "%s: cannot open device %s\n", program, devname);
	free(devname);
	return -1;
    }

    /* Verify that the device we opened is the device intended */
    if (validate_device(path, devfd)) {
	fprintf(stderr, "%s: path %s doesn't match device %s\n",
		program, path, devname);
	free(devname);
	close(devfd);
	return -1;
    }
    if (_devname)
        *_devname = devname;
    else
        free(devname);
    return devfd;
}

static int install_loader(const char *path, int update_only)
{
    struct stat st, fst;
    int devfd, rv;
    char *devname = NULL;

    devfd = open_device(path, &st, &devname);
    if (devfd < 0)
	return 1;

    if (update_only && !syslinux_already_installed(devfd)) {
	fprintf(stderr, "%s: no previous syslinux boot sector found\n",
		program);
	close(devfd);
	return 1;
    }

    /* Read a pre-existing ADV, if already installed */
    if (opt.reset_adv) {
	syslinux_reset_adv(syslinux_adv);
    } else if (ext_read_adv(path, devfd, NULL) < 0) {
	close(devfd);
	return 1;
    }

    if (modify_adv() < 0) {
	close(devfd);
	return 1;
    }

    /* Install ldlinux.sys */
    if (install_file(path, devfd, &fst)) {
	close(devfd);
	return 1;
    }
    if (fst.st_dev != st.st_dev) {
	fprintf(stderr, "%s: file system changed under us - aborting!\n",
		program);
	close(devfd);
	return 1;
    }

    sync();
    rv = install_bootblock(devfd, devname);
    free(devname);
    close(devfd);
    sync();

    return rv;
}

/*
 * Modify the ADV of an existing installation
 */
int modify_existing_adv(const char *path)
{
    const char *filename;
    int devfd;

    devfd = open_device(path, NULL, NULL);
    if (devfd < 0)
	return 1;

    if (ext_read_adv(path, devfd, &filename) < 0) {
	close(devfd);
	return 1;
    }
    if (modify_adv() < 0) {
	close(devfd);
	return 1;
    }
    if (ext_write_adv(path, filename, devfd) < 0) {
	close(devfd);
	return 1;
    }
    close(devfd);
    return 0;
}

int main(int argc, char *argv[])
{
    parse_options(argc, argv, MODE_EXTLINUX);

    if (!opt.directory || opt.install_mbr || opt.activate_partition)
	usage(EX_USAGE, 0);

    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(opt.directory, opt.update_only);
}
