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++);
318 /* Search for LDLINUX_MAGIC to find the patch area */
319 for (wp = (uint32_t *) boot_image; get_32(wp) != LDLINUX_MAGIC; wp++) ;
320 patcharea = (struct patch_area *)wp;
322 /* Set up the totals */
323 dw = boot_image_len >> 2; /* COMPLETE dwords, excluding ADV */
324 set_16(&patcharea->data_sectors, nsect - 2); /* -2 for the ADVs */
325 set_16(&patcharea->adv_sectors, 2);
326 set_32(&patcharea->dwords, dw);
330 set_16(&patcharea->maxtransfer, 1);
332 /* Set the sector pointers */
333 secptroffset = get_16(&patcharea->secptroffset);
334 wp = (uint32_t *) ((char *)boot_image + secptroffset);
335 nptrs = get_16(&patcharea->secptrcnt);
337 memset(wp, 0, nptrs * 4);
338 while (--nsect) /* the first sector in bs->NextSector */
339 set_32(wp++, *sectp++);
341 /* Poke in the base directory path */
342 diroffset = get_16(&patcharea->diroffset);
343 dirlen = get_16(&patcharea->dirlen);
344 if (dirlen <= strlen(subpath)) {
345 fprintf(stderr, "Subdirectory path too long... aborting install!\n");
348 strncpy((char *)boot_image + diroffset, subpath, dirlen);
351 /* write subvol info if we have */
352 subvoloffset = get_16(&patcharea->subvoloffset);
353 subvollen = get_16(&patcharea->subvollen);
354 if (subvollen <= strlen(subvol)) {
355 fprintf(stderr, "Subvol name too long... aborting install!\n");
358 strncpy((char *)boot_image + subvoloffset, subvol, subvollen);
360 /* Now produce a checksum */
361 set_32(&patcharea->checksum, 0);
363 csum = LDLINUX_MAGIC;
364 for (i = 0, wp = (uint32_t *) boot_image; i < dw; i++, wp++)
365 csum -= get_32(wp); /* Negative checksum */
367 set_32(&patcharea->checksum, csum);
370 * Assume all bytes modified. This can be optimized at the expense
371 * of keeping track of what the highest modified address ever was.
377 * Make any user-specified ADV modifications
384 if (syslinux_setadv(ADV_BOOTONCE, strlen(opt.set_once), opt.set_once)) {
385 fprintf(stderr, "%s: not enough space for boot-once command\n",
391 if (syslinux_setadv(ADV_MENUSAVE, strlen(opt.menu_save), opt.menu_save)) {
392 fprintf(stderr, "%s: not enough space for menu-save label\n",
402 * Install the boot block on the specified device.
403 * Must be run AFTER install_file()!
405 int install_bootblock(int fd, const char *device)
407 struct ext2_super_block sb;
408 struct btrfs_super_block sb2;
409 struct boot_sector sb3;
412 if (fs_type == EXT2) {
413 if (xpread(fd, &sb, sizeof sb, EXT2_SUPER_OFFSET) != sizeof sb) {
414 perror("reading superblock");
417 if (sb.s_magic == EXT2_SUPER_MAGIC)
419 } else if (fs_type == BTRFS) {
420 if (xpread(fd, &sb2, sizeof sb2, BTRFS_SUPER_INFO_OFFSET)
422 perror("reading superblock");
425 if (sb2.magic == *(u64 *)BTRFS_MAGIC)
427 } else if (fs_type == VFAT) {
428 if (xpread(fd, &sb3, sizeof sb3, 0) != sizeof sb3) {
429 perror("reading fat superblock");
432 if (sb3.bsResSectors && sb3.bsFATs &&
433 (strstr(sb3.bs16.FileSysType, "FAT") ||
434 strstr(sb3.bs32.FileSysType, "FAT")))
438 fprintf(stderr, "no fat, ext2/3/4 or btrfs superblock found on %s\n",
442 if (fs_type == VFAT) {
443 struct boot_sector *bs = (struct boot_sector *)extlinux_bootsect;
444 if (xpwrite(fd, &bs->bsHead, bsHeadLen, 0) != bsHeadLen ||
445 xpwrite(fd, &bs->bsCode, bsCodeLen,
446 offsetof(struct boot_sector, bsCode)) != bsCodeLen) {
447 perror("writing fat bootblock");
451 if (xpwrite(fd, boot_block, boot_block_len, 0) != boot_block_len) {
452 perror("writing bootblock");
460 int ext2_fat_install_file(const char *path, int devfd, struct stat *rst)
463 int fd = -1, dirfd = -1;
466 asprintf(&file, "%s%sextlinux.sys",
467 path, path[0] && path[strlen(path) - 1] == '/' ? "" : "/");
473 dirfd = open(path, O_RDONLY | O_DIRECTORY);
479 fd = open(file, O_RDONLY);
481 if (errno != ENOENT) {
486 clear_attributes(fd);
490 fd = open(file, O_WRONLY | O_TRUNC | O_CREAT | O_SYNC,
491 S_IRUSR | S_IRGRP | S_IROTH);
497 /* Write it the first time */
498 if (xpwrite(fd, boot_image, boot_image_len, 0) != boot_image_len ||
499 xpwrite(fd, syslinux_adv, 2 * ADV_SIZE,
500 boot_image_len) != 2 * ADV_SIZE) {
501 fprintf(stderr, "%s: write failure on %s\n", program, file);
505 /* Map the file, and patch the initial sector accordingly */
506 modbytes = patch_file_and_bootblock(fd, path, devfd);
508 /* Write the patch area again - this relies on the file being
509 overwritten in place! */
510 if (xpwrite(fd, boot_image, modbytes, 0) != modbytes) {
511 fprintf(stderr, "%s: write failure on %s\n", program, file);
515 /* Attempt to set immutable flag and remove all write access */
516 /* Only set immutable flag if file is owned by root */
519 if (fstat(fd, rst)) {
537 /* btrfs has to install the extlinux.sys in the first 64K blank area, which
538 is not managered by btrfs tree, so actually this is not installed as files.
539 since the cow feature of btrfs will move the extlinux.sys every where */
540 int btrfs_install_file(const char *path, int devfd, struct stat *rst)
542 patch_file_and_bootblock(-1, path, devfd);
543 if (xpwrite(devfd, boot_image, boot_image_len, BTRFS_EXTLINUX_OFFSET)
545 perror("writing bootblock");
548 printf("write boot_image to 0x%x\n", BTRFS_EXTLINUX_OFFSET);
549 if (xpwrite(devfd, syslinux_adv, 2 * ADV_SIZE,
550 BTRFS_EXTLINUX_OFFSET + boot_image_len) != 2 * ADV_SIZE) {
551 perror("writing adv");
554 printf("write adv to 0x%x\n", BTRFS_EXTLINUX_OFFSET + boot_image_len);
555 if (stat(path, rst)) {
562 int install_file(const char *path, int devfd, struct stat *rst)
564 if (fs_type == EXT2 || fs_type == VFAT)
565 return ext2_fat_install_file(path, devfd, rst);
566 else if (fs_type == BTRFS)
567 return btrfs_install_file(path, devfd, rst);
571 /* EXTLINUX installs the string 'EXTLINUX' at offset 3 in the boot
572 sector; this is consistent with FAT filesystems. */
573 int already_installed(int devfd)
577 xpread(devfd, buffer, 8, 3);
578 return !memcmp(buffer, "EXTLINUX", 8);
582 static char devname_buf[64];
584 static void device_cleanup(void)
590 /* Verify that a device fd and a pathname agree.
591 Return 0 on valid, -1 on error. */
592 static int validate_device(const char *path, int devfd)
594 struct stat pst, dst;
597 if (stat(path, &pst) || fstat(devfd, &dst) || statfs(path, &sfs))
599 /* btrfs st_dev is not matched with mnt st_rdev, it is a known issue */
600 if (fs_type == BTRFS && sfs.f_type == BTRFS_SUPER_MAGIC)
602 return (pst.st_dev == dst.st_rdev) ? 0 : -1;
606 static const char *find_device(const char *mtab_file, dev_t dev)
611 const char *devname = NULL;
614 mtab = setmntent(mtab_file, "r");
619 while ((mnt = getmntent(mtab))) {
620 /* btrfs st_dev is not matched with mnt st_rdev, it is a known issue */
623 if (!strcmp(mnt->mnt_type, "btrfs") &&
624 !stat(mnt->mnt_dir, &dst) &&
626 char *opt = strstr(mnt->mnt_opts, BTRFS_SUBVOL_OPT);
632 strcpy(subvol, opt + sizeof(BTRFS_SUBVOL_OPT) - 1);
633 tmp = strchr(subvol, 32);
637 break; /* should break and let upper layer try again */
643 if ((!strcmp(mnt->mnt_type, "ext2") ||
644 !strcmp(mnt->mnt_type, "ext3") ||
645 !strcmp(mnt->mnt_type, "ext4")) &&
646 !stat(mnt->mnt_fsname, &dst) &&
647 dst.st_rdev == dev) {
652 if ((!strcmp(mnt->mnt_type, "vfat")) &&
653 !stat(mnt->mnt_fsname, &dst) &&
654 dst.st_rdev == dev) {
662 devname = strdup(mnt->mnt_fsname);
672 static const char *get_devname(const char *path)
674 const char *devname = NULL;
678 if (stat(path, &st) || !S_ISDIR(st.st_mode)) {
679 fprintf(stderr, "%s: Not a directory: %s\n", program, path);
682 if (statfs(path, &sfs)) {
683 fprintf(stderr, "%s: statfs %s: %s\n", program, path, strerror(errno));
688 /* klibc doesn't have getmntent and friends; instead, just create
689 a new device with the appropriate device type */
690 snprintf(devname_buf, sizeof devname_buf, "/tmp/dev-%u:%u",
691 major(st.st_dev), minor(st.st_dev));
693 if (mknod(devname_buf, S_IFBLK | 0600, st.st_dev)) {
694 fprintf(stderr, "%s: cannot create device %s\n", program, devname);
698 atexit(device_cleanup); /* unlink the device node on exit */
699 devname = devname_buf;
703 /* check /etc/mtab first, since btrfs subvol info is only in here */
704 devname = find_device("/etc/mtab", st.st_dev);
705 if (subvol[0] && !devname) { /* we just find it is a btrfs subvol */
709 strcpy(parent, path);
710 tmp = strrchr(parent, '/');
713 fprintf(stderr, "%s is subvol, try its parent dir %s\n", path, parent);
714 devname = get_devname(parent);
719 /* Didn't find it in /etc/mtab, try /proc/mounts */
720 devname = find_device("/proc/mounts", st.st_dev);
723 fprintf(stderr, "%s: cannot find device for path %s\n", program, path);
727 fprintf(stderr, "%s is device %s\n", path, devname);
732 static int open_device(const char *path, struct stat *st, const char **_devname)
735 const char *devname = NULL;
739 if (stat(path, st) || !S_ISDIR(st->st_mode)) {
740 fprintf(stderr, "%s: Not a directory: %s\n", program, path);
744 if (statfs(path, &sfs)) {
745 fprintf(stderr, "%s: statfs %s: %s\n", program, path, strerror(errno));
748 if (sfs.f_type == EXT2_SUPER_MAGIC)
750 else if (sfs.f_type == BTRFS_SUPER_MAGIC)
752 else if (sfs.f_type == MSDOS_SUPER_MAGIC)
756 fprintf(stderr, "%s: not a fat, ext2/3/4 or btrfs filesystem: %s\n",
762 devname = get_devname(path);
766 if ((devfd = open(devname, O_RDWR | O_SYNC)) < 0) {
767 fprintf(stderr, "%s: cannot open device %s\n", program, devname);
771 /* Verify that the device we opened is the device intended */
772 if (validate_device(path, devfd)) {
773 fprintf(stderr, "%s: path %s doesn't match device %s\n",
774 program, path, devname);
781 static int ext_read_adv(const char *path, const char *cfg, int devfd)
783 if (fs_type == BTRFS) { /* btrfs "extlinux.sys" is in 64k blank area */
784 if (xpread(devfd, syslinux_adv, 2 * ADV_SIZE,
785 BTRFS_ADV_OFFSET) != 2 * ADV_SIZE) {
786 perror("btrfs writing adv");
791 return read_adv(path, cfg);
794 static int ext_write_adv(const char *path, const char *cfg, int devfd)
796 if (fs_type == BTRFS) { /* btrfs "extlinux.sys" is in 64k blank area */
797 if (xpwrite(devfd, syslinux_adv, 2 * ADV_SIZE,
798 BTRFS_ADV_OFFSET) != 2 * ADV_SIZE) {
799 perror("writing adv");
804 return write_adv(path, cfg);
807 int install_loader(const char *path, int update_only)
813 devfd = open_device(path, &st, &devname);
817 if (update_only && !already_installed(devfd)) {
818 fprintf(stderr, "%s: no previous extlinux boot sector found\n",
824 /* Read a pre-existing ADV, if already installed */
826 syslinux_reset_adv(syslinux_adv);
827 else if (ext_read_adv(path, "extlinux.sys", devfd) < 0) {
831 if (modify_adv() < 0) {
836 /* Install extlinux.sys */
837 if (install_file(path, devfd, &fst)) {
841 if (fst.st_dev != st.st_dev) {
842 fprintf(stderr, "%s: file system changed under us - aborting!\n",
849 rv = install_bootblock(devfd, devname);
857 * Modify the ADV of an existing installation
859 int modify_existing_adv(const char *path)
863 devfd = open_device(path, NULL, NULL);
868 syslinux_reset_adv(syslinux_adv);
869 else if (ext_read_adv(path, "extlinux.sys", devfd) < 0) {
873 if (modify_adv() < 0) {
877 if (ext_write_adv(path, "extlinux.sys", devfd) < 0) {
885 int main(int argc, char *argv[])
887 parse_options(argc, argv, MODE_EXTLINUX);
892 if (opt.update_only == -1) {
893 if (opt.reset_adv || opt.set_once || opt.menu_save)
894 return modify_existing_adv(opt.directory);
896 usage(EX_USAGE, MODE_EXTLINUX);
899 return install_loader(opt.directory, opt.update_only);