/* * CD-ROM Maker * by Philip J. Erdelsky * pje@acm.org * http://alumnus.caltech.edu/~pje/ * * http://alumnus.caltech.edu/~pje/cdmake.txt * * According to his website, this file was released into the public domain * by Philip J. Erdelsky. */ /* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS CD-ROM Maker * FILE: tools/cdmake/cdmake.c * PURPOSE: CD-ROM Premastering Utility * PROGRAMMERS: Eric Kohl * Casper S. Hornstrup * Filip Navara * Magnus Olsen * Hermes Belusca-Maito * * HISTORY: * * ElTorito-Support * by Eric Kohl * * Linux port * by Casper S. Hornstrup * chorns@users.sourceforge.net * * Joliet support * by Filip Navara * xnavara@volny.cz * Limitations: * - No Joliet file name validations * - Very bad ISO file name generation * * Convert long filename to ISO9660 file name by Magnus Olsen * magnus@greatlord.com */ #include #include #include #include #include #include #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include # include # include # ifdef _MSC_VER # define R_OK 4 # endif #else # if defined(__FreeBSD__) || defined(__APPLE__) # include # else # include # endif // __FreeBSD__ # include # include # include # include # define TRUE 1 # define FALSE 0 #endif // _WIN32 #include #include #include "config.h" #include "dirhash.h" // FIXME! FIXME! Do it in a portable way!! typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned long DWORD; typedef int BOOL; // file system parameters #define MAX_LEVEL 8 #define MAX_NAME_LENGTH 64 #define MAX_CDNAME_LENGTH 8 #define MAX_EXTENSION_LENGTH 10 #define MAX_CDEXTENSION_LENGTH 3 #define SECTOR_SIZE 2048 #define BUFFER_SIZE (8 * SECTOR_SIZE) #define HIDDEN_FLAG 1 #define DIRECTORY_FLAG 2 struct cd_image { FILE *file; DWORD 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; }; typedef struct _boot_validation_header { BYTE header_id; BYTE platform_id; } BOOT_VALIDATION_HEADER, *PBOOT_VALIDATION_HEADER; typedef struct boot_entry { struct boot_entry *next_entry; BYTE boot_id; BYTE boot_emu_type; WORD load_segment; BYTE system_type; WORD sector_count; // boot_image_size DWORD load_rba; // boot_image_sector // BYTE unused[20]; char bootimage[512]; } BOOT_ENTRY, *PBOOT_ENTRY; typedef struct boot_header { struct boot_header *next_header; BYTE header_id; BYTE platform_id; WORD num_entries; // char id_string[28]; PBOOT_ENTRY entry_list; } BOOT_HEADER, *PBOOT_HEADER; typedef struct date_and_time { BYTE second; BYTE minute; BYTE hour; BYTE day; BYTE month; WORD year; } DATE_AND_TIME, *PDATE_AND_TIME; typedef 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 name_on_cd[MAX_CDNAME_LENGTH+1]; char extension[MAX_EXTENSION_LENGTH+1]; char extension_on_cd[MAX_CDEXTENSION_LENGTH+1]; char *joliet_name; const char *orig_name; DATE_AND_TIME date_and_time; DWORD sector; DWORD size; DWORD joliet_sector; DWORD joliet_size; unsigned level; /* directory record only */ WORD path_table_index; /* directory record only */ } DIR_RECORD, *PDIR_RECORD; typedef enum directory_record_type { DOT_RECORD, DOT_DOT_RECORD, SUBDIRECTORY_RECORD, FILE_RECORD } DIR_RECORD_TYPE, *PDIR_RECORD_TYPE; PDIR_RECORD sort_linked_list(PDIR_RECORD, unsigned, int (*)(PDIR_RECORD, PDIR_RECORD)); static char DIRECTORY_TIMESTAMP[] = "~Y$'KOR$.3K&"; static struct cd_image cd; DIR_RECORD root; char volume_label[32]; char source[512]; char *end_source; enum {QUIET, NORMAL, VERBOSE} verbosity; BOOL show_progress; BOOL scan_files_only = FALSE; // BOOL compute_crc = FALSE; /* * ISO 9660 information */ DWORD size_limit; BOOL accept_punctuation_marks; DWORD total_sectors; DWORD path_table_size; DWORD little_endian_path_table_sector; DWORD big_endian_path_table_sector; /* * Stats */ DWORD number_of_files; DWORD bytes_in_files; DWORD unused_bytes_at_ends_of_files; DWORD number_of_directories; DWORD bytes_in_directories; struct target_dir_hash specified_files; /* * El-Torito boot information */ BOOL eltorito; // TRUE/FALSE: bootable/non-bootable CD-ROM BOOL multi_boot; // TRUE/FALSE: multi/single-boot CD-ROM DWORD boot_catalog_sector; BOOT_VALIDATION_HEADER boot_validation_header; BOOT_ENTRY default_boot_entry; PBOOT_HEADER boot_header_list; /* * Joliet information */ BOOL joliet; DWORD joliet_path_table_size; DWORD joliet_little_endian_path_table_sector; DWORD joliet_big_endian_path_table_sector; /* * UDF information */ BOOL make_bridged_udf = TRUE; // TRUE for "UDF-Bridge" (aka. UDF/ISO); FALSE for pure UDF. /*----------------------------------------------------------------------------- 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) { static char s[14]; unsigned i = 13; do { if (i % 4 == 2) s[--i] = ','; s[--i] = (char)(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 (boot_header_list) { PBOOT_HEADER next_header = boot_header_list->next_header; while (boot_header_list->entry_list) { PBOOT_ENTRY next_entry = boot_header_list->entry_list->next_entry; free(boot_header_list->entry_list); boot_header_list->entry_list = next_entry; } free(boot_header_list); boot_header_list = next_header; } while (root.next_in_memory != NULL) { PDIR_RECORD next = root.next_in_memory->next_in_memory; if (joliet) free(root.next_in_memory->joliet_name); free(root.next_in_memory); root.next_in_memory = next; } if (joliet) free(root.joliet_name); if (cd.buffer != NULL) { free(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* fmt, ...) { va_list arg; va_start(arg, fmt); vprintf(fmt, arg); va_end(arg); printf("\n"); if (cd.file != NULL) fclose(cd.file); release_memory(); exit(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 (fwrite(cd.buffer, cd.count, 1, cd.file) < 1) error_exit("File write error"); cd.count = 0; if (show_progress) { printf("\r%s ", edit_with_commas((total_sectors - cd.sector) * SECTOR_SIZE, TRUE)); } } /*----------------------------------------------------------------------------- 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.file != NULL) { 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(WORD x) { write_byte((BYTE)x); write_byte((BYTE)(x >> 8)); } static void write_big_endian_word(WORD x) { write_byte((BYTE)(x >> 8)); write_byte((BYTE)x); } static void write_both_endian_word(WORD x) { write_little_endian_word(x); write_big_endian_word(x); } static void write_little_endian_dword(DWORD x) { write_byte((BYTE)x); write_byte((BYTE)(x >> 8)); write_byte((BYTE)(x >> 16)); write_byte((BYTE)(x >> 24)); } static void write_big_endian_dword(DWORD x) { write_byte((BYTE)(x >> 24)); write_byte((BYTE)(x >> 16)); write_byte((BYTE)(x >> 8)); write_byte((BYTE)x); } static void write_both_endian_dword(DWORD x) { write_little_endian_dword(x); write_big_endian_dword(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++); } static void write_bytecounted_string(unsigned bytecount, char *s, char padding) { while (*s != 0 && bytecount != 0) { write_byte(*s++); bytecount--; } while (bytecount != 0) { write_byte(padding); bytecount--; } } /*----------------------------------------------------------------------------- This function writes a ansi string as a big endian unicode string to the CD-ROM image. The terminating \0 is not written. -----------------------------------------------------------------------------*/ static void write_string_as_big_endian_unicode(char *s) { while (*s != 0) { write_big_endian_word(*s++); } } static void write_bytecounted_string_as_big_endian_unicode(unsigned bytecount, char *s, char padding) { unsigned wordcount = bytecount / 2; while (*s != 0 && wordcount != 0) { write_big_endian_word(*s++); wordcount--; } while (wordcount != 0) { write_big_endian_word(padding); wordcount--; } if (bytecount % 2 != 0) write_byte(padding); } /*----------------------------------------------------------------------------- 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 block of identical big endian words to the CD-ROM image. -----------------------------------------------------------------------------*/ static void write_word_block(unsigned count, WORD value) { while (count != 0) { write_big_endian_word(value); count--; } } /*----------------------------------------------------------------------------- This function writes a directory record to the CD_ROM image. -----------------------------------------------------------------------------*/ static void write_directory_record(PDIR_RECORD d, DIR_RECORD_TYPE DirType, BOOL joliet) { unsigned identifier_size; unsigned record_size; if (joliet) { if (DirType == DOT_RECORD || DirType == DOT_DOT_RECORD) identifier_size = 1; else identifier_size = strlen(d->joliet_name) * 2; } else { switch (DirType) { case DOT_RECORD: case DOT_DOT_RECORD: identifier_size = 1; break; case SUBDIRECTORY_RECORD: /*printf("Subdir: %s\n", d->name_on_cd);*/ identifier_size = strlen(d->name_on_cd); break; case FILE_RECORD: /*printf("File: %s.%s -> %s.%s\n", d->name, d->extension, d->name_on_cd, d->extension_on_cd);*/ identifier_size = strlen(d->name_on_cd) + 2; if (d->extension_on_cd[0] != 0) identifier_size += 1 + strlen(d->extension_on_cd); break; default: identifier_size = 1; break; } } record_size = 33 + identifier_size; if ((identifier_size & 1) == 0) record_size++; if (cd.offset + record_size > SECTOR_SIZE) fill_sector(); write_byte((BYTE)record_size); write_byte(0); // number of sectors in extended attribute record if (joliet) { write_both_endian_dword(d->joliet_sector); write_both_endian_dword(d->joliet_size); } else { write_both_endian_dword(d->sector); write_both_endian_dword(d->size); } write_byte((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((BYTE)identifier_size); switch (DirType) { case DOT_RECORD: write_byte(0); break; case DOT_DOT_RECORD: write_byte(1); break; case SUBDIRECTORY_RECORD: if (joliet) write_string_as_big_endian_unicode(d->joliet_name); else write_string(d->name_on_cd); break; case FILE_RECORD: if (joliet) { write_string_as_big_endian_unicode(d->joliet_name); } else { write_string(d->name_on_cd); if (d->extension_on_cd[0] != 0) { write_byte('.'); write_string(d->extension_on_cd); } 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(PDATE_AND_TIME dt, time_t *time) { struct tm *timedef; timedef = gmtime(time); dt->second = timedef->tm_sec; dt->minute = timedef->tm_min; dt->hour = timedef->tm_hour; dt->day = timedef->tm_mday; dt->month = timedef->tm_mon + 1; dt->year = timedef->tm_year + 1900; } /*----------------------------------------------------------------------------- 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, const char *name) { c = toupper(c & 0xFF); if (!accept_punctuation_marks && !isalnum(c) && c != '_') error_exit("Punctuation mark in %s", name); return c; } /*----------------------------------------------------------------------------- This function checks to see if there's a cdname conflict. -----------------------------------------------------------------------------*/ #if defined(_WIN32) && !defined(strcasecmp) #define strcasecmp stricmp #endif // _WIN32 static BOOL cdname_exists(PDIR_RECORD d) { PDIR_RECORD p = d->parent->first_record; while (p) { if ( p != d && !strcasecmp(p->name_on_cd, d->name_on_cd) && !strcasecmp(p->extension_on_cd, d->extension_on_cd) ) return TRUE; p = p->next_in_directory; } return FALSE; } static void parse_filename_into_dirrecord(const char* filename, PDIR_RECORD d, BOOL dir) { const char *s = filename; char *t = d->name_on_cd; char *n = d->name; int joliet_length; int filename_counter; filename_counter = 1; while (*s != 0) { if (*s == '.') { s++; break; } if ((size_t)(t-d->name_on_cd) < sizeof(d->name_on_cd)-1) *t++ = check_for_punctuation(*s, filename); else if (!joliet) error_exit("'%s' is not ISO-9660, aborting...", filename); if ((size_t)(n-d->name) < sizeof(d->name)-1) *n++ = *s; else if (!joliet) error_exit("'%s' is not ISO-9660, aborting...", filename); s++; } // Check for extension length if (!joliet && strlen(s) > MAX_EXTENSION_LENGTH) { error_exit("'%s' has too long extension, aborting...", filename); } *t = 0; strcpy(d->extension, s); t = d->extension_on_cd; while (*s != 0) { if ((size_t)(t-d->extension_on_cd) < sizeof(d->extension_on_cd)-1) *t++ = check_for_punctuation(*s, filename); else if (!joliet) error_exit("'%s' is not ISO-9660, aborting...", filename); s++; } *t = 0; *n = 0; if (dir) { if (d->extension[0] != 0) { if (!joliet) error_exit("Directory with extension '%s'", filename); } d->flags = DIRECTORY_FLAG; } else { d->flags = 0; } filename_counter = 1; while (cdname_exists(d)) { // the file name must be at least 8 chars long if (strlen(d->name_on_cd)<8) error_exit("'%s' is a duplicate file name, aborting...", filename); if ((d->name_on_cd[8] == '.') && (strlen(d->name_on_cd) < 13)) error_exit("'%s' is a duplicate file name, aborting...", filename); // max 255 times for equal short filename if (filename_counter>255) error_exit("'%s' is a duplicate file name, aborting...", filename); d->name_on_cd[8] = '~'; memset(&d->name_on_cd[9],0,5); sprintf(&d->name_on_cd[9],"%d",filename_counter); filename_counter++; } if (joliet) { joliet_length = strlen(filename); if (joliet_length > 64) error_exit("'%s' is not Joliet, aborting...", filename); d->joliet_name = malloc(joliet_length + 1); if (d->joliet_name == NULL) error_exit("Insufficient memory"); strcpy(d->joliet_name, filename); } } /*----------------------------------------------------------------------------- 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. -----------------------------------------------------------------------------*/ #ifdef _WIN32 /* Win32 version */ PDIR_RECORD new_directory_record(struct _finddata_t *f, PDIR_RECORD parent) { PDIR_RECORD d; d = calloc(1, sizeof(*d)); if (d == NULL) error_exit("Insufficient memory"); d->next_in_memory = root.next_in_memory; root.next_in_memory = d; /* I need the parent set before calling parse_filename_into_dirrecord(), because that functions checks for duplicate file names*/ d->parent = parent; parse_filename_into_dirrecord(f->name, d, f->attrib & _A_SUBDIR); convert_date_and_time(&d->date_and_time, &f->time_write); d->flags |= f->attrib & _A_HIDDEN ? HIDDEN_FLAG : 0; d->size = d->joliet_size = f->size; d->next_in_directory = parent->first_record; parent->first_record = d; return d; } #else /* Linux version */ PDIR_RECORD new_directory_record(struct dirent *entry, struct stat *stbuf, PDIR_RECORD parent) { PDIR_RECORD d; d = calloc(1, sizeof(*d)); if (d == NULL) error_exit("Insufficient memory"); d->next_in_memory = root.next_in_memory; root.next_in_memory = d; /* I need the parent set before calling parse_filename_into_dirrecord(), because that functions checks for duplicate file names*/ d->parent = parent; #ifdef HAVE_D_TYPE parse_filename_into_dirrecord(entry->d_name, d, entry->d_type == DT_DIR); #else parse_filename_into_dirrecord(entry->d_name, d, S_ISDIR(stbuf->st_mode)); #endif convert_date_and_time(&d->date_and_time, &stbuf->st_mtime); d->flags |= entry->d_name[0] == '.' ? HIDDEN_FLAG : 0; d->size = d->joliet_size = stbuf->st_size; d->next_in_directory = parent->first_record; parent->first_record = d; return d; } #endif /*----------------------------------------------------------------------------- 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(PDIR_RECORD p, PDIR_RECORD q) { int n = strcmp(p->name_on_cd, q->name_on_cd); if (n == 0) n = strcmp(p->extension_on_cd, q->extension_on_cd); 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(PDIR_RECORD p, PDIR_RECORD q) { int n = p->level - q->level; if (p == q) return 0; 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. -----------------------------------------------------------------------------*/ #ifdef _WIN32 static void make_directory_records(PDIR_RECORD d) { PDIR_RECORD new_d; struct _finddata_t f; char *old_end_source; int findhandle; d->first_record = NULL; strcpy(end_source, "*.*"); findhandle = _findfirst(source, &f); if (findhandle != 0) { do { if ((f.attrib & (_A_HIDDEN | _A_SUBDIR)) == 0 && f.name[0] != '.') { if (strcmp(f.name, DIRECTORY_TIMESTAMP) == 0) { convert_date_and_time(&d->date_and_time, &f.time_write); } else { if (verbosity == VERBOSE) { old_end_source = end_source; strcpy(end_source, f.name); printf("%d: file %s\n", d->level, source); end_source = old_end_source; } (void) new_directory_record(&f, d); } } } while (_findnext(findhandle, &f) == 0); _findclose(findhandle); } strcpy(end_source, "*.*"); findhandle = _findfirst(source, &f); if (findhandle) { do { if (f.attrib & _A_SUBDIR && f.name[0] != '.') { old_end_source = end_source; append_string_to_source(f.name); *end_source++ = DIR_SEPARATOR_CHAR; if (verbosity == VERBOSE) { *end_source = 0; printf("%d: directory %s\n", d->level + 1, source); } if (d->level < MAX_LEVEL) { 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(findhandle, &f) == 0); _findclose(findhandle); } // sort directory d->first_record = sort_linked_list(d->first_record, 0, compare_directory_order); } #else /* Linux version */ static void make_directory_records(PDIR_RECORD d) { PDIR_RECORD new_d; DIR *dirp; struct dirent *entry; char *old_end_source; struct stat stbuf; char buf[MAX_PATH]; d->first_record = NULL; #ifdef HAVE_D_TYPE dirp = opendir(source); if (dirp != NULL) { while ((entry = readdir(dirp)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; // skip self and parent if (entry->d_type == DT_REG) // normal file { // Check for an absolute path if (source[0] == DIR_SEPARATOR_CHAR) { strcpy(buf, source); strcat(buf, DIR_SEPARATOR_STRING); strcat(buf, entry->d_name); } else { if (!getcwd(buf, sizeof(buf))) error_exit("Cannot get CWD: %s\n", strerror(errno)); strcat(buf, DIR_SEPARATOR_STRING); strcat(buf, source); strcat(buf, entry->d_name); } if (stat(buf, &stbuf) == -1) { error_exit("Cannot access '%s' (%s)\n", buf, strerror(errno)); return; } if (strcmp(entry->d_name, DIRECTORY_TIMESTAMP) == 0) { convert_date_and_time(&d->date_and_time, &stbuf.st_ctime); } else { if (verbosity == VERBOSE) { printf("%d: file %s\n", d->level, buf); } (void) new_directory_record(entry, &stbuf, d); } } } closedir(dirp); } else { error_exit("Cannot open '%s'\n", source); return; } dirp = opendir(source); if (dirp != NULL) { while ((entry = readdir(dirp)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; // skip self and parent if (entry->d_type == DT_DIR) // directory { old_end_source = end_source; append_string_to_source(entry->d_name); *end_source++ = DIR_SEPARATOR_CHAR; *end_source = 0; if (verbosity == VERBOSE) { printf("%d: directory %s\n", d->level + 1, source); } if (d->level < MAX_LEVEL) { // Check for an absolute path if (source[0] == DIR_SEPARATOR_CHAR) { strcpy(buf, source); } else { if (!getcwd(buf, sizeof(buf))) error_exit("Cannot get CWD: %s\n", strerror(errno)); strcat(buf, DIR_SEPARATOR_STRING); strcat(buf, source); } if (stat(buf, &stbuf) == -1) { error_exit("Cannot access '%s' (%s)\n", buf, strerror(errno)); return; } new_d = new_directory_record(entry, &stbuf, 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; *end_source = 0; } } closedir(dirp); } else { error_exit("Cannot open '%s'\n", source); return; } #else dirp = opendir(source); if (dirp != NULL) { while ((entry = readdir(dirp)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; // skip self and parent // Check for an absolute path if (source[0] == DIR_SEPARATOR_CHAR) { strcpy(buf, source); strcat(buf, DIR_SEPARATOR_STRING); strcat(buf, entry->d_name); } else { if (!getcwd(buf, sizeof(buf))) error_exit("Cannot get CWD: %s\n", strerror(errno)); strcat(buf, DIR_SEPARATOR_STRING); strcat(buf, source); strcat(buf, entry->d_name); } if (stat(buf, &stbuf) == -1) { error_exit("Cannot access '%s' (%s)\n", buf, strerror(errno)); return; } if (S_ISDIR(stbuf.st_mode)) { old_end_source = end_source; append_string_to_source(entry->d_name); *end_source++ = DIR_SEPARATOR_CHAR; *end_source = 0; if (verbosity == VERBOSE) { printf("%d: directory %s\n", d->level + 1, source); } if (d->level < MAX_LEVEL) { new_d = new_directory_record(entry, &stbuf, 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; *end_source = 0; } else if (S_ISREG(stbuf.st_mode)) { if (strcmp(entry->d_name, DIRECTORY_TIMESTAMP) == 0) { convert_date_and_time(&d->date_and_time, &stbuf.st_ctime); } else { if (verbosity == VERBOSE) { printf("%d: file %s\n", d->level, buf); } (void) new_directory_record(entry, &stbuf, d); } } } closedir(dirp); } else { error_exit("Cannot open '%s'\n", source); return; } #endif // sort directory d->first_record = sort_linked_list(d->first_record, 0, compare_directory_order); } #endif static PDIR_RECORD new_empty_dirrecord(PDIR_RECORD d, BOOL directory) { PDIR_RECORD new_d; new_d = calloc(1, sizeof(*new_d)); new_d->parent = d; new_d->level = d->level + 1; new_d->next_in_directory = d->first_record; d->first_record = new_d; new_d->next_in_memory = root.next_in_memory; root.next_in_memory = new_d; new_d->date_and_time = d->date_and_time; if (directory) { new_d->flags |= DIRECTORY_FLAG; new_d->next_in_path_table = root.next_in_path_table; root.next_in_path_table = new_d; } return new_d; } #ifdef _WIN32 static BOOL get_cd_file_time(HANDLE handle, PDATE_AND_TIME cd_time_info) { FILETIME file_time; SYSTEMTIME sys_time; if (!GetFileTime(handle, NULL, NULL, &file_time)) return FALSE; FileTimeToSystemTime(&file_time, &sys_time); memset(cd_time_info, 0, sizeof(*cd_time_info)); cd_time_info->year = sys_time.wYear; cd_time_info->month = sys_time.wMonth; cd_time_info->day = sys_time.wDay; cd_time_info->hour = sys_time.wHour; cd_time_info->minute = sys_time.wMinute; cd_time_info->second = sys_time.wSecond; return TRUE; } #endif static void scan_specified_files(PDIR_RECORD d, struct target_dir_entry *dir) { PDIR_RECORD new_d; #ifdef _WIN32 HANDLE open_file; LARGE_INTEGER file_size; #else struct stat stbuf; #endif struct target_file *file; struct target_dir_entry *child; d->first_record = NULL; for (file = dir->head; file; file = file->next) { if (strcmp(file->target_name, DIRECTORY_TIMESTAMP) == 0) { #ifdef _WIN32 if ((open_file = CreateFileA(file->source_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) { error_exit("Cannot open timestamp file '%s'\n", file->source_name); } if (!get_cd_file_time(open_file, &d->date_and_time)) { error_exit("Cannot stat timestamp file '%s'\n", file->source_name); } CloseHandle(open_file); #else if (stat(file->target_name, &stbuf) == -1) { error_exit("Cannot stat timestamp file '%s'\n", file->source_name); } convert_date_and_time(&d->date_and_time, &stbuf.st_ctime); #endif } else { if (verbosity == VERBOSE) { printf("%d: file %s (from %s)\n", d->level, file->target_name, file->source_name); } new_d = new_empty_dirrecord(d, FALSE); parse_filename_into_dirrecord(file->target_name, new_d, FALSE); #ifdef _WIN32 if ((open_file = CreateFileA(file->source_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) { error_exit("Cannot open file '%s'\n", file->source_name); } if (!get_cd_file_time(open_file, &new_d->date_and_time)) { error_exit("Cannot stat file '%s'\n", file->source_name); } if (!GetFileSizeEx(open_file, &file_size)) { error_exit("Cannot get file size of '%s'\n", file->source_name); } new_d->size = new_d->joliet_size = file_size.QuadPart; new_d->orig_name = file->source_name; CloseHandle(open_file); #else if (stat(file->source_name, &stbuf) == -1) { error_exit("Cannot find '%s' (target '%s')\n", file->source_name, file->target_name); } convert_date_and_time(&new_d->date_and_time, &stbuf.st_mtime); new_d->size = new_d->joliet_size = stbuf.st_size; new_d->orig_name = file->source_name; #endif } } for (child = dir->child; child; child = child->next) { if (verbosity == VERBOSE) { printf("%d: directory %s\n", d->level, child->case_name); } new_d = new_empty_dirrecord(d, TRUE); parse_filename_into_dirrecord(child->case_name, new_d, TRUE); scan_specified_files(new_d, child); } /* sort directory */ d->first_record = sort_linked_list(d->first_record, 0, compare_directory_order); source[0] = 0; end_source = source; } /*----------------------------------------------------------------------------- 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(PDIR_RECORD d) { if (d != &root) { get_file_specifications(d->parent); if (d->joliet_name == NULL) append_string_to_source(d->name); else append_string_to_source(d->joliet_name); if (((d->flags & DIRECTORY_FLAG) == 0 || joliet) && d->extension[0] != 0) { if (d->joliet_name == NULL) { *end_source++ = '.'; append_string_to_source(d->extension); } } if (d->flags & DIRECTORY_FLAG) *end_source++ = DIR_SEPARATOR_CHAR; } } static void get_time_string(char *str) { sprintf(str, "%04d%02d%02d%02d%02d%02d00", root.date_and_time.year, root.date_and_time.month, root.date_and_time.day, root.date_and_time.hour, root.date_and_time.minute, root.date_and_time.second); } static BOOL write_from_file(FILE *file, DWORD size) { if (cd.file != NULL) { int n; fseek(file, 0, SEEK_SET); while (size > 0) { n = BUFFER_SIZE - cd.count; if ((DWORD)n > size) n = size; if (fread(cd.buffer + cd.count, n, 1, file) < 1) return FALSE; cd.count += n; if (cd.count == BUFFER_SIZE) flush_buffer(); cd.sector += n / SECTOR_SIZE; cd.offset += n % SECTOR_SIZE; size -= n; } } else { cd.sector += size / SECTOR_SIZE; cd.offset += size % SECTOR_SIZE; } return TRUE; } static void pass(void) { PDIR_RECORD d, q; unsigned int index; unsigned int name_length; DWORD size; DWORD number_of_sectors; char *old_end_source; FILE *file; PBOOT_HEADER header; PBOOT_ENTRY entry; char timestring[17]; get_time_string(timestring); // first 16 sectors are zeros write_block(16 * SECTOR_SIZE, 0); // Primary Volume Descriptor if (make_bridged_udf) { write_string("\1CD001\1"); write_byte(0); write_bytecounted_string(32, "", ' '); // system identifier write_bytecounted_string(32, volume_label, ' '); // volume label write_block(8, 0); write_both_endian_dword(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_dword(path_table_size); write_little_endian_dword(little_endian_path_table_sector); write_little_endian_dword(0); // second little endian path table write_big_endian_dword(big_endian_path_table_sector); write_big_endian_dword(0); // second big endian path table write_directory_record(&root, DOT_RECORD, FALSE); write_bytecounted_string(128, volume_label, ' '); // volume set identifier write_bytecounted_string(128, PUBLISHER_ID, ' '); // publisher identifier write_bytecounted_string(128, DATA_PREP_ID, ' '); // data preparer identifier write_bytecounted_string(128, APP_ID, ' '); // application identifier write_bytecounted_string(37, "", ' '); // copyright file identifier write_bytecounted_string(37, "", ' '); // abstract file identifier write_bytecounted_string(37, "", ' '); // bibliographic file identifier write_string(timestring); // volume creation write_byte(0); write_string(timestring); // 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(); } // Boot Volume Descriptor if (eltorito) { write_byte(0); // Boot record ID write_string("CD001\1"); write_bytecounted_string(32, "EL TORITO SPECIFICATION", 0); // El-Torito identifier write_block(32, 0); // unused write_little_endian_dword(boot_catalog_sector); // pointer to boot catalog fill_sector(); } // Supplementary Volume Descriptor if (joliet) { write_string("\2CD001\1"); write_byte(0); write_bytecounted_string_as_big_endian_unicode(32, "", ' '); // system identifier write_bytecounted_string_as_big_endian_unicode(32, volume_label, ' '); // volume label write_block(8, 0); write_both_endian_dword(total_sectors); write_string("%/E"); write_block(29, 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_dword(joliet_path_table_size); write_little_endian_dword(joliet_little_endian_path_table_sector); write_little_endian_dword(0); // second little endian path table write_big_endian_dword(joliet_big_endian_path_table_sector); write_big_endian_dword(0); // second big endian path table write_directory_record(&root, DOT_RECORD, TRUE); write_bytecounted_string_as_big_endian_unicode(128, volume_label, ' '); // volume set identifier write_bytecounted_string_as_big_endian_unicode(128, PUBLISHER_ID, ' '); // publisher identifier write_bytecounted_string_as_big_endian_unicode(128, DATA_PREP_ID, ' '); // data preparer identifier write_bytecounted_string_as_big_endian_unicode(128, APP_ID, ' '); // application identifier write_bytecounted_string_as_big_endian_unicode(37, "", ' '); // copyright file identifier write_bytecounted_string_as_big_endian_unicode(37, "", ' '); // abstract file identifier write_bytecounted_string_as_big_endian_unicode(37, "", ' '); // bibliographic file identifier write_string(timestring); // volume creation write_byte(0); write_string(timestring); // 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 if (make_bridged_udf) { write_string("\377CD001\1"); fill_sector(); } // TODO: Add UDF support! // Boot Catalog if (eltorito) { boot_catalog_sector = cd.sector; // Validation entry header write_byte(boot_validation_header.header_id); write_byte(boot_validation_header.platform_id); write_little_endian_word(0); // reserved write_bytecounted_string(24, MANUFACTURER_ID, 0); // Manufacturer identifier write_little_endian_word(0x62E); // checksum // FIXME: This is hardcoded!! write_little_endian_word(0xAA55); // signature // Default entry write_byte(default_boot_entry.boot_id); write_byte(default_boot_entry.boot_emu_type); write_little_endian_word(default_boot_entry.load_segment); write_byte(0); // partition type write_byte(0); // unused write_little_endian_word(default_boot_entry.sector_count); write_little_endian_dword(default_boot_entry.load_rba); write_block(20, 0); // unused // Loop through each boot header header = boot_header_list; while (header) { write_byte(header->header_id); write_byte(header->platform_id); write_little_endian_word(header->num_entries); write_block(28, 0); // Identifier string (unused) // Loop through each boot entry entry = header->entry_list; while (entry) { write_byte(entry->boot_id); write_byte(entry->boot_emu_type); write_little_endian_word(entry->load_segment); write_byte(0); // partition type write_byte(0); // unused write_little_endian_word(entry->sector_count); write_little_endian_dword(entry->load_rba); write_block(20, 0); // Selection criteria (unused) entry = entry->next_entry; } header = header->next_header; } fill_sector(); } // Boot Images if (eltorito) { default_boot_entry.load_rba = cd.sector; file = fopen(default_boot_entry.bootimage, "rb"); if (file == NULL) error_exit("Cannot open '%s'\n", default_boot_entry.bootimage); fseek(file, 0, SEEK_END); size = ftell(file); if (size == 0 || (size % 2048)) { fclose(file); error_exit("Invalid boot image size (%lu bytes)\n", size); } // Sector count in 512 byte sectors and rounded up default_boot_entry.sector_count = (size + 511) / 512; if (!write_from_file(file, size)) { fclose(file); error_exit("Read error in file '%s'\n", default_boot_entry.bootimage); } fclose(file); // Loop through each boot header header = boot_header_list; while (header) { // Loop through each boot entry entry = header->entry_list; while (entry) { entry->load_rba = cd.sector; file = fopen(entry->bootimage, "rb"); if (file == NULL) error_exit("Cannot open '%s'\n", entry->bootimage); fseek(file, 0, SEEK_END); size = ftell(file); if (size == 0 || (size % 2048)) { fclose(file); error_exit("Invalid boot image size (%lu bytes)\n", size); } // Sector count in 512 byte sectors and rounded up entry->sector_count = (size + 511) / 512; if (!write_from_file(file, size)) { fclose(file); error_exit("Read error in file '%s'\n", entry->bootimage); } fclose(file); entry = entry->next_entry; } header = header->next_header; } // 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_dword(root.sector); write_little_endian_word(1); write_byte(0); write_byte(0); index = 1; root.path_table_index = 1; for (d = root.next_in_path_table; d != NULL; d = d->next_in_path_table) { name_length = strlen(d->name_on_cd); write_byte((BYTE)name_length); write_byte(0); // number of sectors in extended attribute record write_little_endian_dword(d->sector); write_little_endian_word(d->parent->path_table_index); write_string(d->name_on_cd); 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_dword(root.sector); write_big_endian_word(1); write_byte(0); write_byte(0); for (d = root.next_in_path_table; d != NULL; d = d->next_in_path_table) { name_length = strlen(d->name_on_cd); write_byte((BYTE)name_length); write_byte(0); // number of sectors in extended attribute record write_big_endian_dword(d->sector); write_big_endian_word(d->parent->path_table_index); write_string(d->name_on_cd); if (name_length & 1) write_byte(0); } fill_sector(); if (joliet) { // Little Endian Path Table joliet_little_endian_path_table_sector = cd.sector; write_byte(1); write_byte(0); // number of sectors in extended attribute record write_little_endian_dword(root.joliet_sector); write_little_endian_word(1); write_byte(0); write_byte(0); for (d = root.next_in_path_table; d != NULL; d = d->next_in_path_table) { name_length = strlen(d->joliet_name) * 2; write_byte((BYTE)name_length); write_byte(0); // number of sectors in extended attribute record write_little_endian_dword(d->joliet_sector); write_little_endian_word(d->parent->path_table_index); write_string_as_big_endian_unicode(d->joliet_name); } joliet_path_table_size = (cd.sector - joliet_little_endian_path_table_sector) * SECTOR_SIZE + cd.offset; fill_sector(); // Big Endian Path Table joliet_big_endian_path_table_sector = cd.sector; write_byte(1); write_byte(0); // number of sectors in extended attribute record write_big_endian_dword(root.joliet_sector); write_big_endian_word(1); write_byte(0); write_byte(0); for (d = root.next_in_path_table; d != NULL; d = d->next_in_path_table) { name_length = strlen(d->joliet_name) * 2; write_byte((BYTE)name_length); write_byte(0); // number of sectors in extended attribute record write_big_endian_dword(d->joliet_sector); write_big_endian_word(d->parent->path_table_index); write_string_as_big_endian_unicode(d->joliet_name); } fill_sector(); } // TODO: Add UDF support! #if 0 // Boot Images ?? #endif // TODO: Add CRC block for header! // Directories and files for (d = &root; d != NULL; d = d->next_in_path_table) { // write directory d->sector = cd.sector; write_directory_record(d, DOT_RECORD, FALSE); write_directory_record(d == &root ? d : d->parent, DOT_DOT_RECORD, FALSE); for (q = d->first_record; q != NULL; q = q->next_in_directory) { write_directory_record(q, q->flags & DIRECTORY_FLAG ? SUBDIRECTORY_RECORD : FILE_RECORD, FALSE); } fill_sector(); d->size = (cd.sector - d->sector) * SECTOR_SIZE; // write directory for joliet if (joliet) { d->joliet_sector = cd.sector; write_directory_record(d, DOT_RECORD, TRUE); write_directory_record(d == &root ? d : d->parent, DOT_DOT_RECORD, TRUE); for (q = d->first_record; q != NULL; q = q->next_in_directory) { write_directory_record(q, q->flags & DIRECTORY_FLAG ? SUBDIRECTORY_RECORD : FILE_RECORD, TRUE); } fill_sector(); d->joliet_size = (cd.sector - d->joliet_sector) * SECTOR_SIZE; bytes_in_directories += d->joliet_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 = q->joliet_sector = cd.sector; size = q->size; if (cd.file == NULL) { 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 { const char *file_source; old_end_source = end_source; if (!q->orig_name) { get_file_specifications(q); *end_source = 0; file_source = source; } else { file_source = q->orig_name; } if (verbosity == VERBOSE) printf("Writing contents of %s\n", file_source); file = fopen(file_source, "rb"); if (file == NULL) error_exit("Cannot open '%s'\n", file_source); if (!write_from_file(file, size)) { fclose(file); error_exit("Read error in file '%s'\n", file_source); } fclose(file); end_source = old_end_source; fill_sector(); } } } } // TODO: Add final CRC block! total_sectors = (DWORD)cd.sector; } static char HELP[] = "\n" "CDMAKE CD-ROM Premastering Utility\n" "Copyright (C) 1997 Philip J. Erdelsky\n" "Copyright (C) 2003-2016 ReactOS Team\n" "\n\n" "CDMAKE [-vN] [-p] [-s N] [-m] [-j] [-q] " // "[-x] " "[-pN] [-eN] [-b bootimage]\n" " [-bootdata:N###...#]\n" " source volume image\n" "\n" "Mandatory options:\n" " source Specifications of base directory containing all files to be\n" " written to CD-ROM image. Can be a directory, or a path to a\n" " file list (in which case the path must start with a '@').\n" " volume Volume label.\n" " image Image file or device.\n" "\n" "General options:\n" " -vN Verbosity level. Valid values for 'N' are:\n" " 0: Quiet mode - display nothing but error messages.\n" " 1: Normal mode (default).\n" " 2: Verbose mode - display file information as files are\n" " scanned and written. Overrides the -p option.\n" " -p Show progress while writing.\n" " -s N Abort operation before beginning write if image will be larger\n" " than N megabytes (i.e. 1024*1024*N bytes).\n" " -q Only scan the source files; do not create an image.\n" // " -x Compute and encode the AutoCRC value in the image.\n" "\n" "ISO 9660 and Joliet options:\n" " -m Accept punctuation marks other than underscores in names and\n" " extensions.\n" " -j Generate Joliet filename records.\n" "\n" // "UDF options:\n" // " Not implemented yet!\n" // "\n" "El-Torito boot options:\n" " -pN Boot platform ID in hex format (default: 00 for a BIOS system).\n" " -eN Boot media emulation. Valid values for 'N' are:\n" " 0 (or nothing): No emulation.\n" " 1: 1.2 MB diskette.\n" " 2: 1.44MB diskette.\n" " 3: 2.88MB diskette.\n" " 4: Hard disk.\n" " -b bootimage Create a single-boot El-Torito image.\n" " -bootdata: Create a multi-boot El-Torito image. This option cannot be\n" " combined with the -b option.\n" " Syntax:\n" " -bootdata:N###...#\n" " 'N': number of boot entries following.\n" " defaultBootEntry: The default boot entry, needed in all cases.\n" " Used by BIOSes which do not support additional boot entries.\n" " bootEntryX: Additional boot entries.\n" " - Do not use spaces.\n" " - Each multi-boot entry must be delimited with a hash symbol (#).\n" " - Each option for a boot entry must be delimited with a comma (,).\n" " - Each boot entry must specify the platform ID.\n"; /*----------------------------------------------------------------------------- Program execution starts here. -----------------------------------------------------------------------------*/ #if (defined(__GNUC__) || (_MSC_VER < 1900)) char* strtok_s(char *str, const char *delim, char **ctx) { if (delim == NULL || ctx == NULL || (str == NULL && *ctx == NULL)) { return NULL; } if (!str) str = *ctx; while (*str && strchr(delim, *str)) str++; if (!*str) { *ctx = str; return NULL; } *ctx = str + 1; while (**ctx && !strchr(delim, **ctx)) (*ctx)++; if (**ctx) *(*ctx)++ = '\0'; return str; } #endif static void init_boot_entry(PBOOT_ENTRY boot_entry, BYTE boot_emu_type, WORD load_segment, char* bootimage) { boot_entry->boot_id = 0x88; // Bootable entry boot_entry->boot_emu_type = boot_emu_type; // 0: No emulation, etc... boot_entry->load_segment = load_segment; // If 0 then use default 0x07C0 boot_entry->bootimage[0] = '\0'; strncpy(boot_entry->bootimage, bootimage, sizeof(boot_entry->bootimage)); boot_entry->bootimage[sizeof(boot_entry->bootimage)-1] = '\0'; } int main(int argc, char **argv) { time_t timestamp = time(NULL); int i; char *t; if (argc < 2) { puts(HELP); return 1; } // Initialize CD-ROM write buffer cd.file = NULL; cd.filespecs[0] = 0; cd.buffer = malloc(BUFFER_SIZE); if (cd.buffer == NULL) error_exit("Insufficient memory"); // Initialize root directory memset(&root, 0, sizeof(root)); root.level = 1; root.flags = DIRECTORY_FLAG; convert_date_and_time(&root.date_and_time, ×tamp); // Initialize parameters scan_files_only = FALSE; make_bridged_udf = TRUE; // compute_crc = FALSE; verbosity = NORMAL; show_progress = FALSE; size_limit = 0; accept_punctuation_marks = FALSE; source[0] = 0; volume_label[0] = 0; // Initialize boot information eltorito = FALSE; multi_boot = FALSE; boot_validation_header.header_id = 1; // Validation header ID boot_validation_header.platform_id = 0; // x86/64 BIOS system init_boot_entry(&default_boot_entry, 0, // No emulation 0, // Use default 0x07C0 ""); default_boot_entry.sector_count = 0; default_boot_entry.load_rba = 0; boot_header_list = NULL; // Scan command line arguments for (i = 1; i < argc; i++) { if (strncmp(argv[i], "-v", 2) == 0) { t = argv[i] + 2; if (*t == 0) // Normal verbosity level. verbosity = NORMAL; else // Verbosity level in decimal verbosity = strtoul(t, NULL, 10); // Check for validity if (verbosity > VERBOSE) verbosity = NORMAL; // Disable by default, unless we are in normal verbosity level. // If progress is still wanted, use '-p'. if (verbosity == QUIET || verbosity == VERBOSE) show_progress = FALSE; } else if (strcmp(argv[i], "-p") == 0) show_progress = TRUE; else if (strncmp(argv[i], "-s", 2) == 0) { t = argv[i] + 2; if (*t == 0) { if (++i < argc) t = argv[i]; else error_exit("Missing size limit parameter"); } // size_limit = strtoul(t, NULL, 10); 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], "-m") == 0) accept_punctuation_marks = TRUE; else if (strcmp(argv[i], "-j") == 0) joliet = TRUE; else if (strncmp(argv[i], "-e", 2) == 0) { // Check whether the multi-boot option '-bootdata:' was already set. // If so, print an error and bail out. if (eltorito && multi_boot) error_exit("Single-boot and multi-boot entries cannot be combined"); eltorito = TRUE; multi_boot = FALSE; t = argv[i] + 2; if (*t == 0) // No emulation default_boot_entry.boot_emu_type = 0; else // ID in decimal default_boot_entry.boot_emu_type = (BYTE)strtoul(t, NULL, 10); } else if (strncmp(argv[i], "-p", 2) == 0) { // Check whether the multi-boot option '-bootdata:' was already set. // If so, print an error and bail out. if (eltorito && multi_boot) error_exit("Single-boot and multi-boot entries cannot be combined"); eltorito = TRUE; multi_boot = FALSE; // Platform ID in hexadecimal boot_validation_header.platform_id = (BYTE)strtoul(argv[i] + 2, NULL, 16); } else if (strcmp(argv[i], "-b") == 0) { // Check whether the multi-boot option '-bootdata:' was already set. // If so, print an error and bail out. if (eltorito && multi_boot) error_exit("Single-boot and multi-boot entries cannot be combined"); eltorito = TRUE; multi_boot = FALSE; strncpy(default_boot_entry.bootimage, argv[++i], sizeof(default_boot_entry.bootimage)); default_boot_entry.bootimage[sizeof(default_boot_entry.bootimage)-1] = '\0'; } else if (strncmp(argv[i], "-bootdata:", sizeof("-bootdata:") - 1) == 0) { char *bootdata, *entry_ctx, *option_ctx; DWORD num_boot_entries = 0; BOOL default_entry = TRUE; // Start by setting the default boot entry PBOOT_HEADER boot_header = NULL; // Current boot header PBOOT_ENTRY boot_entry = NULL; // The last boot entry in the current boot header BYTE platform_id, old_platform_id = 0; BYTE boot_emu_type; WORD load_segment; char bootimage[512]; // Check whether the single-boot option '-b' was already set. // If so, print an error and bail out. if (eltorito && !multi_boot) error_exit("Single-boot and multi-boot entries cannot be combined"); t = argv[i] + (sizeof("-bootdata:") - 1); bootdata = strdup(t); if (bootdata == NULL) error_exit("Insufficient memory"); eltorito = TRUE; multi_boot = TRUE; // FIXME: Paths containing '#' or ',' or ' ' are not yet supported!! // Start parsing... t = strtok_s(bootdata, "#", &entry_ctx); if (t == NULL) { free(bootdata); error_exit("Malformed bootdata command"); } num_boot_entries = strtoul(t, NULL, 10); while (num_boot_entries--) { // Reset to default values platform_id = 0; // x86/64 BIOS system boot_emu_type = 0; // No emulation load_segment = 0; // Use default 0x07C0 bootimage[0] = '\0'; t = strtok_s(NULL, "#", &entry_ctx); if (t == NULL) { free(bootdata); error_exit("Malformed bootdata command"); } t = strtok_s(t, ",", &option_ctx); while (t != NULL) { switch (*t++) { case 'b': // Boot sector file { char *q; // Searches for any of the valid separators: // '#' starts a new boot entry; // ',' starts a new boot option; // ' ' finishes the bootdata command. q = strpbrk(t, "#, "); if (!q) q = t + strlen(t); strncpy(bootimage, t, q - t + 1); break; } case 'p': // Platform ID { // Platform ID in hexadecimal platform_id = (BYTE)strtoul(t, NULL, 16); break; } case 'e': // No floppy-disk emulation { if (*t == 0) // No emulation boot_emu_type = 0; else // ID in decimal boot_emu_type = (BYTE)strtoul(t, NULL, 10); break; } case 't': // Loading segment { if (*t == 0) // Not specified: use default 0x07C0 load_segment = 0; else // Segment in hexadecimal load_segment = (BYTE)strtoul(t, NULL, 16); break; } default: free(bootdata); error_exit("Malformed bootdata command"); } t = strtok_s(NULL, ",", &option_ctx); } // Create a new entry and possibly a boot header if (default_entry) { // Initialize the default boot entry and header boot_validation_header.header_id = 1; // Validation header ID boot_validation_header.platform_id = platform_id; init_boot_entry(&default_boot_entry, boot_emu_type, load_segment, bootimage); // Default entry is now initialized. default_entry = FALSE; } else { // Initialize a new boot entry PBOOT_ENTRY old_boot_entry = boot_entry; boot_entry = calloc(1, sizeof(*boot_entry)); if (boot_entry == NULL) error_exit("Insufficient memory"); // boot_entry->next_entry = NULL; init_boot_entry(boot_entry, boot_emu_type, load_segment, bootimage); // Create a new boot header if we don't have one yet if (boot_header == NULL) { boot_header = calloc(1, sizeof(*boot_header)); if (boot_header == NULL) error_exit("Insufficient memory"); boot_header->header_id = 0x91; // So far this is the last boot header boot_header->platform_id = platform_id; // boot_header->next_header = NULL; // boot_header->num_entries = 0; // boot_header->entry_list = NULL; old_boot_entry = NULL; old_platform_id = platform_id; boot_header_list = boot_header; } else { // Create a new boot header if we change the platform ID if (old_platform_id != platform_id) { PBOOT_HEADER prev_boot_header = boot_header; boot_header = calloc(1, sizeof(*boot_header)); if (boot_header == NULL) error_exit("Insufficient memory"); boot_header->header_id = 0x91; // So far this is the last boot header boot_header->platform_id = platform_id; // boot_header->next_header = NULL; // boot_header->num_entries = 0; // boot_header->entry_list = NULL; old_boot_entry = NULL; old_platform_id = platform_id; // Link into the header list prev_boot_header->header_id = 0x90; // The previous boot header was not the last one prev_boot_header->next_header = boot_header; } } // Add the entry into the header ++boot_header->num_entries; if (old_boot_entry == NULL) boot_header->entry_list = boot_entry; else old_boot_entry->next_entry = boot_entry; } } free(bootdata); } else if (strcmp(argv[i], "-q") == 0) scan_files_only = TRUE; // else if (strcmp(argv[i], "-x") == 0) // compute_crc = 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 (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"); if (source[0] != '@') { /* set source[] and end_source to source directory, * with a terminating directory separator */ end_source = source + strlen(source); if (end_source[-1] == ':') *end_source++ = '.'; if (end_source[-1] != DIR_SEPARATOR_CHAR) *end_source++ = DIR_SEPARATOR_CHAR; /* scan all files and create directory structure in memory */ make_directory_records(&root); } else { char *trimmedline, *targetname, *normdir, *srcname, *eq; char lineread[1024]; FILE *f = fopen(source+1, "r"); if (!f) { error_exit("Cannot open CD-ROM file description '%s'\n", source+1); } while (fgets(lineread, sizeof(lineread), f)) { /* We treat these characters as line endings */ trimmedline = strtok(lineread, "\t\r\n;"); eq = strchr(trimmedline, '='); if (!eq) { /* Treat this as a directory name */ targetname = trimmedline; normdir = strdup(targetname); normalize_dirname(normdir); dir_hash_create_dir(&specified_files, targetname, normdir); free(normdir); } else { targetname = strtok(lineread, "="); srcname = strtok(NULL, ""); #ifdef _WIN32 if (_access(srcname, R_OK) == 0) #else if (access(srcname, R_OK) == 0) #endif { if (!dir_hash_add_file(&specified_files, srcname, targetname)) error_exit("Target '%s' (file '%s') is invalid\n", targetname, srcname); } else error_exit("Cannot access file '%s' (target '%s')\n", srcname, targetname); } } fclose(f); /* scan all files and create directory structure in memory */ scan_specified_files(&root, &specified_files.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.file = NULL; 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, TRUE)); 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, TRUE)); printf("%s bytes ", edit_with_commas(bytes_in_directories, TRUE)); printf("in %s directories\n", edit_with_commas(number_of_directories, FALSE)); printf("%s other bytes\n", edit_with_commas(root.sector * SECTOR_SIZE, TRUE)); puts("-------------"); printf("%s total bytes\n", edit_with_commas(total_sectors * SECTOR_SIZE, TRUE)); puts("============="); } if (size_limit != 0 && total_sectors > size_limit) error_exit("Size limit exceeded"); if (!scan_files_only) { // re-initialize CD-ROM write buffer cd.file = fopen(cd.filespecs, "w+b"); if (cd.file == NULL) error_exit("Cannot 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 (fclose(cd.file) != 0) { cd.file = NULL; error_exit("File write error in image file '%s'", cd.filespecs); } if (verbosity >= NORMAL) puts("CD-ROM image made successfully"); } dir_hash_destroy(&specified_files); release_memory(); return 0; } /* EOF */