diff options
Diffstat (limited to 'linux/syslinux.c')
-rwxr-xr-x | linux/syslinux.c | 535 |
1 files changed, 535 insertions, 0 deletions
diff --git a/linux/syslinux.c b/linux/syslinux.c new file mode 100755 index 0000000..912de71 --- /dev/null +++ b/linux/syslinux.c @@ -0,0 +1,535 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 1998-2008 H. Peter Anvin - All Rights Reserved + * Copyright 2009-2010 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. + * + * ----------------------------------------------------------------------- */ + +/* + * syslinux.c - Linux installer program for SYSLINUX + * + * This is Linux-specific by now. + * + * This is an alternate version of the installer which doesn't require + * mtools, but requires root privilege. + */ + +/* + * If DO_DIRECT_MOUNT is 0, call mount(8) + * If DO_DIRECT_MOUNT is 1, call mount(2) + */ +#ifdef __KLIBC__ +# define DO_DIRECT_MOUNT 1 +#else +# define DO_DIRECT_MOUNT 0 /* glibc has broken losetup ioctls */ +#endif + +#define _GNU_SOURCE +#define _XOPEN_SOURCE 500 /* For pread() pwrite() */ +#define _FILE_OFFSET_BITS 64 +#include <alloca.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/mount.h> + +#include "linuxioctl.h" + +#include <paths.h> +#ifndef _PATH_MOUNT +# define _PATH_MOUNT "/bin/mount" +#endif +#ifndef _PATH_UMOUNT +# define _PATH_UMOUNT "/bin/umount" +#endif +#ifndef _PATH_TMP +# define _PATH_TMP "/tmp/" +#endif + +#include "syslinux.h" + +#if DO_DIRECT_MOUNT +# include <linux/loop.h> +#endif + +#include <getopt.h> +#include <sysexits.h> +#include "syslxcom.h" +#include "syslxfs.h" +#include "setadv.h" +#include "syslxopt.h" /* unified options */ + +extern const char *program; /* Name of program */ + +pid_t mypid; +char *mntpath = NULL; /* Path on which to mount */ + +#if DO_DIRECT_MOUNT +int loop_fd = -1; /* Loop device */ +#endif + +void __attribute__ ((noreturn)) die(const char *msg) +{ + fprintf(stderr, "%s: %s\n", program, msg); + +#if DO_DIRECT_MOUNT + if (loop_fd != -1) { + ioctl(loop_fd, LOOP_CLR_FD, 0); /* Free loop device */ + close(loop_fd); + loop_fd = -1; + } +#endif + + if (mntpath) + unlink(mntpath); + + exit(1); +} + +/* + * Mount routine + */ +int do_mount(int dev_fd, int *cookie, const char *mntpath, const char *fstype) +{ + struct stat st; + + (void)cookie; + + if (fstat(dev_fd, &st) < 0) + return errno; + +#if DO_DIRECT_MOUNT + { + if (!S_ISBLK(st.st_mode)) { + /* It's file, need to mount it loopback */ + unsigned int n = 0; + struct loop_info64 loopinfo; + int loop_fd; + + for (n = 0; loop_fd < 0; n++) { + snprintf(devfdname, sizeof devfdname, "/dev/loop%u", n); + loop_fd = open(devfdname, O_RDWR); + if (loop_fd < 0 && errno == ENOENT) { + die("no available loopback device!"); + } + if (ioctl(loop_fd, LOOP_SET_FD, (void *)dev_fd)) { + close(loop_fd); + loop_fd = -1; + if (errno != EBUSY) + die("cannot set up loopback device"); + else + continue; + } + + if (ioctl(loop_fd, LOOP_GET_STATUS64, &loopinfo) || + (loopinfo.lo_offset = opt.offset, + ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo))) + die("cannot set up loopback device"); + } + + *cookie = loop_fd; + } else { + snprintf(devfdname, sizeof devfdname, "/proc/%lu/fd/%d", + (unsigned long)mypid, dev_fd); + *cookie = -1; + } + + return mount(devfdname, mntpath, fstype, + MS_NOEXEC | MS_NOSUID, "umask=077,quiet"); + } +#else + { + char devfdname[128], mnt_opts[128]; + pid_t f, w; + int status; + + snprintf(devfdname, sizeof devfdname, "/proc/%lu/fd/%d", + (unsigned long)mypid, dev_fd); + + f = fork(); + if (f < 0) { + return -1; + } else if (f == 0) { + if (!S_ISBLK(st.st_mode)) { + snprintf(mnt_opts, sizeof mnt_opts, + "rw,nodev,noexec,loop,offset=%llu,umask=077,quiet", + (unsigned long long)opt.offset); + } else { + snprintf(mnt_opts, sizeof mnt_opts, + "rw,nodev,noexec,umask=077,quiet"); + } + execl(_PATH_MOUNT, _PATH_MOUNT, "-t", fstype, "-o", mnt_opts, + devfdname, mntpath, NULL); + _exit(255); /* execl failed */ + } + + w = waitpid(f, &status, 0); + return (w != f || status) ? -1 : 0; + } +#endif +} + +/* + * umount routine + */ +void do_umount(const char *mntpath, int cookie) +{ +#if DO_DIRECT_MOUNT + int loop_fd = cookie; + + if (umount2(mntpath, 0)) + die("could not umount path"); + + if (loop_fd != -1) { + ioctl(loop_fd, LOOP_CLR_FD, 0); /* Free loop device */ + close(loop_fd); + loop_fd = -1; + } +#else + pid_t f = fork(); + pid_t w; + int status; + (void)cookie; + + if (f < 0) { + perror("fork"); + exit(1); + } else if (f == 0) { + execl(_PATH_UMOUNT, _PATH_UMOUNT, mntpath, NULL); + } + + w = waitpid(f, &status, 0); + if (w != f || status) { + exit(1); + } +#endif +} + +/* + * Modify the ADV of an existing installation + */ +int modify_existing_adv(const char *path) +{ + if (opt.reset_adv) + syslinux_reset_adv(syslinux_adv); + else if (read_adv(path, "ldlinux.sys") < 0) + return 1; + + if (modify_adv() < 0) + return 1; + + if (write_adv(path, "ldlinux.sys") < 0) + return 1; + + return 0; +} + +int do_open_file(char *name) +{ + int fd; + + if ((fd = open(name, O_RDONLY)) >= 0) { + uint32_t zero_attr = 0; + ioctl(fd, FAT_IOCTL_SET_ATTRIBUTES, &zero_attr); + close(fd); + } + + unlink(name); + fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0444); + if (fd < 0) + perror(name); + + return fd; +} + +int main(int argc, char *argv[]) +{ + static unsigned char sectbuf[SECTOR_SIZE]; + int dev_fd, fd; + struct stat st; + int err = 0; + char mntname[128]; + char *ldlinux_name; + char *ldlinux_path; + char *subdir; + sector_t *sectors = NULL; + int ldlinux_sectors = (boot_image_len + SECTOR_SIZE - 1) >> SECTOR_SHIFT; + const char *errmsg; + int mnt_cookie; + int patch_sectors; + int i, rv; + + mypid = getpid(); + umask(077); + parse_options(argc, argv, MODE_SYSLINUX); + + /* Note: subdir is guaranteed to start and end in / */ + if (opt.directory && opt.directory[0]) { + int len = strlen(opt.directory); + int rv = asprintf(&subdir, "%s%s%s", + opt.directory[0] == '/' ? "" : "/", + opt.directory, + opt.directory[len-1] == '/' ? "" : "/"); + if (rv < 0 || !subdir) { + perror(program); + exit(1); + } + } else { + subdir = "/"; + } + + if (!opt.device || opt.install_mbr || opt.activate_partition) + usage(EX_USAGE, MODE_SYSLINUX); + + /* + * First make sure we can open the device at all, and that we have + * read/write permission. + */ + dev_fd = open(opt.device, O_RDWR); + if (dev_fd < 0 || fstat(dev_fd, &st) < 0) { + perror(opt.device); + exit(1); + } + + if (!S_ISBLK(st.st_mode) && !S_ISREG(st.st_mode) && !S_ISCHR(st.st_mode)) { + die("not a device or regular file"); + } + + if (opt.offset && S_ISBLK(st.st_mode)) { + die("can't combine an offset with a block device"); + } + + xpread(dev_fd, sectbuf, SECTOR_SIZE, opt.offset); + fsync(dev_fd); + + /* + * Check to see that what we got was indeed an FAT/NTFS + * boot sector/superblock + */ + if ((errmsg = syslinux_check_bootsect(sectbuf, &fs_type))) { + fprintf(stderr, "%s: %s\n", opt.device, errmsg); + exit(1); + } + + /* + * Now mount the device. + */ + if (geteuid()) { + die("This program needs root privilege"); + } else { + int i = 0; + struct stat dst; + int rv; + + /* We're root or at least setuid. + Make a temp dir and pass all the gunky options to mount. */ + + if (chdir(_PATH_TMP)) { + fprintf(stderr, "%s: Cannot access the %s directory.\n", + program, _PATH_TMP); + exit(1); + } +#define TMP_MODE (S_IXUSR|S_IWUSR|S_IXGRP|S_IWGRP|S_IWOTH|S_IXOTH|S_ISVTX) + + if (stat(".", &dst) || !S_ISDIR(dst.st_mode) || + (dst.st_mode & TMP_MODE) != TMP_MODE) { + die("possibly unsafe " _PATH_TMP " permissions"); + } + + for (i = 0;; i++) { + snprintf(mntname, sizeof mntname, "syslinux.mnt.%lu.%d", + (unsigned long)mypid, i); + + if (lstat(mntname, &dst) != -1 || errno != ENOENT) + continue; + + rv = mkdir(mntname, 0000); + + if (rv == -1) { + if (errno == EEXIST || errno == EINTR) + continue; + perror(program); + exit(1); + } + + if (lstat(mntname, &dst) || dst.st_mode != (S_IFDIR | 0000) || + dst.st_uid != 0) { + die("someone is trying to symlink race us!"); + } + break; /* OK, got something... */ + } + + mntpath = mntname; + } + + if (fs_type == VFAT) { + if (do_mount(dev_fd, &mnt_cookie, mntpath, "vfat") && + do_mount(dev_fd, &mnt_cookie, mntpath, "msdos")) { + rmdir(mntpath); + die("failed on mounting fat volume"); + } + } else if (fs_type == NTFS) { + if (do_mount(dev_fd, &mnt_cookie, mntpath, "ntfs-3g")) { + rmdir(mntpath); + die("failed on mounting ntfs volume"); + } + } + + ldlinux_path = alloca(strlen(mntpath) + strlen(subdir) + 1); + sprintf(ldlinux_path, "%s%s", mntpath, subdir); + + ldlinux_name = alloca(strlen(ldlinux_path) + 14); + if (!ldlinux_name) { + perror(program); + err = 1; + goto umount; + } + sprintf(ldlinux_name, "%sldlinux.sys", ldlinux_path); + + /* update ADV only ? */ + if (opt.update_only == -1) { + if (opt.reset_adv || opt.set_once) { + modify_existing_adv(ldlinux_path); + do_umount(mntpath, mnt_cookie); + sync(); + rmdir(mntpath); + exit(0); + } else if (opt.update_only && !syslinux_already_installed(dev_fd)) { + fprintf(stderr, "%s: no previous syslinux boot sector found\n", + argv[0]); + exit(1); + } else { + fprintf(stderr, "%s: please specify --install or --update for the future\n", argv[0]); + opt.update_only = 0; + } + } + + /* Read a pre-existing ADV, if already installed */ + if (opt.reset_adv) + syslinux_reset_adv(syslinux_adv); + else if (read_adv(ldlinux_path, "ldlinux.sys") < 0) + syslinux_reset_adv(syslinux_adv); + if (modify_adv() < 0) + exit(1); + + fd = do_open_file(ldlinux_name); + if (fd < 0) { + err = 1; + goto umount; + } + + /* Write it the first time */ + if (xpwrite(fd, (const char _force *)boot_image, boot_image_len, 0) + != (int)boot_image_len || + xpwrite(fd, syslinux_adv, 2 * ADV_SIZE, + boot_image_len) != 2 * ADV_SIZE) { + fprintf(stderr, "%s: write failure on %s\n", program, ldlinux_name); + exit(1); + } + + fsync(fd); + /* + * Set the attributes + */ + { + uint32_t attr = 0x07; /* Hidden+System+Readonly */ + ioctl(fd, FAT_IOCTL_SET_ATTRIBUTES, &attr); + } + + /* + * Create a block map. + */ + ldlinux_sectors += 2; /* 2 ADV sectors */ + sectors = calloc(ldlinux_sectors, sizeof *sectors); + if (sectmap(fd, sectors, ldlinux_sectors)) { + perror("bmap"); + exit(1); + } + close(fd); + sync(); + + sprintf(ldlinux_name, "%sldlinux.c32", ldlinux_path); + fd = do_open_file(ldlinux_name); + if (fd < 0) { + err = 1; + goto umount; + } + + 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, ldlinux_name); + exit(1); + } + + fsync(fd); + /* + * Set the attributes + */ + { + uint32_t attr = 0x07; /* Hidden+System+Readonly */ + ioctl(fd, FAT_IOCTL_SET_ATTRIBUTES, &attr); + } + + close(fd); + sync(); + +umount: + do_umount(mntpath, mnt_cookie); + sync(); + rmdir(mntpath); + + if (err) + exit(err); + + /* + * Patch ldlinux.sys and the boot sector + */ + i = syslinux_patch(sectors, ldlinux_sectors, opt.stupid_mode, + opt.raid_mode, subdir, NULL); + patch_sectors = (i + SECTOR_SIZE - 1) >> SECTOR_SHIFT; + + /* + * Write the now-patched first sectors of ldlinux.sys + */ + for (i = 0; i < patch_sectors; i++) { + xpwrite(dev_fd, + (const char _force *)boot_image + i * SECTOR_SIZE, + SECTOR_SIZE, + opt.offset + ((off_t) sectors[i] << SECTOR_SHIFT)); + } + + /* + * To finish up, write the boot sector + */ + + /* Read the superblock again since it might have changed while mounted */ + xpread(dev_fd, sectbuf, SECTOR_SIZE, opt.offset); + + /* Copy the syslinux code into the boot sector */ + syslinux_make_bootsect(sectbuf, fs_type); + + /* Write new boot sector */ + xpwrite(dev_fd, sectbuf, SECTOR_SIZE, opt.offset); + + close(dev_fd); + sync(); + + /* Done! */ + + return 0; +} |