/* ----------------------------------------------------------------------- * * * Copyright 1998-2008 H. Peter Anvin - All Rights Reserved * Copyright 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 program now requires mtools. It turned out to be a lot * easier to deal with than dealing with needing mount privileges. * We need device write permission anyway. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "syslinux.h" #include "libfat.h" #include "setadv.h" #include "syslxopt.h" char *program; /* Name of program */ pid_t mypid; void __attribute__ ((noreturn)) die(const char *msg) { fprintf(stderr, "%s: %s\n", program, msg); exit(1); } void __attribute__ ((noreturn)) die_err(const char *msg) { fprintf(stderr, "%s: %s: %s\n", program, msg, strerror(errno)); 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; } /* * Version of the read function suitable for libfat */ int libfat_xpread(intptr_t pp, void *buf, size_t secsize, libfat_sector_t sector) { off_t offset = (off_t) sector * secsize + opt.offset; return xpread(pp, buf, secsize, offset); } int main(int argc, char *argv[]) { static unsigned char sectbuf[SECTOR_SIZE]; int dev_fd; struct stat st; int status; const char *tmpdir; char *mtools_conf; int mtc_fd; FILE *mtc, *mtp; struct libfat_filesystem *fs; libfat_sector_t s, *secp; libfat_sector_t *sectors; int32_t ldlinux_cluster; int nsectors; const char *errmsg; int ldlinux_sectors, patch_sectors; int i; (void)argc; /* Unused */ mypid = getpid(); program = argv[0]; parse_options(argc, argv, MODE_SYSLINUX); if (!opt.device) usage(EX_USAGE, MODE_SYSLINUX); if (opt.sectors || opt.heads || opt.reset_adv || opt.set_once || (opt.update_only > 0) || opt.menu_save) { fprintf(stderr, "At least one specified option not yet implemented" " for this installer.\n"); exit(1); } /* * Temp directory of choice... */ tmpdir = getenv("TMPDIR"); if (!tmpdir) { #ifdef P_tmpdir tmpdir = P_tmpdir; #elif defined(_PATH_TMP) tmpdir = _PATH_TMP; #else tmpdir = "/tmp"; #endif } /* * 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) { die_err(opt.device); exit(1); } if (!opt.force && !S_ISBLK(st.st_mode) && !S_ISREG(st.st_mode)) { fprintf(stderr, "%s: not a block device or regular file (use -f to override)\n", opt.device); exit(1); } xpread(dev_fd, sectbuf, SECTOR_SIZE, opt.offset); /* * Check to see that what we got was indeed an MS-DOS boot sector/superblock */ if ((errmsg = syslinux_check_bootsect(sectbuf))) { die(errmsg); } /* * Create an mtools configuration file */ if (asprintf(&mtools_conf, "%s//syslinux-mtools-XXXXXX", tmpdir) < 0 || !mtools_conf) die_err(tmpdir); mtc_fd = mkstemp(mtools_conf); if (mtc_fd < 0 || !(mtc = fdopen(mtc_fd, "w"))) die_err(mtools_conf); fprintf(mtc, /* These are needed for some flash memories */ "MTOOLS_SKIP_CHECK=1\n" "MTOOLS_FAT_COMPATIBILITY=1\n" "drive s:\n" " file=\"/proc/%lu/fd/%d\"\n" " offset=%llu\n", (unsigned long)mypid, dev_fd, (unsigned long long)opt.offset); if (ferror(mtc) || fclose(mtc)) die_err(mtools_conf); /* * Run mtools to create the LDLINUX.SYS file */ if (setenv("MTOOLSRC", mtools_conf, 1)) { perror(program); exit(1); } /* * Create a vacuous ADV in memory. This should be smarter. */ syslinux_reset_adv(syslinux_adv); /* This command may fail legitimately */ status = system("mattrib -h -r -s s:/ldlinux.sys 2>/dev/null"); (void)status; /* Keep _FORTIFY_SOURCE happy */ mtp = popen("mcopy -D o -D O -o - s:/ldlinux.sys", "w"); if (!mtp || fwrite(syslinux_ldlinux, 1, syslinux_ldlinux_len, mtp) != syslinux_ldlinux_len || fwrite(syslinux_adv, 1, 2 * ADV_SIZE, mtp) != 2 * ADV_SIZE || (status = pclose(mtp), !WIFEXITED(status) || WEXITSTATUS(status))) { die("failed to create ldlinux.sys"); } /* * Now, use libfat to create a block map */ ldlinux_sectors = (syslinux_ldlinux_len + 2 * ADV_SIZE + SECTOR_SIZE - 1) >> SECTOR_SHIFT; sectors = calloc(ldlinux_sectors, sizeof *sectors); fs = libfat_open(libfat_xpread, dev_fd); ldlinux_cluster = libfat_searchdir(fs, 0, "LDLINUX SYS", NULL); secp = sectors; nsectors = 0; s = libfat_clustertosector(fs, ldlinux_cluster); while (s && nsectors < ldlinux_sectors) { *secp++ = s; nsectors++; s = libfat_nextsector(fs, s); } libfat_close(fs); /* Patch ldlinux.sys and the boot sector */ i = syslinux_patch(sectors, nsectors, opt.stupid_mode, opt.raid_mode, opt.directory, 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, syslinux_ldlinux + i * SECTOR_SIZE, SECTOR_SIZE, opt.offset + ((off_t) sectors[i] << SECTOR_SHIFT)); } /* Move ldlinux.sys to the desired location */ if (opt.directory) { char target_file[4096], command[5120]; char *cp = target_file, *ep = target_file + sizeof target_file - 16; const char *sd; int slash = 1; cp += sprintf(cp, "'s:/"); for (sd = opt.directory; *sd; sd++) { if (*sd == '/' || *sd == '\\') { if (slash) continue; /* Remove duplicated slashes */ slash = 1; } else if (*sd == '\'' || *sd == '!') { slash = 0; if (cp < ep) *cp++ = '\''; if (cp < ep) *cp++ = '\\'; if (cp < ep) *cp++ = *sd; if (cp < ep) *cp++ = '\''; continue; } else { slash = 0; } if (cp < ep) *cp++ = *sd; } if (!slash) *cp++ = '/'; strcpy(cp, "ldlinux.sys'"); /* This command may fail legitimately */ sprintf(command, "mattrib -h -r -s %s 2>/dev/null", target_file); status = system(command); (void)status; /* Keep _FORTIFY_SOURCE happy */ sprintf(command, "mmove -D o -D O s:/ldlinux.sys %s", target_file); status = system(command); if (!WIFEXITED(status) || WEXITSTATUS(status)) { fprintf(stderr, "%s: warning: unable to move ldlinux.sys\n", program); status = system("mattrib +r +h +s s:/ldlinux.sys"); } else { sprintf(command, "mattrib +r +h +s %s", target_file); status = system(command); } } else { status = system("mattrib +r +h +s s:/ldlinux.sys"); } if (!WIFEXITED(status) || WEXITSTATUS(status)) { fprintf(stderr, "%s: warning: failed to set system bit on ldlinux.sys\n", program); } /* * Cleanup */ unlink(mtools_conf); /* * 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); /* Write new boot sector */ xpwrite(dev_fd, sectbuf, SECTOR_SIZE, opt.offset); close(dev_fd); sync(); /* Done! */ return 0; }