1 /* ----------------------------------------------------------------------- *
3 * Copyright 1998-2008 H. Peter Anvin - All Rights Reserved
4 * Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, Inc., 53 Temple Place Ste 330,
9 * Boston MA 02111-1307, USA; either version 2 of the License, or
10 * (at your option) any later version; incorporated herein by reference.
12 * ----------------------------------------------------------------------- */
17 * Install the extlinux boot block on an fat, ext2/3/4 and btrfs filesystem
20 #define _GNU_SOURCE /* Enable everything */
22 /* This is needed to deal with the kernel headers imported into glibc 3.3.3. */
38 #include <sys/ioctl.h>
40 #include <sys/types.h>
41 #include <sys/mount.h>
44 #include <linux/fd.h> /* Floppy geometry */
45 #include <linux/hdreg.h> /* Hard disk geometry */
46 #define statfs _kernel_statfs /* HACK to deal with broken 2.4 distros */
47 #include <linux/fs.h> /* FIGETBSZ, FIBMAP */
48 #include <linux/msdos_fs.h> /* FAT_IOCTL_SET_ATTRIBUTES */
49 #ifndef FAT_IOCTL_SET_ATTRIBUTES
50 # define FAT_IOCTL_SET_ATTRIBUTES _IOW('r', 0x11, uint32_t)
53 #undef SECTOR_SIZE /* Garbage from <linux/msdos_fs.h> */
58 #include "../version.h"
60 #include "syslxcom.h" /* common functions shared with extlinux and syslinux */
62 #include "syslxopt.h" /* unified options */
65 # define dprintf printf
67 # define dprintf(...) ((void)0)
70 #if defined(__linux__) && !defined(BLKGETSIZE64)
71 /* This takes a u64, but the size field says size_t. Someone screwed big. */
72 # define BLKGETSIZE64 _IOR(0x12,114,size_t)
75 #ifndef EXT2_SUPER_OFFSET
76 #define EXT2_SUPER_OFFSET 1024
79 /* the btrfs partition first 64K blank area is used to store boot sector and
80 boot image, the boot sector is from 0~512, the boot image starts at 2K */
81 #define BTRFS_EXTLINUX_OFFSET (2*1024)
82 #define BTRFS_SUBVOL_OPT "subvol="
83 #define BTRFS_SUBVOL_MAX 256 /* By btrfs specification */
84 static char subvol[BTRFS_SUBVOL_MAX];
89 extern unsigned char extlinux_bootsect[];
90 extern unsigned int extlinux_bootsect_len;
93 #define boot_block extlinux_bootsect
94 #define boot_block_len extlinux_bootsect_len
99 extern unsigned char extlinux_image[];
100 extern unsigned int extlinux_image_len;
102 #undef boot_image_len
103 #define boot_image extlinux_image
104 #define boot_image_len extlinux_image_len
106 #define BTRFS_ADV_OFFSET (BTRFS_EXTLINUX_OFFSET + boot_image_len)
109 * Get the size of a block device
111 uint64_t get_size(int devfd)
118 if (!ioctl(devfd, BLKGETSIZE64, &bytes))
121 if (!ioctl(devfd, BLKGETSIZE, §s))
122 return (uint64_t) sects << 9;
123 else if (!fstat(devfd, &st) && st.st_size)
130 * Get device geometry and partition offset
132 struct geometry_table {
134 struct hd_geometry g;
137 /* Standard floppy disk geometries, plus LS-120. Zipdisk geometry
138 (x/64/32) is the final fallback. I don't know what LS-240 has
139 as its geometry, since I don't have one and don't know anyone that does,
140 and Google wasn't helpful... */
141 static const struct geometry_table standard_geometries[] = {
142 {360 * 1024, {2, 9, 40, 0}},
143 {720 * 1024, {2, 9, 80, 0}},
144 {1200 * 1024, {2, 15, 80, 0}},
145 {1440 * 1024, {2, 18, 80, 0}},
146 {1680 * 1024, {2, 21, 80, 0}},
147 {1722 * 1024, {2, 21, 80, 0}},
148 {2880 * 1024, {2, 36, 80, 0}},
149 {3840 * 1024, {2, 48, 80, 0}},
150 {123264 * 1024, {8, 32, 963, 0}}, /* LS120 */
154 int get_geometry(int devfd, uint64_t totalbytes, struct hd_geometry *geo)
156 struct floppy_struct fd_str;
157 const struct geometry_table *gp;
159 memset(geo, 0, sizeof *geo);
161 if (!ioctl(devfd, HDIO_GETGEO, &geo)) {
163 } else if (!ioctl(devfd, FDGETPRM, &fd_str)) {
164 geo->heads = fd_str.head;
165 geo->sectors = fd_str.sect;
166 geo->cylinders = fd_str.track;
171 /* Didn't work. Let's see if this is one of the standard geometries */
172 for (gp = standard_geometries; gp->bytes; gp++) {
173 if (gp->bytes == totalbytes) {
174 memcpy(geo, &gp->g, sizeof *geo);
179 /* Didn't work either... assign a geometry of 64 heads, 32 sectors; this is
180 what zipdisks use, so this would help if someone has a USB key that
181 they're booting in USB-ZIP mode. */
183 geo->heads = opt.heads ? : 64;
184 geo->sectors = opt.sectors ? : 32;
185 geo->cylinders = totalbytes / (geo->heads * geo->sectors << SECTOR_SHIFT);
188 if (!opt.sectors && !opt.heads)
190 "Warning: unable to obtain device geometry (defaulting to %d heads, %d sectors)\n"
191 " (on hard disks, this is usually harmless.)\n",
192 geo->heads, geo->sectors);
198 * Query the device geometry and put it into the boot sector.
199 * Map the file and put the map in the boot sector and file.
200 * Stick the "current directory" inode number into the file.
202 * Returns the number of modified bytes in the boot file.
204 int patch_file_and_bootblock(int fd, const char *dir, int devfd)
206 struct stat dirst, xdst;
207 struct hd_geometry geo;
209 uint64_t totalbytes, totalsectors;
212 struct boot_sector *bs;
213 struct patch_area *patcharea;
216 int secptroffset, diroffset, dirlen, subvoloffset, subvollen;
217 char *dirpath, *subpath, *xdirpath, *xsubpath;
219 dirpath = realpath(dir, NULL);
220 if (!dirpath || stat(dir, &dirst)) {
221 perror("accessing install directory");
222 exit(255); /* This should never happen */
225 if (lstat(dirpath, &xdst) ||
226 dirst.st_ino != xdst.st_ino ||
227 dirst.st_dev != xdst.st_dev) {
228 perror("realpath returned nonsense");
232 subpath = strchr(dirpath, '\0');
234 if (*subpath == '/') {
235 if (subpath > dirpath) {
237 xsubpath = subpath+1;
243 if (lstat(xdirpath, &xdst) || dirst.st_dev != xdst.st_dev) {
244 subpath = strchr(subpath+1, '/');
246 subpath = "/"; /* It's the root of the filesystem */
252 if (subpath == dirpath)
258 /* Now subpath should contain the path relative to the fs base */
259 dprintf("subpath = %s\n", subpath);
261 totalbytes = get_size(devfd);
262 get_geometry(devfd, totalbytes, &geo);
265 geo.heads = opt.heads;
267 geo.sectors = opt.sectors;
269 /* Patch this into a fake FAT superblock. This isn't because
270 FAT is a good format in any way, it's because it lets the
271 early bootstrap share code with the FAT version. */
272 dprintf("heads = %u, sect = %u\n", geo.heads, geo.sectors);
274 bs = (struct boot_sector *)boot_block;
276 totalsectors = totalbytes >> SECTOR_SHIFT;
277 if (totalsectors >= 65536) {
278 set_16(&bs->bsSectors, 0);
280 set_16(&bs->bsSectors, totalsectors);
282 set_32(&bs->bsHugeSectors, totalsectors);
284 set_16(&bs->bsBytesPerSec, SECTOR_SIZE);
285 set_16(&bs->bsSecPerTrack, geo.sectors);
286 set_16(&bs->bsHeads, geo.heads);
287 set_32(&bs->bsHiddenSecs, geo.start);
289 /* If we're in RAID mode then patch the appropriate instruction;
290 either way write the proper boot signature */
291 i = get_16(&bs->bsSignature);
293 set_16((uint16_t *) (boot_block + i), 0x18CD); /* INT 18h */
295 set_16(&bs->bsSignature, 0xAA55);
297 /* Construct the boot file */
299 dprintf("directory inode = %lu\n", (unsigned long)dirst.st_ino);
300 nsect = (boot_image_len + SECTOR_SIZE - 1) >> SECTOR_SHIFT;
301 nsect += 2; /* Two sectors for the ADV */
302 sectp = alloca(sizeof(uint32_t) * nsect);
303 if (fs_type == EXT2 || fs_type == VFAT) {
304 if (sectmap(fd, sectp, nsect)) {
308 } else if (fs_type == BTRFS) {
311 for (i = 0; i < nsect; i++)
312 *(sectp + i) = BTRFS_EXTLINUX_OFFSET/SECTOR_SIZE + i;
315 /* First sector need pointer in boot sector */
316 set_32(&bs->NextSector, *sectp++);
319 set_16(&bs->MaxTransfer, 1);
321 /* Search for LDLINUX_MAGIC to find the patch area */
322 for (wp = (uint32_t *) boot_image; get_32(wp) != LDLINUX_MAGIC; wp++) ;
323 patcharea = (struct patch_area *)wp;
325 /* Set up the totals */
326 dw = boot_image_len >> 2; /* COMPLETE dwords, excluding ADV */
327 set_16(&patcharea->data_sectors, nsect - 2); /* -2 for the ADVs */
328 set_16(&patcharea->adv_sectors, 2);
329 set_32(&patcharea->dwords, dw);
331 /* Set the sector pointers */
332 secptroffset = get_16(&patcharea->secptroffset);
333 wp = (uint32_t *) ((char *)boot_image + secptroffset);
334 nptrs = get_16(&patcharea->secptrcnt);
336 memset(wp, 0, nptrs * 4);
337 while (--nsect) /* the first sector in bs->NextSector */
338 set_32(wp++, *sectp++);
340 /* Poke in the base directory path */
341 diroffset = get_16(&patcharea->diroffset);
342 dirlen = get_16(&patcharea->dirlen);
343 if (dirlen <= strlen(subpath)) {
344 fprintf(stderr, "Subdirectory path too long... aborting install!\n");
347 strncpy((char *)boot_image + diroffset, subpath, dirlen);
350 /* write subvol info if we have */
351 subvoloffset = get_16(&patcharea->subvoloffset);
352 subvollen = get_16(&patcharea->subvollen);
353 if (subvollen <= strlen(subvol)) {
354 fprintf(stderr, "Subvol name too long... aborting install!\n");
357 strncpy((char *)boot_image + subvoloffset, subvol, subvollen);
359 /* Now produce a checksum */
360 set_32(&patcharea->checksum, 0);
362 csum = LDLINUX_MAGIC;
363 for (i = 0, wp = (uint32_t *) boot_image; i < dw; i++, wp++)
364 csum -= get_32(wp); /* Negative checksum */
366 set_32(&patcharea->checksum, csum);
369 * Assume all bytes modified. This can be optimized at the expense
370 * of keeping track of what the highest modified address ever was.
376 * Make any user-specified ADV modifications
383 if (syslinux_setadv(ADV_BOOTONCE, strlen(opt.set_once), opt.set_once)) {
384 fprintf(stderr, "%s: not enough space for boot-once command\n",
390 if (syslinux_setadv(ADV_MENUSAVE, strlen(opt.menu_save), opt.menu_save)) {
391 fprintf(stderr, "%s: not enough space for menu-save label\n",
401 * Install the boot block on the specified device.
402 * Must be run AFTER install_file()!
404 int install_bootblock(int fd, const char *device)
406 struct ext2_super_block sb;
407 struct btrfs_super_block sb2;
408 struct boot_sector sb3;
411 if (fs_type == EXT2) {
412 if (xpread(fd, &sb, sizeof sb, EXT2_SUPER_OFFSET) != sizeof sb) {
413 perror("reading superblock");
416 if (sb.s_magic == EXT2_SUPER_MAGIC)
418 } else if (fs_type == BTRFS) {
419 if (xpread(fd, &sb2, sizeof sb2, BTRFS_SUPER_INFO_OFFSET)
421 perror("reading superblock");
424 if (sb2.magic == *(u64 *)BTRFS_MAGIC)
426 } else if (fs_type == VFAT) {
427 if (xpread(fd, &sb3, sizeof sb3, 0) != sizeof sb3) {
428 perror("reading fat superblock");
431 if (sb3.bsResSectors && sb3.bsFATs &&
432 (strstr(sb3.bs16.FileSysType, "FAT") ||
433 strstr(sb3.bs32.FileSysType, "FAT")))
437 fprintf(stderr, "no fat, ext2/3/4 or btrfs superblock found on %s\n",
441 if (fs_type == VFAT) {
442 struct boot_sector *bs = (struct boot_sector *)extlinux_bootsect;
443 if (xpwrite(fd, &bs->bsHead, bsHeadLen, 0) != bsHeadLen ||
444 xpwrite(fd, &bs->bsCode, bsCodeLen,
445 offsetof(struct boot_sector, bsCode)) != bsCodeLen) {
446 perror("writing fat bootblock");
450 if (xpwrite(fd, boot_block, boot_block_len, 0) != boot_block_len) {
451 perror("writing bootblock");
459 int ext2_fat_install_file(const char *path, int devfd, struct stat *rst)
462 int fd = -1, dirfd = -1;
465 asprintf(&file, "%s%sextlinux.sys",
466 path, path[0] && path[strlen(path) - 1] == '/' ? "" : "/");
472 dirfd = open(path, O_RDONLY | O_DIRECTORY);
478 fd = open(file, O_RDONLY);
480 if (errno != ENOENT) {
485 clear_attributes(fd);
489 fd = open(file, O_WRONLY | O_TRUNC | O_CREAT | O_SYNC,
490 S_IRUSR | S_IRGRP | S_IROTH);
496 /* Write it the first time */
497 if (xpwrite(fd, boot_image, boot_image_len, 0) != boot_image_len ||
498 xpwrite(fd, syslinux_adv, 2 * ADV_SIZE,
499 boot_image_len) != 2 * ADV_SIZE) {
500 fprintf(stderr, "%s: write failure on %s\n", program, file);
504 /* Map the file, and patch the initial sector accordingly */
505 modbytes = patch_file_and_bootblock(fd, path, devfd);
507 /* Write the patch area again - this relies on the file being
508 overwritten in place! */
509 if (xpwrite(fd, boot_image, modbytes, 0) != modbytes) {
510 fprintf(stderr, "%s: write failure on %s\n", program, file);
514 /* Attempt to set immutable flag and remove all write access */
515 /* Only set immutable flag if file is owned by root */
518 if (fstat(fd, rst)) {
536 /* btrfs has to install the extlinux.sys in the first 64K blank area, which
537 is not managered by btrfs tree, so actually this is not installed as files.
538 since the cow feature of btrfs will move the extlinux.sys every where */
539 int btrfs_install_file(const char *path, int devfd, struct stat *rst)
541 patch_file_and_bootblock(-1, path, devfd);
542 if (xpwrite(devfd, boot_image, boot_image_len, BTRFS_EXTLINUX_OFFSET)
544 perror("writing bootblock");
547 printf("write boot_image to 0x%x\n", BTRFS_EXTLINUX_OFFSET);
548 if (xpwrite(devfd, syslinux_adv, 2 * ADV_SIZE,
549 BTRFS_EXTLINUX_OFFSET + boot_image_len) != 2 * ADV_SIZE) {
550 perror("writing adv");
553 printf("write adv to 0x%x\n", BTRFS_EXTLINUX_OFFSET + boot_image_len);
554 if (stat(path, rst)) {
561 int install_file(const char *path, int devfd, struct stat *rst)
563 if (fs_type == EXT2 || fs_type == VFAT)
564 return ext2_fat_install_file(path, devfd, rst);
565 else if (fs_type == BTRFS)
566 return btrfs_install_file(path, devfd, rst);
570 /* EXTLINUX installs the string 'EXTLINUX' at offset 3 in the boot
571 sector; this is consistent with FAT filesystems. */
572 int already_installed(int devfd)
576 xpread(devfd, buffer, 8, 3);
577 return !memcmp(buffer, "EXTLINUX", 8);
581 static char devname_buf[64];
583 static void device_cleanup(void)
589 /* Verify that a device fd and a pathname agree.
590 Return 0 on valid, -1 on error. */
591 static int validate_device(const char *path, int devfd)
593 struct stat pst, dst;
596 if (stat(path, &pst) || fstat(devfd, &dst) || statfs(path, &sfs))
598 /* btrfs st_dev is not matched with mnt st_rdev, it is a known issue */
599 if (fs_type == BTRFS && sfs.f_type == BTRFS_SUPER_MAGIC)
601 return (pst.st_dev == dst.st_rdev) ? 0 : -1;
605 static const char *find_device(const char *mtab_file, dev_t dev)
610 const char *devname = NULL;
613 mtab = setmntent(mtab_file, "r");
618 while ((mnt = getmntent(mtab))) {
619 /* btrfs st_dev is not matched with mnt st_rdev, it is a known issue */
622 if (!strcmp(mnt->mnt_type, "btrfs") &&
623 !stat(mnt->mnt_dir, &dst) &&
625 char *opt = strstr(mnt->mnt_opts, BTRFS_SUBVOL_OPT);
631 strcpy(subvol, opt + sizeof(BTRFS_SUBVOL_OPT) - 1);
632 tmp = strchr(subvol, 32);
636 break; /* should break and let upper layer try again */
642 if ((!strcmp(mnt->mnt_type, "ext2") ||
643 !strcmp(mnt->mnt_type, "ext3") ||
644 !strcmp(mnt->mnt_type, "ext4")) &&
645 !stat(mnt->mnt_fsname, &dst) &&
646 dst.st_rdev == dev) {
651 if ((!strcmp(mnt->mnt_type, "vfat")) &&
652 !stat(mnt->mnt_fsname, &dst) &&
653 dst.st_rdev == dev) {
661 devname = strdup(mnt->mnt_fsname);
671 static const char *get_devname(const char *path)
673 const char *devname = NULL;
677 if (stat(path, &st) || !S_ISDIR(st.st_mode)) {
678 fprintf(stderr, "%s: Not a directory: %s\n", program, path);
681 if (statfs(path, &sfs)) {
682 fprintf(stderr, "%s: statfs %s: %s\n", program, path, strerror(errno));
687 /* klibc doesn't have getmntent and friends; instead, just create
688 a new device with the appropriate device type */
689 snprintf(devname_buf, sizeof devname_buf, "/tmp/dev-%u:%u",
690 major(st.st_dev), minor(st.st_dev));
692 if (mknod(devname_buf, S_IFBLK | 0600, st.st_dev)) {
693 fprintf(stderr, "%s: cannot create device %s\n", program, devname);
697 atexit(device_cleanup); /* unlink the device node on exit */
698 devname = devname_buf;
702 /* check /etc/mtab first, since btrfs subvol info is only in here */
703 devname = find_device("/etc/mtab", st.st_dev);
704 if (subvol[0] && !devname) { /* we just find it is a btrfs subvol */
708 strcpy(parent, path);
709 tmp = strrchr(parent, '/');
712 fprintf(stderr, "%s is subvol, try its parent dir %s\n", path, parent);
713 devname = get_devname(parent);
718 /* Didn't find it in /etc/mtab, try /proc/mounts */
719 devname = find_device("/proc/mounts", st.st_dev);
722 fprintf(stderr, "%s: cannot find device for path %s\n", program, path);
726 fprintf(stderr, "%s is device %s\n", path, devname);
731 static int open_device(const char *path, struct stat *st, const char **_devname)
734 const char *devname = NULL;
738 if (stat(path, st) || !S_ISDIR(st->st_mode)) {
739 fprintf(stderr, "%s: Not a directory: %s\n", program, path);
743 if (statfs(path, &sfs)) {
744 fprintf(stderr, "%s: statfs %s: %s\n", program, path, strerror(errno));
747 if (sfs.f_type == EXT2_SUPER_MAGIC)
749 else if (sfs.f_type == BTRFS_SUPER_MAGIC)
751 else if (sfs.f_type == MSDOS_SUPER_MAGIC)
755 fprintf(stderr, "%s: not a fat, ext2/3/4 or btrfs filesystem: %s\n",
761 devname = get_devname(path);
765 if ((devfd = open(devname, O_RDWR | O_SYNC)) < 0) {
766 fprintf(stderr, "%s: cannot open device %s\n", program, devname);
770 /* Verify that the device we opened is the device intended */
771 if (validate_device(path, devfd)) {
772 fprintf(stderr, "%s: path %s doesn't match device %s\n",
773 program, path, devname);
780 static int ext_read_adv(const char *path, const char *cfg, int devfd)
782 if (fs_type == BTRFS) { /* btrfs "extlinux.sys" is in 64k blank area */
783 if (xpread(devfd, syslinux_adv, 2 * ADV_SIZE,
784 BTRFS_ADV_OFFSET) != 2 * ADV_SIZE) {
785 perror("btrfs writing adv");
790 return read_adv(path, cfg);
793 static int ext_write_adv(const char *path, const char *cfg, int devfd)
795 if (fs_type == BTRFS) { /* btrfs "extlinux.sys" is in 64k blank area */
796 if (xpwrite(devfd, syslinux_adv, 2 * ADV_SIZE,
797 BTRFS_ADV_OFFSET) != 2 * ADV_SIZE) {
798 perror("writing adv");
803 return write_adv(path, cfg);
806 int install_loader(const char *path, int update_only)
812 devfd = open_device(path, &st, &devname);
816 if (update_only && !already_installed(devfd)) {
817 fprintf(stderr, "%s: no previous extlinux boot sector found\n",
823 /* Read a pre-existing ADV, if already installed */
825 syslinux_reset_adv(syslinux_adv);
826 else if (ext_read_adv(path, "extlinux.sys", devfd) < 0) {
830 if (modify_adv() < 0) {
835 /* Install extlinux.sys */
836 if (install_file(path, devfd, &fst)) {
840 if (fst.st_dev != st.st_dev) {
841 fprintf(stderr, "%s: file system changed under us - aborting!\n",
848 rv = install_bootblock(devfd, devname);
856 * Modify the ADV of an existing installation
858 int modify_existing_adv(const char *path)
862 devfd = open_device(path, NULL, NULL);
867 syslinux_reset_adv(syslinux_adv);
868 else if (ext_read_adv(path, "extlinux.sys", devfd) < 0) {
872 if (modify_adv() < 0) {
876 if (ext_write_adv(path, "extlinux.sys", devfd) < 0) {
884 int main(int argc, char *argv[])
886 parse_options(argc, argv, MODE_EXTLINUX);
891 if (opt.update_only == -1) {
892 if (opt.reset_adv || opt.set_once || opt.menu_save)
893 return modify_existing_adv(opt.directory);
895 usage(EX_USAGE, MODE_EXTLINUX);
898 return install_loader(opt.directory, opt.update_only);