/** ** 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 #include #include #include #include #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; }