/** ** SCCS ID: @(#)BuildImage.c 2.2 1/16/25 ** ** @file BuildImage.c ** ** @author K. Reek ** @author Jon Coles ** @author Warren R. Carithers ** @author Garrett C. Smith ** ** Modify the bootstrap image to include the information ** on the programs to be loaded, and produce the file ** that contains the concatenation of these programs. ** */ #define _POSIX_C_SOURCE 200809L #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define TRUE 1 #define FALSE 0 #define DRIVE_FLOPPY 0x00 #define DRIVE_USB 0x80 #define SECT_SIZE 512 char *progname; /* invocation name of this program */ char *bootstrap_filename; /* path of file holding bootstrap program */ char *output_filename; /* path of disk image file */ FILE *out; /* output stream for disk image file */ short drive = DRIVE_USB; /* boot drive */ /* ** Array into which program information will be stored, starting at the ** end and moving back toward the front. The array is the same size as ** a sector, which is guaranteed to be larger than the maximum possible ** space available for this stuff in the bootstrap image. Thus, the ** bootstrap image itself (and the amount of space available on the ** device) are the only limiting factors on how many program sections ** can be loaded. */ #define N_INFO (SECT_SIZE / sizeof(short)) short info[N_INFO]; int n_info = N_INFO; /** ** quit with an appropriate message ** ** @param msg NULL, or a message to be printed to stderr ** @param call_perror non-zero if perror() should be used; else, ** fprintf() will be used ** ** does not return */ void quit(char *msg, int call_perror) { if (msg != NULL) { // preserve the error code in case we need it int err_num = errno; fprintf(stderr, "%s: ", progname); errno = err_num; if (call_perror) { perror(msg); } else { fprintf(stderr, "%s\n", msg); } } if (output_filename != NULL) { unlink(output_filename); } exit(EXIT_FAILURE); // NOTREACHED } const char usage_error_msg[] = "\nUsage: %s [ -d drive ] -b bootfile -o outfile { progfile loadpt } " "...\n\n" "\t'drive' is either 'floppy' or 'usb' (default 'usb')\n\n" "\tThere must be at least one program file and load point.\n\n" "\tLoad points may be specified either as 32-bit quantities in hex,\n" "\tdecimal or octal (e.g. 0x10c00, 68608, 0206000 are all equivalent),\n" "\tor as an explicit segment:offset pair whose digits are always\n" "\tinterpreted as hexadecimal values (e.g. 10c0:0000, 1000:0c00 are\n" "\tboth equivalent to the previous examples).\n\n"; /** ** print a usage message and then call quit() ** ** does not return */ void usage_error(void) { fprintf(stderr, usage_error_msg, progname); quit(NULL, FALSE); // NOTREACHED } /** ** copy the contents of a binary file into the output file, padding the ** last sector with NUL bytes ** ** @param in open FILE to be read ** @return the number of sectors copied from the file */ int copy_file(FILE *in) { int n_sectors = 0; char buf[SECT_SIZE]; int n_bytes; int i; /* ** Copy the file to the output, being careful that the ** last sector is padded with null bytes out to the ** sector size. */ n_sectors = 0; while ((n_bytes = fread(buf, 1, sizeof(buf), in)) > 0) { // pad this sector out to block size if (n_bytes < sizeof(buf)) { int i; for (i = n_bytes; i < sizeof(buf); i += 1) { buf[i] = '\0'; } } if (fwrite(buf, 1, sizeof(buf), out) != sizeof(buf)) { quit("Write failed or was wrong size", FALSE); } n_sectors += 1; } return n_sectors; } /** ** process a file whose contents should be at a specific' ** address in memory when the program is loaded ** ** @param name path to the file to be copied ** @param addr string containing the load address */ void process_file(char *name, char *addr) { long address; short segment, offset; int n_bytes; /* ** Open the input file. */ FILE *in = fopen(name, "rb"); if (in == NULL) { quit(name, TRUE); } /* ** Copy the file to the output, being careful that the ** last block is padded with null bytes. */ int n_sectors = copy_file(in); fclose(in); /* ** Decode the address they gave us. We'll accept two forms: ** "nnnn:nnnn" for a segment:offset value (assumed to be hex), ** "nnnnnnn" for a decimal, hex, or octal value */ int valid_address = FALSE; char *cp = strchr(addr, ':'); if (cp != NULL) { // must be in nnnn:nnnn form exactly if (strlen(addr) == 9 && cp == addr + 4) { char *ep1, *ep2; int a1, a2; segment = strtol(addr, &ep1, 16); offset = strtol(addr + 5, &ep2, 16); address = (segment << 4) + offset; valid_address = *ep1 == '\0' && *ep2 == '\0'; } else { fprintf(stderr, "Bad address format - '%s'\n", addr); quit(NULL, FALSE); } } else { // just a number, possibly hex or octal char *ep; address = strtol(addr, &ep, 0); segment = (short)(address >> 4); offset = (short)(address & 0xf); valid_address = *ep == '\0' && address <= 0x0009ffff; } if (!valid_address) { fprintf(stderr, "%s: Invalid address: %s\n", progname, addr); quit(NULL, FALSE); } /* ** Make sure the program will fit! */ if (address + n_sectors * SECT_SIZE > 0x0009ffff) { fprintf(stderr, "Program %s too large to start at 0x%08x\n", name, (unsigned int)address); quit(NULL, FALSE); } if (n_info < 3) { quit("Too many programs!", FALSE); } /* ** Looks good: report and store the information. */ fprintf(stderr, " %s: %d sectors, loaded at 0x%x\n", name, n_sectors, (unsigned int)address); info[--n_info] = n_sectors; info[--n_info] = segment; info[--n_info] = offset; } /* ** Global variables set by getopt() */ extern int optind, optopt; extern char *optarg; /** ** process the command-line arguments ** ** @param ac the count of entries in av ** @param av the argument vector */ void process_args(int ac, char **av) { int c; while ((c = getopt(ac, av, ":d:o:b:")) != EOF) { switch (c) { case ':': /* missing arg value */ fprintf(stderr, "missing operand after -%c\n", optopt); /* FALL THROUGH */ case '?': /* error */ usage_error(); /* NOTREACHED */ case 'b': /* -b bootstrap_file */ bootstrap_filename = optarg; break; case 'd': /* -d drive */ switch (*optarg) { case 'f': drive = DRIVE_FLOPPY; break; case 'u': drive = DRIVE_USB; break; default: usage_error(); } break; case 'o': /* -o output_file */ output_filename = optarg; break; default: usage_error(); } } if (!bootstrap_filename) { fprintf(stderr, "%s: no bootstrap file specified\n", progname); exit(2); } if (!output_filename) { fprintf(stderr, "%s: no disk image file specified\n", progname); exit(2); } /* ** Must have at least two remaining arguments (file to load, ** address at which it should be loaded), and must have an ** even number of remaining arguments. */ int remain = ac - optind; if (remain < 2 || (remain & 1) != 0) { usage_error(); } } /** ** build a bootable image file from one or more binary files ** ** usage: ** BuildImage [ -d drive ] -b bootfile -o outfile { binfile1 loadpt1 } ... ] ** ** @param ac command-line argument count ** @param av command-line argument vector ** @return EXIT_SUCCESS or EXIT_FAILURE */ int main(int ac, char **av) { FILE *bootimage; int bootimage_size; int n_bytes, n_words; short existing_data[N_INFO]; int i; /* ** Save the program name for error messages */ progname = strrchr(av[0], '/'); if (progname != NULL) { progname++; } else { progname = av[0]; } /* ** Process arguments */ process_args(ac, av); /* ** Open the output file */ out = fopen(output_filename, "wb+"); if (out == NULL) { quit(output_filename, TRUE); } /* ** Open the bootstrap file and copy it to the output image. */ bootimage = fopen(bootstrap_filename, "rb"); if (bootimage == NULL) { quit(bootstrap_filename, TRUE); } /* ** Remember the size of the bootstrap for later, as we ** need to patch some things into it */ int n_sectors = copy_file(bootimage); fclose(bootimage); bootimage_size = n_sectors * SECT_SIZE; fprintf(stderr, " %s: %d sectors\n", bootstrap_filename, n_sectors); /* ** Process the programs one by one */ ac -= optind; av += optind; while (ac >= 2) { process_file(av[0], av[1]); ac -= 2; av += 2; } /* ** Check for oddball leftover argument */ if (ac > 0) { usage_error(); } /* ** Seek to where the array of module data must begin and read ** what's already there. */ n_words = (N_INFO - n_info); n_bytes = n_words * sizeof(info[0]); fseek(out, bootimage_size - n_bytes, SEEK_SET); if (fread(existing_data, sizeof(info[0]), n_words, out) != n_words) { quit("Read from boot image failed or was too short", FALSE); } /* ** If that space is non-zero, we have a problem */ for (i = 0; i < n_words; i += 1) { if (existing_data[i] != 0) { quit("Too many programs to load!", FALSE); } } /* ** We know that we're only overwriting zeros at the end of ** the bootstrap image, so it is ok to go ahead and do it. */ fseek(out, bootimage_size - n_bytes, SEEK_SET); if (fwrite(info + n_info, sizeof(info[0]), n_words, out) != n_words) { quit("Write to boot image failed or was too short", FALSE); } /* ** Write the drive index to the image. */ fseek(out, 508, SEEK_SET); fwrite((void *)&drive, sizeof(drive), 1, out); fclose(out); return EXIT_SUCCESS; }