CD-ROM Maker Philip J. Erdelsky The CDMAKE utility converts files from DOS/Windows format to ISO9660 (CD-ROM) format. First, gather all the files to be converted and put them into a single base directory and its subdirectories, arranged just the way you want them on the CD-ROM. Remember that ISO9660 allows subdirectories to be nested only eight levels deep. Therefore, if the base directory is C:\CDROM, C:\CDROM\D2\D3\D4\D5\D6\D7\D8\FOO.TXT is permitted, but C:\CDROM\D2\D3\D4\D5\D6\D7\D8\D9\FOO.TXT is forbidden. Also, ISO9660 does not allow directories to have extensions, although DOS does. Finally, the characters in file and directory names and file extensions must be letters, digits or underscores. Other punctuation marks permitted by DOS/Windows are forbidden by ISO9660. You can use the -c option to override this restriction, but the resulting CD-ROM may not be readable on systems other than DOS/Windows. Files in the base directory will be written to the root directory of the CD-ROM image. All subdirectories of the base directory will appear as subdirectories of the root directory of the CD-ROM image. Their contents, and the contents of their subdirectories, down to the eighth level, will be faithfully copied to the CD-ROM image. System files will not be written to the CD-ROM image. Hidden files will be written to the CD-ROM image, and will retain their hidden attributes. Read-only files will be written, and will remain read-only on the CD-ROM, but this does not distinguish them in any way, because on a CD-ROM all files are read-only. The archive attribute will be lost. File and directory date and time stamps will be preserved in the CD-ROM image. The utility is called up by a command line of the following form: CDMAKE [-q] [-v] [-p] [-s N] source volume image source specifications of base directory containing all files to be written to CD-ROM image volume volume label image image file or device -q quiet mode - display nothing but error messages -v verbose mode - display file information as files are scanned and written - overrides -p option -p show progress while writing -s N abort operation before beginning write if image will be larger than N megabytes (i.e. 1024*1024*N bytes) -m accept punctuation marks other than underscores in names and extensions The utility makes three passes over the source files: (1) The scanning pass, in which the names and extensions are checked for validity, and the names, extensions, sizes, dates, times and attributes are recorded internally. The files are not actually read during this pass. (2) The layout pass, in which the sizes and positions of directories, files and other items in the CD-ROM image are determined. (3) The writing pass, in which the files are actually read and the CD-ROM image is actually written to the specified file or device. The image is always written sequentially. If neither the -q nor the -v option is used, CDMAKE will display the volume label, size, number of files and directories and the total bytes in each at the end of the layout pass. If the -p option is used, and is not overridden by the -v option, then during the writing pass, CDMAKE will display the number of bytes still to be written to the CD-ROM image, updating it frequently. The number will decrease as the operation progresses, and will reach zero when the operation is complete. The operation of CDMAKE can be aborted by typing Ctrl-C when the utility is displaying text of any kind. // CD-ROM Maker #include #include #include #include #include #include #include #include #include #include extern "C" { struct directory_record *sort_linked_list(struct directory_record *, unsigned, int (*)(struct directory_record *, struct directory_record *)); } typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned long DWORD; typedef int BOOL; const BOOL TRUE = 1; const BOOL FALSE = 0; // file system parameters const unsigned MAX_LEVEL = 8; const unsigned MAX_NAME_LENGTH = 8; const unsigned MAX_EXTENSION_LENGTH = 3; const unsigned SECTOR_SIZE = 2048; const BYTE HIDDEN_FLAG = 1; const BYTE DIRECTORY_FLAG = 2; const unsigned BUFFER_SIZE = 8*SECTOR_SIZE; struct cd_image { int handle; long sector; // sector to receive next byte int offset; // offset of next byte in sector int count; // number of bytes in buffer char filespecs[128]; BYTE *buffer; }; struct date_and_time { BYTE second; BYTE minute; BYTE hour; BYTE day; BYTE month; WORD year; }; struct directory_record { struct directory_record *next_in_directory; struct directory_record *next_in_path_table; /* directory record only */ struct directory_record *next_in_memory; struct directory_record *first_record; /* directory record only */ struct directory_record *parent; BYTE flags; char name[MAX_NAME_LENGTH+1]; char extension[MAX_EXTENSION_LENGTH+1]; struct date_and_time date_and_time; DWORD sector; DWORD size; unsigned level; /* directory record only */ WORD path_table_index; /* directory record only */ }; enum directory_record_type {DOT_RECORD, DOT_DOT_RECORD, SUBDIRECTORY_RECORD, FILE_RECORD}; static char DIRECTORY_TIMESTAMP[] = "~Y$'KOR$.3K&"; static jmp_buf error; static struct cd_image cd; static char volume_label[32]; static struct directory_record root; static char source[512]; static char *end_source; static enum {QUIET, NORMAL, VERBOSE} verbosity; static BOOL show_progress; static DWORD size_limit; static BOOL accept_punctuation_marks; static DWORD total_sectors; static DWORD path_table_size; static DWORD little_endian_path_table_sector; static DWORD big_endian_path_table_sector; static DWORD number_of_files; static DWORD bytes_in_files; static DWORD unused_bytes_at_ends_of_files; static DWORD number_of_directories; static DWORD bytes_in_directories; /*----------------------------------------------------------------------------- This function edits a 32-bit unsigned number into a comma-delimited form, such as 4,294,967,295 for the largest possible number, and returns a pointer to a static buffer containing the result. It suppresses leading zeros and commas, but optionally pads the result with blanks at the left so the result is always exactly 13 characters long (excluding the terminating zero). CAUTION: A statement containing more than one call on this function, such as printf("%s, %s", edit_with_commas(a), edit_with_commas(b)), will produce incorrect results because all calls use the same static bufffer. -----------------------------------------------------------------------------*/ static char *edit_with_commas(DWORD x, BOOL pad = TRUE) { static char s[14]; unsigned i = 13; do { if (i % 4 == 2) s[--i] = ','; s[--i] = x % 10 + '0'; } while ((x/=10) != 0); if (pad) { while (i > 0) s[--i] = ' '; } return s + i; } /*----------------------------------------------------------------------------- This function releases all allocated memory blocks. -----------------------------------------------------------------------------*/ static void release_memory(void) { while (root.next_in_memory != NULL) { struct directory_record *next = root.next_in_memory->next_in_memory; delete root.next_in_memory; root.next_in_memory = next; } if (cd.buffer != NULL) { delete cd.buffer; cd.buffer = NULL; } } /*----------------------------------------------------------------------------- This function edits and displays an error message and then jumps back to the error exit point in main(). -----------------------------------------------------------------------------*/ static void error_exit(const char *format, ...) { vfprintf(stderr, format, &format + 1); fprintf(stderr, "\n"); if (cd.handle >= 0) close(cd.handle); release_memory(); longjmp(error, 1); } /*----------------------------------------------------------------------------- This function, which is called only on the second pass, and only when the buffer is not empty, flushes the buffer to the CD-ROM image. -----------------------------------------------------------------------------*/ static void flush_buffer(void) { if (write(cd.handle, cd.buffer, cd.count) < cd.count) error_exit("File write error"); cd.count = 0; if (show_progress) { printf("\r%s ", edit_with_commas((total_sectors - cd.sector) * SECTOR_SIZE)); } } /*----------------------------------------------------------------------------- This function writes a single byte to the CD-ROM image. On the first pass (in which cd.handle < 0), it does not actually write anything but merely updates the file pointer as though the byte had been written. -----------------------------------------------------------------------------*/ static void write_byte(BYTE x) { if (cd.handle >= 0) { cd.buffer[cd.count] = x; if (++cd.count == BUFFER_SIZE) flush_buffer(); } if (++cd.offset == SECTOR_SIZE) { cd.sector++; cd.offset = 0; } } /*----------------------------------------------------------------------------- These functions write a word or double word to the CD-ROM image with the specified endianity. -----------------------------------------------------------------------------*/ static void write_little_endian(WORD x) { write_byte(x); write_byte(x >> 8); } static void write_big_endian(WORD x) { write_byte(x >> 8); write_byte(x); } static void write_both_endian(WORD x) { write_little_endian(x); write_big_endian(x); } static void write_little_endian(DWORD x) { write_byte(x); write_byte(x >> 8); write_byte(x >> 16); write_byte(x >> 24); } static void write_big_endian(DWORD x) { write_byte(x >> 24); write_byte(x >> 16); write_byte(x >> 8); write_byte(x); } static void write_both_endian(DWORD x) { write_little_endian(x); write_big_endian(x); } /*----------------------------------------------------------------------------- This function writes enough zeros to fill out the end of a sector, and leaves the file pointer at the beginning of the next sector. If the file pointer is already at the beginning of a sector, it writes nothing. -----------------------------------------------------------------------------*/ static void fill_sector(void) { while (cd.offset != 0) write_byte(0); } /*----------------------------------------------------------------------------- This function writes a string to the CD-ROM image. The terminating \0 is not written. -----------------------------------------------------------------------------*/ static void write_string(char *s) { while (*s != 0) write_byte(*s++); } /*----------------------------------------------------------------------------- This function writes a block of identical bytes to the CD-ROM image. -----------------------------------------------------------------------------*/ static void write_block(unsigned count, BYTE value) { while (count != 0) { write_byte(value); count--; } } /*----------------------------------------------------------------------------- This function writes a directory record to the CD_ROM image. -----------------------------------------------------------------------------*/ static void write_directory_record(directory_record *d, enum directory_record_type type) { unsigned identifier_size; switch (type) { case DOT_RECORD: case DOT_DOT_RECORD: identifier_size = 1; break; case SUBDIRECTORY_RECORD: identifier_size = strlen(d->name); break; case FILE_RECORD: identifier_size = strlen(d->name) + 2; if (d->extension[0] != 0) identifier_size += 1 + strlen(d->extension); break; } unsigned record_size = 33 + identifier_size; if ((identifier_size & 1) == 0) record_size++; if (cd.offset + record_size > SECTOR_SIZE) fill_sector(); write_byte(record_size); write_byte(0); // number of sectors in extended attribute record write_both_endian(d->sector); write_both_endian(d->size); write_byte(d->date_and_time.year - 1900); write_byte(d->date_and_time.month); write_byte(d->date_and_time.day); write_byte(d->date_and_time.hour); write_byte(d->date_and_time.minute); write_byte(d->date_and_time.second); write_byte(0); // GMT offset write_byte(d->flags); write_byte(0); // file unit size for an interleaved file write_byte(0); // interleave gap size for an interleaved file write_both_endian((WORD) 1); // volume sequence number write_byte(identifier_size); switch (type) { case DOT_RECORD: write_byte(0); break; case DOT_DOT_RECORD: write_byte(1); break; case SUBDIRECTORY_RECORD: write_string(d->name); break; case FILE_RECORD: write_string(d->name); if (d->extension[0] != 0) { write_byte('.'); write_string(d->extension); } write_string(";1"); break; } if ((identifier_size & 1) == 0) write_byte(0); } /*----------------------------------------------------------------------------- This function converts the date and time words from an ffblk structure and puts them into a date_and_time structure. -----------------------------------------------------------------------------*/ static void convert_date_and_time(date_and_time &dt, int date, int time) { dt.second = 2 * (time & 0x1F); dt.minute = time >> 5 & 0x3F; dt.hour = time >> 11 & 0x1F; dt.day = date & 0x1F; dt.month = date >> 5 & 0xF; dt.year = (date >> 9 & 0x7F) + 1980; } /*----------------------------------------------------------------------------- This function checks the specified character, if necessary, and generates an error if it is a punctuation mark other than an underscore. It also converts small letters to capital letters and returns the result. -----------------------------------------------------------------------------*/ static int check_for_punctuation(int c, char *name) { c = toupper(c & 0xFF); if (!accept_punctuation_marks && !isalnum(c) && c != '_') error_exit("Punctuation mark in %s", name); return c; } /*----------------------------------------------------------------------------- This function creates a new directory record with the information from the specified ffblk. It links it into the beginning of the directory list for the specified parent and returns a pointer to the new record. -----------------------------------------------------------------------------*/ directory_record *new_directory_record(struct ffblk &f, directory_record *parent) { directory_record *d = new directory_record; if (d == NULL) error_exit("Insufficient memory"); d->next_in_memory = root.next_in_memory; root.next_in_memory = d; { char *s = f.ff_name; char *t = d->name; while (*s != 0) { if (*s == '.') { s++; break; } *t++ = check_for_punctuation(*s++, f.ff_name); } *t = 0; t = d->extension; while (*s != 0) *t++ = check_for_punctuation(*s++, f.ff_name); *t = 0; } convert_date_and_time(d->date_and_time, f.ff_fdate, f.ff_ftime); if (f.ff_attrib & FA_DIREC) { if (d->extension[0] != 0) error_exit("Directory with extension %s", f.ff_name); d->flags = DIRECTORY_FLAG; } else d->flags = f.ff_attrib & FA_HIDDEN ? HIDDEN_FLAG : 0; d->size = f.ff_fsize; d->next_in_directory = parent->first_record; parent->first_record = d; d->parent = parent; return d; } /*----------------------------------------------------------------------------- This function compares two directory records according to the ISO9660 rules for directory sorting and returns a negative value if p is before q, or a positive value if p is after q. -----------------------------------------------------------------------------*/ static int compare_directory_order(directory_record *p, directory_record *q) { int n = strcmp(p->name, q->name); if (n == 0) n = strcmp(p->extension, q->extension); return n; } /*----------------------------------------------------------------------------- This function compares two directory records (which must represent directories) according to the ISO9660 rules for path table sorting and returns a negative value if p is before q, or a positive vlaue if p is after q. -----------------------------------------------------------------------------*/ static int compare_path_table_order(directory_record *p, directory_record *q) { if (p == q) return 0; int n = p->level - q->level; if (n == 0) { n = compare_path_table_order(p->parent, q->parent); if (n == 0) n = compare_directory_order(p, q); } return n; } /*----------------------------------------------------------------------------- This function appends the specified string to the buffer source[]. -----------------------------------------------------------------------------*/ static void append_string_to_source(char *s) { while (*s != 0) *end_source++ = *s++; } /*----------------------------------------------------------------------------- This function scans all files from the current source[] (which must end in \, and represents a directory already in the database as d), and puts the appropriate directory records into the database in memory, with the specified root. It calls itself recursively to scan all subdirectories. -----------------------------------------------------------------------------*/ static void make_directory_records(directory_record *d) { struct ffblk f; d->first_record = NULL; strcpy(end_source, "*.*"); if (findfirst(source, &f, FA_HIDDEN) == 0) { do { if (strcmp(f.ff_name, DIRECTORY_TIMESTAMP) == 0) convert_date_and_time(d->date_and_time, f.ff_fdate, f.ff_ftime); else { if (verbosity == VERBOSE) { char *old_end_source = end_source; strcpy(end_source, f.ff_name); printf("%d: file %s\n", d->level, source); end_source = old_end_source; } (void) new_directory_record(f, d); } } while (findnext(&f) == 0); } strcpy(end_source, "*.*"); if (findfirst(source, &f, FA_DIREC) == 0) { do { if (f.ff_attrib & FA_DIREC && f.ff_name[0] != '.') { char *old_end_source = end_source; append_string_to_source(f.ff_name); *end_source++ = '\\'; if (verbosity == VERBOSE) { *end_source = 0; printf("%d: directory %s\n", d->level + 1, source); } if (d->level < MAX_LEVEL) { directory_record *new_d = new_directory_record(f, d); new_d->next_in_path_table = root.next_in_path_table; root.next_in_path_table = new_d; new_d->level = d->level + 1; make_directory_records(new_d); } else error_exit("Directory is nested too deep"); end_source = old_end_source; } } while (findnext(&f) == 0); } // sort directory d->first_record = sort_linked_list(d->first_record, 0, compare_directory_order); } /*----------------------------------------------------------------------------- This function loads the file specifications for the file or directory corresponding to the specified directory record into the source[] buffer. It is recursive. -----------------------------------------------------------------------------*/ static void get_file_specifications(directory_record *d) { if (d != &root) { get_file_specifications(d->parent); append_string_to_source(d->name); if ((d->flags & DIRECTORY_FLAG) == 0 && d->extension[0] != 0) { *end_source++ = '.'; append_string_to_source(d->extension); } if (d->flags & DIRECTORY_FLAG) *end_source++ = '\\'; } } static void pass(void) { // first 16 sectors are zeros write_block(16 * SECTOR_SIZE, 0); // Primary Volume Descriptor write_string("\1CD001\1"); write_byte(0); write_block(32, ' '); // system identifier { char *t = volume_label; for (unsigned i = 0; i < 32; i++) write_byte(*t != 0 ? toupper(*t++) : ' '); } write_block(8, 0); write_both_endian(total_sectors); write_block(32, 0); write_both_endian((WORD) 1); // volume set size write_both_endian((WORD) 1); // volume sequence number write_both_endian((WORD) 2048); // sector size write_both_endian(path_table_size); write_little_endian(little_endian_path_table_sector); write_little_endian((DWORD) 0); // second little endian path table write_big_endian(big_endian_path_table_sector); write_big_endian((DWORD) 0); // second big endian path table write_directory_record(&root, DOT_RECORD); write_block(128, ' '); // volume set identifier write_block(128, ' '); // publisher identifier write_block(128, ' '); // data preparer identifier write_block(128, ' '); // application identifier write_block(37, ' '); // copyright file identifier write_block(37, ' '); // abstract file identifier write_block(37, ' '); // bibliographic file identifier write_string("0000000000000000"); // volume creation write_byte(0); write_string("0000000000000000"); // most recent modification write_byte(0); write_string("0000000000000000"); // volume expires write_byte(0); write_string("0000000000000000"); // volume is effective write_byte(0); write_byte(1); write_byte(0); fill_sector(); // Volume Descriptor Set Terminator write_string("\377CD001\1"); fill_sector(); // Little Endian Path Table little_endian_path_table_sector = cd.sector; write_byte(1); write_byte(0); // number of sectors in extended attribute record write_little_endian(root.sector); write_little_endian((WORD) 1); write_byte(0); write_byte(0); { directory_record *d; unsigned index = 1; root.path_table_index = 1; for (d = root.next_in_path_table; d != NULL; d = d->next_in_path_table) { unsigned name_length = strlen(d->name); write_byte(name_length); write_byte(0); // number of sectors in extended attribute record write_little_endian(d->sector); write_little_endian(d->parent->path_table_index); write_string(d->name); if (name_length & 1) write_byte(0); d->path_table_index = ++index; } path_table_size = (cd.sector - little_endian_path_table_sector) * SECTOR_SIZE + cd.offset; fill_sector(); } // Big Endian Path Table big_endian_path_table_sector = cd.sector; write_byte(1); write_byte(0); // number of sectors in extended attribute record write_big_endian(root.sector); write_big_endian((WORD) 1); write_byte(0); write_byte(0); { directory_record *d; for (d = root.next_in_path_table; d != NULL; d = d->next_in_path_table) { unsigned name_length = strlen(d->name); write_byte(name_length); write_byte(0); // number of sectors in extended attribute record write_big_endian(d->sector); write_big_endian(d->parent->path_table_index); write_string(d->name); if (name_length & 1) write_byte(0); } fill_sector(); } // directories and files { directory_record *d; for (d = &root; d != NULL; d = d->next_in_path_table) { directory_record *q; // write directory d->sector = cd.sector; write_directory_record(d, DOT_RECORD); write_directory_record(d == &root ? d : d->parent, DOT_DOT_RECORD); for (q = d->first_record; q != NULL; q = q->next_in_directory) { write_directory_record(q, q->flags & DIRECTORY_FLAG ? SUBDIRECTORY_RECORD : FILE_RECORD); } fill_sector(); d->size = (cd.sector - d->sector) * SECTOR_SIZE; number_of_directories++; bytes_in_directories += d->size; // write file data for (q = d->first_record; q != NULL; q = q->next_in_directory) { if ((q->flags & DIRECTORY_FLAG) == 0) { q->sector = cd.sector; DWORD size = q->size; if (cd.handle < 0) { DWORD number_of_sectors = (size + SECTOR_SIZE - 1) / SECTOR_SIZE; cd.sector += number_of_sectors; number_of_files++; bytes_in_files += size; unused_bytes_at_ends_of_files += number_of_sectors * SECTOR_SIZE - size; } else { char *old_end_source = end_source; get_file_specifications(q); *end_source = 0; if (verbosity == VERBOSE) printf("Writing %s\n", source); int h = _open(source, O_RDONLY); if (h < 0) error_exit("Can't open %s\n", source); while (size > 0) { int n = BUFFER_SIZE - cd.count; if ((DWORD) n > size) n = size; if (_read(h, cd.buffer + cd.count, n) < n) { close(h); error_exit("Read error in file %s\n", source); } cd.count += n; if (cd.count == BUFFER_SIZE) flush_buffer(); cd.sector += n / SECTOR_SIZE; cd.offset += n % SECTOR_SIZE; size -= n; } close(h); end_source = old_end_source; fill_sector(); } } } } } total_sectors = cd.sector; } static char HELP[] = "CDMAKE [-q] [-v] [-p] [-s N] source volume image\n" "\n" " source specifications of base directory containing all files to\n" " be written to CD-ROM image\n" "\n" " volume volume label\n" "\n" " image image file or device\n" "\n" " -q quiet mode - display nothing but error messages\n" "\n" " -v verbose mode - display file information as files are\n" " scanned and written - overrides -p option\n" "\n" " -p show progress while writing\n" "\n" " -s N abort operation before beginning write if image will be\n" " larger than N megabytes (i.e. 1024*1024*N bytes)\n" "\n" " -m accept punctuation marks other than underscores in\n" " names and extensions\n"; /*----------------------------------------------------------------------------- Program execution starts here. -----------------------------------------------------------------------------*/ int main(int argc, char **argv) { if (argc < 2) { puts(HELP); return 1; } if (setjmp(error)) return 1; // initialize root directory cd.buffer = new BYTE[BUFFER_SIZE]; if (cd.buffer == NULL) error_exit("Insufficient memory"); memset(&root, 0, sizeof(root)); root.level = 1; root.flags = DIRECTORY_FLAG; // initialize CD-ROM write buffer cd.handle = -1; cd.filespecs[0] = 0; // initialize parameters verbosity = NORMAL; size_limit = 0; show_progress = FALSE; accept_punctuation_marks = FALSE; source[0] = 0; volume_label[0] = 0; // scan command line arguments { BOOL q_option = FALSE; BOOL v_option = FALSE; for (int i = 1; i < argc; i++) { if (memcmp(argv[i], "-s", 2) == 0) { char *t = argv[i] + 2; if (*t == 0) { if (++i < argc) t = argv[i]; else error_exit("Missing size limit parameter"); } while (isdigit(*t)) size_limit = size_limit * 10 + *t++ - '0'; if (size_limit < 1 || size_limit > 800) error_exit("Invalid size limit"); size_limit <<= 9; // convert megabyte to sector count } else if (strcmp(argv[i], "-q") == 0) q_option = TRUE; else if (strcmp(argv[i], "-v") == 0) v_option = TRUE; else if (strcmp(argv[i], "-p") == 0) show_progress = TRUE; else if (strcmp(argv[i], "-m") == 0) accept_punctuation_marks = TRUE; else if (i + 2 < argc) { strcpy(source, argv[i++]); strncpy(volume_label, argv[i++], sizeof(volume_label) - 1); strcpy(cd.filespecs, argv[i]); } else error_exit("Missing command line argument"); } if (v_option) { show_progress = FALSE; verbosity = VERBOSE; } else if (q_option) { verbosity = QUIET; show_progress = FALSE; } if (source[0] == 0) error_exit("Missing source directory"); if (volume_label[0] == 0) error_exit("Missing volume label"); if (cd.filespecs[0] == 0) error_exit("Missing image file specifications"); } // set source[] and end_source to source directory, with a terminating \ end_source = source + strlen(source); if (end_source[-1] == ':') *end_source++ = '.'; if (end_source[-1] != '\\') *end_source++ = '\\'; // scan all files and create directory structure in memory make_directory_records(&root); // sort path table entries root.next_in_path_table = sort_linked_list(root.next_in_path_table, 1, compare_path_table_order); // initialize CD-ROM write buffer cd.handle = -1; cd.sector = 0; cd.offset = 0; cd.count = 0; // make non-writing pass over directory structure to obtain the proper // sector numbers and offsets and to determine the size of the image number_of_files = bytes_in_files = number_of_directories = bytes_in_directories = unused_bytes_at_ends_of_files =0; pass(); if (verbosity >= NORMAL) { printf("%s bytes ", edit_with_commas(bytes_in_files)); printf("in %s files\n", edit_with_commas(number_of_files, FALSE)); printf("%s unused bytes at ends of files\n", edit_with_commas(unused_bytes_at_ends_of_files)); printf("%s bytes ", edit_with_commas(bytes_in_directories)); printf("in %s directories\n", edit_with_commas(number_of_directories, FALSE)); printf("%s other bytes\n", edit_with_commas(root.sector * SECTOR_SIZE)); puts("-------------"); printf("%s total bytes\n", edit_with_commas(total_sectors * SECTOR_SIZE)); puts("============="); } if (size_limit != 0 && total_sectors > size_limit) error_exit("Size limit exceeded"); // re-initialize CD-ROM write buffer cd.handle = _creat(cd.filespecs, 0); if (cd.handle < 0) error_exit("Can't open image file %s", cd.filespecs); cd.sector = 0; cd.offset = 0; cd.count = 0; // make writing pass over directory structure pass(); if (cd.count > 0) flush_buffer(); if (show_progress) printf("\r \n"); if (close(cd.handle) != 0) { cd.handle = -1; error_exit("File write error in image file %s", cd.filespecs); } if (verbosity >= NORMAL) puts("CD-ROM image made successfully"); release_memory(); return 0; } LLMOSRT.C: /* A Linked-List Memory Sort by Philip J. Erdelsky pje@acm.org http://www.alumni.caltech.edu/~pje/ */ #include void *sort_linked_list(void *p, unsigned index, int (*compare)(void *, void *)) { unsigned base; unsigned long block_size; struct record { struct record *next[1]; /* other members not directly accessed by this function */ }; struct tape { struct record *first, *last; unsigned long count; } tape[4]; /* Distribute the records alternately to tape[0] and tape[1]. */ tape[0].count = tape[1].count = 0L; tape[0].first = NULL; base = 0; while (p != NULL) { struct record *next = ((struct record *)p)->next[index]; ((struct record *)p)->next[index] = tape[base].first; tape[base].first = ((struct record *)p); tape[base].count++; p = next; base ^= 1; } /* If the list is empty or contains only a single record, then */ /* tape[1].count == 0L and this part is vacuous. */ for (base = 0, block_size = 1L; tape[base+1].count != 0L; base ^= 2, block_size <<= 1) { int dest; struct tape *tape0, *tape1; tape0 = tape + base; tape1 = tape + base + 1; dest = base ^ 2; tape[dest].count = tape[dest+1].count = 0; for (; tape0->count != 0; dest ^= 1) { unsigned long n0, n1; struct tape *output_tape = tape + dest; n0 = n1 = block_size; while (1) { struct record *chosen_record; struct tape *chosen_tape; if (n0 == 0 || tape0->count == 0) { if (n1 == 0 || tape1->count == 0) break; chosen_tape = tape1; n1--; } else if (n1 == 0 || tape1->count == 0) { chosen_tape = tape0; n0--; } else if ((*compare)(tape0->first, tape1->first) > 0) { chosen_tape = tape1; n1--; } else { chosen_tape = tape0; n0--; } chosen_tape->count--; chosen_record = chosen_tape->first; chosen_tape->first = chosen_record->next[index]; if (output_tape->count == 0) output_tape->first = chosen_record; else output_tape->last->next[index] = chosen_record; output_tape->last = chosen_record; output_tape->count++; } } } if (tape[base].count > 1L) tape[base].last->next[index] = NULL; return tape[base].first; }