diff options
author | hpa <hpa> | 2004-12-15 10:14:39 +0000 |
---|---|---|
committer | hpa <hpa> | 2004-12-15 10:14:39 +0000 |
commit | 7be70022dd3ae138998fc7185351b9b49ad9a9b2 (patch) | |
tree | 2599a8ede407eec131228fb607a52e22145ba1f9 /unix | |
parent | 4912df96419eff85394da1160bcb19a4dd2c1c59 (diff) | |
download | syslinux-7be70022dd3ae138998fc7185351b9b49ad9a9b2.tar.gz |
Prepping for new 2.20 version: rewrite main syslinux program to support
FAT32 and EDD, and a new cleaner installer infrastructure.
Diffstat (limited to 'unix')
-rw-r--r-- | unix/Makefile | 39 | ||||
-rw-r--r-- | unix/syslinux.c | 487 |
2 files changed, 526 insertions, 0 deletions
diff --git a/unix/Makefile b/unix/Makefile new file mode 100644 index 00000000..9ba5e554 --- /dev/null +++ b/unix/Makefile @@ -0,0 +1,39 @@ +CC = gcc +OPTFLAGS = -g -O +INCLUDES = -I. -I.. -I../libfat +CFLAGS = -W -Wall -D_FILE_OFFSET_BITS=64 $(OPTFLAGS) $(INCLUDES) +LDFLAGS = + +SRCS = syslinux.c ../syslxmod.c ../bootsect_bin.c ../ldlinux_bin.c $(wildcard ../libfat/*.c) +OBJS = $(patsubst %.c,%.o,$(notdir $(SRCS))) + +.SUFFIXES: .c .o .i .s .S + +VPATH = .:..:../libfat + +all: installer + +tidy: + -rm -f *.o *.i *.s *.a .*.d + +clean: tidy + -rm -f syslinux + +spotless: clean + +installer: syslinux + +syslinux: $(OBJS) + $(CC) $(LDFLAGS) -o $@ $^ + +%.o: %.c + $(CC) -Wp,-MT,$@,-MMD,.$@.d $(CFLAGS) -c -o $@ $< +%.i: %.c + $(CC) $(CFLAGS) -E -o $@ $< +%.s: %.c + $(CC) $(CFLAGS) -S -o $@ $< + +-include .*.d + + + diff --git a/unix/syslinux.c b/unix/syslinux.c new file mode 100644 index 00000000..182e1bf9 --- /dev/null +++ b/unix/syslinux.c @@ -0,0 +1,487 @@ +#ident "$Id$" +/* ----------------------------------------------------------------------- * + * + * Copyright 1998-2004 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. + * + * ----------------------------------------------------------------------- */ + +/* + * syslinux.c - Linux installer program for SYSLINUX + * + * This program ought to be portable. I hope so, at least. + * + * This is an alternate version of the installer which doesn't require + * mtools, but requires root privilege. + */ + +#ifdef __KLIBC__ +# define DO_DIRECT_MOUNT 1 /* Call mount(2) directly */ +#else +# define DO_DIRECT_MOUNT 0 /* Call /bin/mount instead */ +#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 "syslinux.h" +#include "libfat.h" + +#if DO_DIRECT_MOUNT + +# include <linux/loop.h> + +#else + +# include <paths.h> +# ifndef _PATH_MOUNT +# define _PATH_MOUNT "/bin/mount" +# endif +# ifndef _PATH_UMOUNT +# define _PATH_UMOUNT "/bin/umount" +# endif + +#endif + +const char *program; /* Name of program */ +const char *device; /* Device to install to */ +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)) usage(void) +{ + fprintf(stderr, "Usage: %s [-sf] [-o offset] device\n", program); + exit(1); +} + +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); +} + +/* + * read/write wrapper functions + */ +ssize_t xpread(int fd, void *buf, size_t count, off_t offset) +{ + ssize_t rv; + ssize_t done = 0; + + while ( count ) { + rv = pread(fd, buf, count, offset); + if ( rv == 0 ) { + die("short read"); + } else if ( rv == -1 ) { + if ( errno == EINTR ) { + continue; + } else { + perror(program); + exit(1); + } + } else { + offset += rv; + done += rv; + count -= rv; + } + } + + return done; +} + +ssize_t xpwrite(int fd, void *buf, size_t count, off_t offset) +{ + ssize_t rv; + ssize_t done = 0; + + while ( count ) { + rv = pwrite(fd, buf, count, offset); + if ( rv == 0 ) { + die("short write"); + } else if ( rv == -1 ) { + if ( errno == EINTR ) { + continue; + } else { + perror(program); + exit(1); + } + } else { + offset += rv; + done += rv; + count -= rv; + } + } + + return done; +} + +/* + * Version of the read function suitable for libfat + */ +int libfat_xpread(void *pp, void *buf, size_t secsize, libfat_sector_t sector) +{ + off_t offset = (off_t)sector * secsize; + return xpread((int)pp, buf, secsize, offset); +} + +int main(int argc, char *argv[]) +{ + static unsigned char sectbuf[512]; + unsigned char *dp; + const unsigned char *cdp; + int dev_fd, fd; + struct stat st; + int nb, left; + int err = 0; + pid_t f, w; + int status; + char mntname[64], devfdname[64]; + char *ldlinux_name, **argp, *opt; + int force = 0; /* -f (force) option */ + off_t offset = 0; /* -o (offset) option */ + struct libfat_filesystem *fs; + libfat_sector_t s, *secp, sectors[65]; /* 65 is maximum possible */ + int32_t ldlinux_cluster; + int nsectors; + + (void)argc; /* Unused */ + + program = argv[0]; + mypid = getpid(); + + device = NULL; + + umask(077); + + for ( argp = argv+1 ; *argp ; argp++ ) { + if ( **argp == '-' ) { + opt = *argp + 1; + if ( !*opt ) + usage(); + + while ( *opt ) { + if ( *opt == 's' ) { + syslinux_make_stupid(); /* Use "safe, slow and stupid" code */ + } else if ( *opt == 'f' ) { + force = 1; /* Force install */ + } else if ( *opt == 'o' && argp[1] ) { + offset = (off_t)strtoull(*++argp, NULL, 0); /* Byte offset */ + } else { + usage(); + } + opt++; + } + } else { + if ( device ) + usage(); + device = *argp; + } + } + + if ( !device ) + usage(); + + /* + * First make sure we can open the device at all, and that we have + * read/write permission. + */ + dev_fd = open(device, O_RDWR); + if ( dev_fd < 0 || fstat(dev_fd, &st) < 0 ) { + perror(device); + exit(1); + } + + if ( !force && !S_ISBLK(st.st_mode) && !S_ISREG(st.st_mode) ) { + die("not a block device or regular file (use -f to override)"); + } + + if ( !force && offset != 0 && !S_ISREG(st.st_mode) ) { + die("not a regular file and an offset specified (use -f to override)"); + } + + xpread(dev_fd, sectbuf, 512, offset); + fsync(dev_fd); + + /* + * Check to see that what we got was indeed an MS-DOS boot sector/superblock + */ + if(!syslinux_check_bootsect(sectbuf,device)) { + 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("/tmp") ) { + perror(program); + 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 /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 DO_DIRECT_MOUNT + if ( S_ISREG(st.st_mode) ) { + /* It's file, need to mount it loopback */ + unsigned int n = 0; + struct loop_info64 loopinfo; + + 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 = offset, + ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo)) ) + die("cannot set up loopback device"); + } + } else { + snprintf(devfdname, sizeof devfdname, "/proc/%lu/fd/%d", + (unsigned long)mypid, dev_fd); + } + + if ( mount(devfdname, mntpath, "msdos", + MS_NOEXEC|MS_NOSUID, "umask=077,quiet") ) + die("could not mount filesystem"); + +#else + + snprintf(devfdname, sizeof devfdname, "/proc/%lu/fd/%d", + (unsigned long)mypid, dev_fd); + + f = fork(); + if ( f < 0 ) { + perror(program); + rmdir(mntpath); + exit(1); + } else if ( f == 0 ) { + char mnt_opts[128]; + if ( S_ISREG(st.st_mode) ) { + snprintf(mnt_opts, sizeof mnt_opts, "rw,nodev,noexec,loop,offset=%llu,umask=077,quiet", + (unsigned long long)offset); + } else { + snprintf(mnt_opts, sizeof mnt_opts, "rw,nodev,noexec,umask=077,quiet"); + } + execl(_PATH_MOUNT, _PATH_MOUNT, "-t", "msdos", "-o", mnt_opts,\ + devfdname, mntpath, NULL); + _exit(255); /* execl failed */ + } + + w = waitpid(f, &status, 0); + if ( w != f || status ) { + rmdir(mntpath); + exit(1); /* Mount failed */ + } + +#endif + } + + ldlinux_name = alloca(strlen(mntpath)+13); + if ( !ldlinux_name ) { + perror(program); + err = 1; + goto umount; + } + sprintf(ldlinux_name, "%s/ldlinux.sys", mntpath); + + unlink(ldlinux_name); + fd = open(ldlinux_name, O_WRONLY|O_CREAT|O_TRUNC, 0444); + if ( fd < 0 ) { + perror(device); + err = 1; + goto umount; + } + + cdp = syslinux_ldlinux; + left = syslinux_ldlinux_len; + while ( left ) { + nb = write(fd, cdp, left); + if ( nb == -1 && errno == EINTR ) + continue; + else if ( nb <= 0 ) { + perror(device); + err = 1; + goto umount; + } + + dp += nb; + left -= nb; + } + + /* + * I don't understand why I need this. Does the DOS filesystems + * not honour the mode passed to open()? + */ + fchmod(fd, 0400); + + close(fd); + + sync(); + +umount: +#if DO_DIRECT_MOUNT + + 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 + + f = fork(); + 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 + + sync(); + rmdir(mntpath); + + if ( err ) + exit(err); + + /* + * Now, use libfat to create a block map. This probably + * should be changed to use ioctl(...,FIBMAP,...) since + * this is supposed to be a simple, privileged version + * of the installer. + */ + fs = libfat_open(libfat_xpread, (void *)dev_fd); + ldlinux_cluster = libfat_searchdir(fs, 0, "LDLINUX SYS", NULL); + secp = sectors; + nsectors = 0; + s = libfat_clustertosector(fs, ldlinux_cluster); + while ( s && nsectors < 65 ) { + *secp++ = s; + nsectors++; + s = libfat_nextsector(fs, s); + } + libfat_close(fs); + + /* + * Patch ldlinux.sys and the boot sector + */ + syslinux_patch(sectors, nsectors); + + /* + * Write the now-patched first sector of ldlinux.sys + */ + xpwrite(dev_fd, syslinux_ldlinux, 512, offset + ((off_t)sectors[0] << 9)); + + /* + * To finish up, write the boot sector + */ + + /* Read the superblock again since it might have changed while mounted */ + xpread(dev_fd, sectbuf, 512, offset); + + /* Copy the syslinux code into the boot sector */ + syslinux_make_bootsect(sectbuf); + + /* Write new boot sector */ + xpwrite(dev_fd, sectbuf, 512, offset); + + close(dev_fd); + sync(); + + /* Done! */ + + return 0; +} + |