diff options
Diffstat (limited to 'extlinux/main.c')
-rw-r--r-- | extlinux/main.c | 1548 |
1 files changed, 1548 insertions, 0 deletions
diff --git a/extlinux/main.c b/extlinux/main.c new file mode 100644 index 0000000..09740bd --- /dev/null +++ b/extlinux/main.c @@ -0,0 +1,1548 @@ +/* ----------------------------------------------------------------------- * + * + * 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 an 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/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 "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, §s)) + 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; +} + +/* + * 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 no enough space in MBR to " + "determine where Syslinux bootsector can be installed " + "regardless 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 */ + ret = xpwrite(fd, syslinux_adv, 2 * ADV_SIZE, boot_image_len); + if (ret != 2 * ADV_SIZE) { + 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 the ldlinux.sys in the first 64K blank area, which + is not managered by btrfs tree, so actually this is not installed as files. + since the cow feature of btrfs will move the ldlinux.sys every where */ +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 (xpwrite(devfd, syslinux_adv, 2 * ADV_SIZE, BTRFS_ADV_OFFSET) + != 2 * ADV_SIZE) { + perror("writing adv"); + 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 const char *find_device(const char *mtab_file, dev_t dev) +{ + struct mntent *mnt; + struct stat dst; + FILE *mtab; + const 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 const 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 const char *get_devname(const char *path) +{ + const 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 = opt.device; + + if (!devname){ + if (fs_type == BTRFS) { + /* For btrfs try to get the device name from btrfs itself */ + devname = find_device_btrfs(path); + } + } + + if (!devname) { + devname = 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 = 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, const char **_devname) +{ + int devfd; + const 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 (_devname) + *_devname = devname; + + if ((devfd = open(devname, O_RDWR | O_SYNC)) < 0) { + fprintf(stderr, "%s: cannot open device %s\n", program, 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); + close(devfd); + return -1; + } + return devfd; +} + +static int btrfs_read_adv(int devfd) +{ + if (xpread(devfd, syslinux_adv, 2 * ADV_SIZE, BTRFS_ADV_OFFSET) + != 2 * ADV_SIZE) + return -1; + + return syslinux_validate_adv(syslinux_adv) ? 1 : 0; +} + +static inline int xfs_read_adv(int devfd) +{ + const size_t adv_size = 2 * ADV_SIZE; + + if (xpread(devfd, syslinux_adv, adv_size, boot_image_len) != 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 btrfs_read_adv(devfd); + } else if (fs_type == XFS) { + /* XFS "ldlinux.sys" is in the first 2048 bytes of the primary AG */ + return xfs_read_adv(devfd); + } 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(const char *path, const char *cfg, int devfd) +{ + if (fs_type == BTRFS) { /* btrfs "ldlinux.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); +} + +static int install_loader(const char *path, int update_only) +{ + struct stat st, fst; + int devfd, rv; + const char *devname; + + 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); + 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); +} |