reactos/modules/rosapps/applications/devutils/cdmake/cdmake.c

2445 lines
77 KiB
C

/*
* 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 <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <io.h>
# include <dos.h>
# ifdef _MSC_VER
# define R_OK 4
# endif
#else
# if defined(__FreeBSD__) || defined(__APPLE__)
# include <sys/uio.h>
# else
# include <sys/io.h>
# endif // __FreeBSD__
# include <errno.h>
# include <sys/types.h>
# include <dirent.h>
# include <unistd.h>
# define TRUE 1
# define FALSE 0
#endif // _WIN32
#include <ctype.h>
#include <time.h>
#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#<defaultBootEntry>#<bootEntry1>#...#<bootEntryN>]\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#<defaultBootEntry>#<bootEntry2>#...#<bootEntryN>\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, &timestamp);
// 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 */