#ident "$Id: extlinux.c,v 1.15 2005/04/03 00:00:36 hpa Exp $" /* ----------------------------------------------------------------------- * * * Copyright 1998-2005 H. Peter Anvin - All Rights Reserved * * 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 extlinux boot block on an ext2/3 filesystem */ #define _GNU_SOURCE /* Enable everything */ #include /* This is needed to deal with the kernel headers imported into glibc 3.3.3. */ typedef uint64_t u64; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Floppy geometry */ #include /* Hard disk geometry */ #include /* FIGETBSZ, FIBMAP */ #include "ext2_fs.h" #include "../version.h" #ifdef DEBUG # define dprintf printf #else # define dprintf(...) ((void)0) #endif /* Global option handling */ const char *program; /* These are the options we can set and their values */ struct my_options { unsigned int sectors; unsigned int heads; } opt = { .sectors = 0, .heads = 0, }; static void __attribute__((noreturn)) usage(int rv) { fprintf(stderr, "Usage: %s [options] directory\n" " --zip -z Force zipdrive geometry (-H 64 -S 32)\n" " --sectors=# -S Force the number of sectors per track\n" " --heads=# -H Force number of heads\n" "\n" " Note: geometry is determined at boot time for devices which\n" " are considered hard disks by the BIOS. Unfortunately, this is\n" " not possible for devices which are considered floppy disks,\n" " which includes zipdisks and LS-120 superfloppies.\n" "\n" " The -z option is useful for USB devices which are considered\n" " hard disks by some BIOSes and zipdrives by other BIOSes.\n", program); exit(rv); } static const struct option long_options[] = { { "zipdrive", 0, NULL, 'z' }, { "sectors", 1, NULL, 'S' }, { "heads", 1, NULL, 'H' }, { "version", 0, NULL, 'v' }, { "help", 0, NULL, 'h' }, { 0, 0, 0, 0 } }; static const char short_options[] = "zS:H:vh"; #if defined(__linux__) && !defined(BLKGETSIZE64) /* This takes a u64, but the size field says size_t. Someone screwed big. */ # define BLKGETSIZE64 _IOR(0x12,114,size_t) #endif #define LDLINUX_MAGIC 0x3eb202fe enum bs_offsets { bsJump = 0x00, bsOemName = 0x03, bsBytesPerSec = 0x0b, bsSecPerClust = 0x0d, bsResSectors = 0x0e, bsFATs = 0x10, bsRootDirEnts = 0x11, bsSectors = 0x13, bsMedia = 0x15, bsFATsecs = 0x16, bsSecPerTrack = 0x18, bsHeads = 0x1a, bsHiddenSecs = 0x1c, bsHugeSectors = 0x20, /* FAT12/16 only */ bs16DriveNumber = 0x24, bs16Reserved1 = 0x25, bs16BootSignature = 0x26, bs16VolumeID = 0x27, bs16VolumeLabel = 0x2b, bs16FileSysType = 0x36, bs16Code = 0x3e, /* FAT32 only */ bs32FATSz32 = 36, bs32ExtFlags = 40, bs32FSVer = 42, bs32RootClus = 44, bs32FSInfo = 48, bs32BkBootSec = 50, bs32Reserved = 52, bs32DriveNumber = 64, bs32Reserved1 = 65, bs32BootSignature = 66, bs32VolumeID = 67, bs32VolumeLabel = 71, bs32FileSysType = 82, bs32Code = 90, bsSignature = 0x1fe }; #define bsHead bsJump #define bsHeadLen (bsOemName-bsHead) #define bsCode bs32Code /* The common safe choice */ #define bsCodeLen (bsSignature-bs32Code) /* * Access functions for littleendian numbers, possibly misaligned. */ static inline uint8_t get_8(const unsigned char *p) { return *(const uint8_t *)p; } static inline uint16_t get_16(const unsigned char *p) { #if defined(__i386__) || defined(__x86_64__) /* Littleendian and unaligned-capable */ return *(const uint16_t *)p; #else return (uint16_t)p[0] + ((uint16_t)p[1] << 8); #endif } static inline uint32_t get_32(const unsigned char *p) { #if defined(__i386__) || defined(__x86_64__) /* Littleendian and unaligned-capable */ return *(const uint32_t *)p; #else return (uint32_t)p[0] + ((uint32_t)p[1] << 8) + ((uint32_t)p[2] << 16) + ((uint32_t)p[3] << 24); #endif } static inline void set_16(unsigned char *p, uint16_t v) { #if defined(__i386__) || defined(__x86_64__) /* Littleendian and unaligned-capable */ *(uint16_t *)p = v; #else p[0] = (v & 0xff); p[1] = ((v >> 8) & 0xff); #endif } static inline void set_32(unsigned char *p, uint32_t v) { #if defined(__i386__) || defined(__x86_64__) /* Littleendian and unaligned-capable */ *(uint32_t *)p = v; #else p[0] = (v & 0xff); p[1] = ((v >> 8) & 0xff); p[2] = ((v >> 16) & 0xff); p[3] = ((v >> 24) & 0xff); #endif } #ifndef EXT2_SUPER_OFFSET #define EXT2_SUPER_OFFSET 1024 #endif #define SECTOR_SHIFT 9 /* 512-byte sectors */ #define SECTOR_SIZE (1 << SECTOR_SHIFT) const char *program; /* * Boot block */ extern unsigned char extlinux_bootsect[]; extern unsigned int extlinux_bootsect_len; #define boot_block extlinux_bootsect #define boot_block_len extlinux_bootsect_len /* * Image file */ extern unsigned char extlinux_image[]; extern unsigned int extlinux_image_len; #define boot_image extlinux_image #define boot_image_len extlinux_image_len /* * Common abort function */ void __attribute__((noreturn)) die(const char *msg) { fputs(msg, stderr); exit(1); } /* * read/write wrapper functions */ ssize_t xpread(int fd, void *buf, size_t count, off_t offset) { char *bufp = (char *)buf; ssize_t rv; ssize_t done = 0; while ( count ) { rv = pread(fd, bufp, count, offset); if ( rv == 0 ) { die("short read"); } else if ( rv == -1 ) { if ( errno == EINTR ) { continue; } else { die(strerror(errno)); } } else { bufp += rv; offset += rv; done += rv; count -= rv; } } return done; } ssize_t xpwrite(int fd, const void *buf, size_t count, off_t offset) { const char *bufp = (const char *)buf; ssize_t rv; ssize_t done = 0; while ( count ) { rv = pwrite(fd, bufp, count, offset); if ( rv == 0 ) { die("short write"); } else if ( rv == -1 ) { if ( errno == EINTR ) { continue; } else { die(strerror(errno)); } } else { bufp += rv; offset += rv; done += rv; count -= rv; } } return done; } /* * Produce file map */ int sectmap(int fd, uint32_t *sectors, int nsectors) { unsigned int blksize, blk, nblk; unsigned int i; /* Get block size */ if ( ioctl(fd, FIGETBSZ, &blksize) ) return -1; /* Number of sectors per block */ blksize >>= SECTOR_SHIFT; nblk = 0; while ( nsectors ) { blk = nblk++; dprintf("querying block %u\n", blk); if ( ioctl(fd, FIBMAP, &blk) ) return -1; blk *= blksize; for ( i = 0 ; i < blksize ; i++ ) { if ( !nsectors ) return 0; dprintf("Sector: %10u\n", blk); *sectors++ = blk++; nsectors--; } } return 0; } /* * Get the size of a block device */ 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; }; /* 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; const struct geometry_table *gp; memset(geo, 0, sizeof *geo); if ( !ioctl(devfd, HDIO_GETGEO, &geo) ) { return 0; } 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; return 0; } /* 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); return 0; } } /* 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); return 1; } /* * 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. */ void patch_file_and_bootblock(int fd, int dirfd, int devfd) { struct stat dirst; struct hd_geometry geo; uint32_t *sectp; uint64_t totalbytes, totalsectors; int nsect; unsigned char *p, *patcharea; int i, dw; uint32_t csum; if ( fstat(dirfd, &dirst) ) { perror("fstat dirfd"); exit(255); /* This should never happen */ } 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); totalsectors = totalbytes >> SECTOR_SHIFT; if ( totalsectors >= 65536 ) { set_16(boot_block+bsSectors, 0); } else { set_16(boot_block+bsSectors, totalsectors); } set_32(boot_block+bsHugeSectors, totalsectors); set_16(boot_block+bsBytesPerSec, SECTOR_SIZE); set_16(boot_block+bsSecPerTrack, geo.sectors); set_16(boot_block+bsHeads, geo.heads); set_32(boot_block+bsHiddenSecs, geo.start); /* Construct the boot file */ dprintf("directory inode = %lu\n", (unsigned long) dirst.st_ino); nsect = (boot_image_len+SECTOR_SIZE-1) >> SECTOR_SHIFT; sectp = alloca(sizeof(uint32_t)*nsect); if ( sectmap(fd, sectp, nsect) ) { perror("bmap"); exit(1); } /* First sector need pointer in boot sector */ set_32(boot_block+0x1F8, *sectp++); nsect--; /* Search for LDLINUX_MAGIC to find the patch area */ for ( p = boot_image ; get_32(p) != LDLINUX_MAGIC ; p += 4 ); patcharea = p+8; /* Set up the totals */ dw = boot_image_len >> 2; /* COMPLETE dwords! */ set_16(patcharea, dw); set_16(patcharea+2, nsect); /* Does not include the first sector! */ set_32(patcharea+8, dirst.st_ino); /* "Current" directory */ /* Set the sector pointers */ p = patcharea+12; memset(p, 0, 64*4); while ( nsect-- ) { set_32(p, *sectp++); p += 4; } /* Now produce a checksum */ set_32(patcharea+4, 0); csum = LDLINUX_MAGIC; for ( i = 0, p = boot_image ; i < dw ; i++, p += 4 ) csum -= get_32(p); /* Negative checksum */ set_32(patcharea+4, csum); } /* * 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; if ( xpread(fd, &sb, sizeof sb, EXT2_SUPER_OFFSET) != sizeof sb ) { perror("reading superblock"); return 1; } if ( sb.s_magic != EXT2_SUPER_MAGIC ) { fprintf(stderr, "no ext2/ext3 superblock found on %s\n", device); return 1; } if ( xpwrite(fd, boot_block, boot_block_len, 0) != boot_block_len ) { perror("writing bootblock"); return 1; } return 0; } int install_file(const char *path, int devfd, struct stat *rst) { char *file; int fd = -1, dirfd = -1, flags; struct stat st; asprintf(&file, "%s%sextlinux.sys", path, path[0] && path[strlen(path)-1] == '/' ? "" : "/"); if ( !file ) { 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 { /* If file exist, remove the immutable flag and set u+w mode */ if ( !ioctl(fd, EXT2_IOC_GETFLAGS, &flags) ) { flags &= ~EXT2_IMMUTABLE_FL; ioctl(fd, EXT2_IOC_SETFLAGS, &flags); } if ( !fstat(fd, &st) ) { fchmod(fd, st.st_mode | S_IWUSR); } } close(fd); fd = open(file, O_WRONLY|O_TRUNC|O_CREAT, S_IRUSR|S_IRGRP|S_IROTH); if ( fd < 0 ) { perror(file); goto bail; } /* Write it the first time */ if ( xpwrite(fd, boot_image, boot_image_len, 0) != boot_image_len ) { fprintf(stderr, "%s: write failure on %s\n", program, file); goto bail; } /* Map the file, and patch the initial sector accordingly */ patch_file_and_bootblock(fd, dirfd, devfd); /* Write it again - this relies on the file being overwritten in place! */ if ( xpwrite(fd, boot_image, boot_image_len, 0) != boot_image_len ) { fprintf(stderr, "%s: write failure on %s\n", program, file); goto bail; } /* Attempt to set immutable flag and remove all write access */ /* Only set immutable flag if file is owned by root */ if ( !fstat(fd, &st) ) { fchmod(fd, st.st_mode & (S_IRUSR|S_IRGRP|S_IROTH)); if ( st.st_uid == 0 && !ioctl(fd, EXT2_IOC_GETFLAGS, &flags) ) { flags |= EXT2_IMMUTABLE_FL; ioctl(fd, EXT2_IOC_SETFLAGS, &flags); } } if ( fstat(fd, rst) ) { perror(file); goto bail; } close(dirfd); close(fd); return 0; bail: if ( dirfd >= 0 ) close(dirfd); if ( fd >= 0 ) close(fd); return 1; } int install_loader(const char *path) { struct stat st, dst, fst; struct mntent *mnt = NULL; int devfd, rv; FILE *mtab; if ( stat(path, &st) || !S_ISDIR(st.st_mode) ) { fprintf(stderr, "%s: Not a directory: %s\n", program, path); return 1; } devfd = -1; if ( (mtab = setmntent("/proc/mounts", "r")) ) { while ( (mnt = getmntent(mtab)) ) { if ( (!strcmp(mnt->mnt_type, "ext2") || !strcmp(mnt->mnt_type, "ext3")) && !stat(mnt->mnt_fsname, &dst) && dst.st_rdev == st.st_dev ) { fprintf(stderr, "%s is device %s\n", path, mnt->mnt_fsname); if ( (devfd = open(mnt->mnt_fsname, O_RDWR|O_SYNC)) < 0 ) { fprintf(stderr, "%s: cannot open device %s\n", program, mnt->mnt_fsname); return 1; } break; } } } if ( devfd < 0 ) { /* Didn't find it in /proc/mounts, try /etc/mtab */ if ( (mtab = setmntent("/etc/mtab", "r")) ) { while ( (mnt = getmntent(mtab)) ) { if ( (!strcmp(mnt->mnt_type, "ext2") || !strcmp(mnt->mnt_type, "ext3")) && !stat(mnt->mnt_fsname, &dst) && dst.st_rdev == st.st_dev ) { fprintf(stderr, "%s is device %s\n", path, mnt->mnt_fsname); if ( (devfd = open(mnt->mnt_fsname, O_RDWR|O_SYNC)) < 0 ) { fprintf(stderr, "%s: cannot open device %s\n", program, mnt->mnt_fsname); return 1; } break; } } } } if ( devfd < 0 ) { fprintf(stderr, "%s: cannot find device for path %s\n", program, path); return 1; } install_file(path, devfd, &fst); if ( fst.st_dev != st.st_dev ) { fprintf(stderr, "%s: file system changed under us - aborting!\n", program); return 1; } sync(); rv = install_bootblock(devfd, mnt->mnt_fsname); close(devfd); sync(); endmntent(mtab); if ( rv ) return rv; return 0; } int main(int argc, char *argv[]) { int o; const char *directory; program = argv[0]; while ( (o = getopt_long(argc, argv, short_options, long_options, NULL)) != EOF ) { switch ( o ) { case 'z': opt.heads = 64; opt.sectors = 32; break; case 'S': opt.sectors = strtoul(optarg, NULL, 0); if ( opt.sectors < 1 || opt.sectors > 63 ) { fprintf(stderr, "%s: invalid number of sectors: %u (must be 1-63)\n", program, opt.sectors); exit(EX_USAGE); } break; case 'H': opt.heads = strtoul(optarg, NULL, 0); if ( opt.heads < 1 || opt.heads > 256 ) { fprintf(stderr, "%s: invalid number of heads: %u (must be 1-256)\n", program, opt.heads); exit(EX_USAGE); } break; case 'h': usage(0); break; case 'v': fputs("extlinux " VERSION "\n", stderr); exit(0); default: fprintf(stderr, "%s: Unknown option: %c\n", program, optopt); exit(EX_USAGE); } } directory = argv[optind]; if ( !directory ) usage(EX_USAGE); return install_loader(directory); }