Home | History | Annotate | Download | only in mtools
      1 /* ----------------------------------------------------------------------- *
      2  *
      3  *   Copyright 1998-2008 H. Peter Anvin - All Rights Reserved
      4  *   Copyright 2010 Intel Corporation; author: H. Peter Anvin
      5  *
      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.
     11  *
     12  * ----------------------------------------------------------------------- */
     13 
     14 /*
     15  * syslinux.c - Linux installer program for SYSLINUX
     16  *
     17  * This program now requires mtools.  It turned out to be a lot
     18  * easier to deal with than dealing with needing mount privileges.
     19  * We need device write permission anyway.
     20  */
     21 
     22 #define _GNU_SOURCE
     23 #include <alloca.h>
     24 #include <errno.h>
     25 #include <fcntl.h>
     26 #include <getopt.h>
     27 #include <inttypes.h>
     28 #include <mntent.h>
     29 #include <paths.h>
     30 #include <stdio.h>
     31 #include <string.h>
     32 #include <stdlib.h>
     33 #include <sysexits.h>
     34 #include <syslog.h>
     35 #include <unistd.h>
     36 #include <sys/types.h>
     37 #include <sys/stat.h>
     38 #include <sys/wait.h>
     39 
     40 #include "syslinux.h"
     41 #include "libfat.h"
     42 #include "setadv.h"
     43 #include "syslxopt.h"
     44 #include "syslxfs.h"
     45 
     46 char *program;			/* Name of program */
     47 pid_t mypid;
     48 
     49 void __attribute__ ((noreturn)) die(const char *msg)
     50 {
     51     fprintf(stderr, "%s: %s\n", program, msg);
     52     exit(1);
     53 }
     54 
     55 void __attribute__ ((noreturn)) die_err(const char *msg)
     56 {
     57     fprintf(stderr, "%s: %s: %s\n", program, msg, strerror(errno));
     58     exit(1);
     59 }
     60 
     61 /*
     62  * read/write wrapper functions
     63  */
     64 ssize_t xpread(int fd, void *buf, size_t count, off_t offset)
     65 {
     66     char *bufp = (char *)buf;
     67     ssize_t rv;
     68     ssize_t done = 0;
     69 
     70     while (count) {
     71 	rv = pread(fd, bufp, count, offset);
     72 	if (rv == 0) {
     73 	    die("short read");
     74 	} else if (rv == -1) {
     75 	    if (errno == EINTR) {
     76 		continue;
     77 	    } else {
     78 		die(strerror(errno));
     79 	    }
     80 	} else {
     81 	    bufp += rv;
     82 	    offset += rv;
     83 	    done += rv;
     84 	    count -= rv;
     85 	}
     86     }
     87 
     88     return done;
     89 }
     90 
     91 ssize_t xpwrite(int fd, const void *buf, size_t count, off_t offset)
     92 {
     93     const char *bufp = (const char *)buf;
     94     ssize_t rv;
     95     ssize_t done = 0;
     96 
     97     while (count) {
     98 	rv = pwrite(fd, bufp, count, offset);
     99 	if (rv == 0) {
    100 	    die("short write");
    101 	} else if (rv == -1) {
    102 	    if (errno == EINTR) {
    103 		continue;
    104 	    } else {
    105 		die(strerror(errno));
    106 	    }
    107 	} else {
    108 	    bufp += rv;
    109 	    offset += rv;
    110 	    done += rv;
    111 	    count -= rv;
    112 	}
    113     }
    114 
    115     return done;
    116 }
    117 
    118 /*
    119  * Version of the read function suitable for libfat
    120  */
    121 int libfat_xpread(intptr_t pp, void *buf, size_t secsize,
    122 		  libfat_sector_t sector)
    123 {
    124     off_t offset = (off_t) sector * secsize + opt.offset;
    125     return xpread(pp, buf, secsize, offset);
    126 }
    127 
    128 static int move_file(char *filename)
    129 {
    130     char target_file[4096], command[5120];
    131     char *cp = target_file, *ep = target_file + sizeof target_file - 16;
    132     const char *sd;
    133     int slash = 1;
    134     int status;
    135 
    136     cp += sprintf(cp, "'s:/");
    137     for (sd = opt.directory; *sd; sd++) {
    138 	if (*sd == '/' || *sd == '\\') {
    139 	    if (slash)
    140 		continue;	/* Remove duplicated slashes */
    141 	    slash = 1;
    142 	} else if (*sd == '\'' || *sd == '!') {
    143 	    slash = 0;
    144 	    if (cp < ep)
    145 		*cp++ = '\'';
    146 	    if (cp < ep)
    147 		*cp++ = '\\';
    148 	    if (cp < ep)
    149 		*cp++ = *sd;
    150 	    if (cp < ep)
    151 		*cp++ = '\'';
    152 	    continue;
    153 	} else {
    154 	    slash = 0;
    155 	}
    156 
    157 	if (cp < ep)
    158 	    *cp++ = *sd;
    159     }
    160     if (!slash)
    161 	*cp++ = '/';
    162     sprintf(cp, "%s'", filename);
    163 
    164     /* This command may fail legitimately */
    165     sprintf(command, "mattrib -h -r -s %s 2>/dev/null", target_file);
    166     status = system(command);
    167     (void)status;		/* Keep _FORTIFY_SOURCE happy */
    168 
    169     sprintf(command, "mmove -D o -D O s:/%s %s", filename, target_file);
    170     status = system(command);
    171 
    172     if (!WIFEXITED(status) || WEXITSTATUS(status)) {
    173 	fprintf(stderr,
    174 		"%s: warning: unable to move %s\n", program, filename);
    175 
    176 	sprintf(command, "mattrib +r +h +s s:/%s", filename);
    177 	status = system(command);
    178     } else {
    179 	sprintf(command, "mattrib +r +h +s %s", target_file);
    180 	status = system(command);
    181     }
    182 
    183     return status;
    184 }
    185 
    186 int main(int argc, char *argv[])
    187 {
    188     static unsigned char sectbuf[SECTOR_SIZE];
    189     int dev_fd;
    190     struct stat st;
    191     int status;
    192     const char *tmpdir;
    193     char *mtools_conf;
    194     int mtc_fd;
    195     FILE *mtc, *mtp;
    196     struct libfat_filesystem *fs;
    197     libfat_sector_t s, *secp;
    198     libfat_sector_t *sectors;
    199     int32_t ldlinux_cluster;
    200     int nsectors;
    201     const char *errmsg;
    202     int ldlinux_sectors, patch_sectors;
    203     int i;
    204 
    205     (void)argc;			/* Unused */
    206 
    207     mypid = getpid();
    208     program = argv[0];
    209 
    210     parse_options(argc, argv, MODE_SYSLINUX);
    211 
    212     if (!opt.device)
    213 	usage(EX_USAGE, MODE_SYSLINUX);
    214 
    215     if (opt.sectors || opt.heads || opt.reset_adv || opt.set_once
    216 	|| (opt.update_only > 0) || opt.menu_save) {
    217 	fprintf(stderr,
    218 		"At least one specified option not yet implemented"
    219 		" for this installer.\n");
    220 	exit(1);
    221     }
    222 
    223     /*
    224      * Temp directory of choice...
    225      */
    226     tmpdir = getenv("TMPDIR");
    227     if (!tmpdir) {
    228 #ifdef P_tmpdir
    229 	tmpdir = P_tmpdir;
    230 #elif defined(_PATH_TMP)
    231 	tmpdir = _PATH_TMP;
    232 #else
    233 	tmpdir = "/tmp";
    234 #endif
    235     }
    236 
    237     /*
    238      * First make sure we can open the device at all, and that we have
    239      * read/write permission.
    240      */
    241     dev_fd = open(opt.device, O_RDWR);
    242     if (dev_fd < 0 || fstat(dev_fd, &st) < 0) {
    243 	die_err(opt.device);
    244 	exit(1);
    245     }
    246 
    247     if (!opt.force && !S_ISBLK(st.st_mode) && !S_ISREG(st.st_mode)) {
    248 	fprintf(stderr,
    249 		"%s: not a block device or regular file (use -f to override)\n",
    250 		opt.device);
    251 	exit(1);
    252     }
    253 
    254     xpread(dev_fd, sectbuf, SECTOR_SIZE, opt.offset);
    255 
    256     /*
    257      * Check to see that what we got was indeed an MS-DOS boot sector/superblock
    258      */
    259     if ((errmsg = syslinux_check_bootsect(sectbuf, NULL))) {
    260 	die(errmsg);
    261     }
    262 
    263     /*
    264      * Create an mtools configuration file
    265      */
    266     if (asprintf(&mtools_conf, "%s//syslinux-mtools-XXXXXX", tmpdir) < 0 ||
    267 	!mtools_conf)
    268 	die_err(tmpdir);
    269 
    270     mtc_fd = mkstemp(mtools_conf);
    271     if (mtc_fd < 0 || !(mtc = fdopen(mtc_fd, "w")))
    272 	die_err(mtools_conf);
    273 
    274     fprintf(mtc,
    275 	    /* These are needed for some flash memories */
    276 	    "MTOOLS_SKIP_CHECK=1\n"
    277 	    "MTOOLS_FAT_COMPATIBILITY=1\n"
    278 	    "drive s:\n"
    279 	    "  file=\"/proc/%lu/fd/%d\"\n"
    280 	    "  offset=%llu\n",
    281 	    (unsigned long)mypid,
    282 	    dev_fd, (unsigned long long)opt.offset);
    283 
    284     if (ferror(mtc) || fclose(mtc))
    285 	die_err(mtools_conf);
    286 
    287     /*
    288      * Run mtools to create the LDLINUX.SYS file
    289      */
    290     if (setenv("MTOOLSRC", mtools_conf, 1)) {
    291 	perror(program);
    292 	exit(1);
    293     }
    294 
    295     /*
    296      * Create a vacuous ADV in memory.  This should be smarter.
    297      */
    298     syslinux_reset_adv(syslinux_adv);
    299 
    300     /* This command may fail legitimately */
    301     status = system("mattrib -h -r -s s:/ldlinux.sys 2>/dev/null");
    302     (void)status;		/* Keep _FORTIFY_SOURCE happy */
    303 
    304     mtp = popen("mcopy -D o -D O -o - s:/ldlinux.sys", "w");
    305     if (!mtp ||
    306 	fwrite((const void _force *)syslinux_ldlinux,
    307 	       1, syslinux_ldlinux_len, mtp)
    308 		!= syslinux_ldlinux_len ||
    309 	fwrite((const void _force *)syslinux_adv,
    310 	       1, 2 * ADV_SIZE, mtp)
    311 		!= 2 * ADV_SIZE ||
    312 	(status = pclose(mtp), !WIFEXITED(status) || WEXITSTATUS(status))) {
    313 	die("failed to create ldlinux.sys");
    314     }
    315 
    316     /*
    317      * Now, use libfat to create a block map
    318      */
    319     ldlinux_sectors = (syslinux_ldlinux_len + 2 * ADV_SIZE
    320 		       + SECTOR_SIZE - 1) >> SECTOR_SHIFT;
    321     sectors = calloc(ldlinux_sectors, sizeof *sectors);
    322     fs = libfat_open(libfat_xpread, dev_fd);
    323     ldlinux_cluster = libfat_searchdir(fs, 0, "LDLINUX SYS", NULL);
    324     secp = sectors;
    325     nsectors = 0;
    326     s = libfat_clustertosector(fs, ldlinux_cluster);
    327     while (s && nsectors < ldlinux_sectors) {
    328 	*secp++ = s;
    329 	nsectors++;
    330 	s = libfat_nextsector(fs, s);
    331     }
    332     libfat_close(fs);
    333 
    334     /* Patch ldlinux.sys and the boot sector */
    335     i = syslinux_patch(sectors, nsectors, opt.stupid_mode, opt.raid_mode,
    336 		       opt.directory, NULL);
    337     patch_sectors = (i + SECTOR_SIZE - 1) >> SECTOR_SHIFT;
    338 
    339     /* Write the now-patched first sectors of ldlinux.sys */
    340     for (i = 0; i < patch_sectors; i++) {
    341 	xpwrite(dev_fd, (const char _force *)syslinux_ldlinux
    342 		+ i * SECTOR_SIZE, SECTOR_SIZE,
    343 		opt.offset + ((off_t) sectors[i] << SECTOR_SHIFT));
    344     }
    345 
    346     /* Move ldlinux.sys to the desired location */
    347     if (opt.directory) {
    348 	status = move_file("ldlinux.sys");
    349     } else {
    350 	status = system("mattrib +r +h +s s:/ldlinux.sys");
    351     }
    352 
    353     if (!WIFEXITED(status) || WEXITSTATUS(status)) {
    354 	fprintf(stderr,
    355 		"%s: warning: failed to set system bit on ldlinux.sys\n",
    356 		program);
    357     }
    358 
    359     /* This command may fail legitimately */
    360     status = system("mattrib -h -r -s s:/ldlinux.c32 2>/dev/null");
    361     (void)status;		/* Keep _FORTIFY_SOURCE happy */
    362 
    363     mtp = popen("mcopy -D o -D O -o - s:/ldlinux.c32", "w");
    364     if (!mtp ||	fwrite((const char _force *)syslinux_ldlinuxc32,
    365 		       1, syslinux_ldlinuxc32_len, mtp)
    366 	!= syslinux_ldlinuxc32_len ||
    367 	(status = pclose(mtp), !WIFEXITED(status) || WEXITSTATUS(status))) {
    368 	die("failed to create ldlinux.c32");
    369     }
    370 
    371     /* Move ldlinux.c32 to the desired location */
    372     if (opt.directory) {
    373 	status = move_file("ldlinux.c32");
    374     } else {
    375 	status = system("mattrib +r +h +s s:/ldlinux.c32");
    376     }
    377 
    378     if (!WIFEXITED(status) || WEXITSTATUS(status)) {
    379 	fprintf(stderr,
    380 		"%s: warning: failed to set system bit on ldlinux.c32\n",
    381 		program);
    382     }
    383 
    384     /*
    385      * Cleanup
    386      */
    387     unlink(mtools_conf);
    388 
    389     /*
    390      * To finish up, write the boot sector
    391      */
    392 
    393     /* Read the superblock again since it might have changed while mounted */
    394     xpread(dev_fd, sectbuf, SECTOR_SIZE, opt.offset);
    395 
    396     /* Copy the syslinux code into the boot sector */
    397     syslinux_make_bootsect(sectbuf, VFAT);
    398 
    399     /* Write new boot sector */
    400     xpwrite(dev_fd, sectbuf, SECTOR_SIZE, opt.offset);
    401 
    402     close(dev_fd);
    403     sync();
    404 
    405     /* Done! */
    406 
    407     return 0;
    408 }
    409