/* ----------------------------------------------------------------------- * * * 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 /* This is needed to deal with the kernel headers imported into glibc 3.3.3. */ #include #include #include #include #include #include #ifndef __KLIBC__ #include #endif #include #include #include #include #include #include #include #include #include #include #include #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, §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 ldlinux.sys in the first 64K blank 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. */ 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 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 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; 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); }