3 * by Philip J. Erdelsky
5 * http://alumnus.caltech.edu/~pje/
7 * http://alumnus.caltech.edu/~pje/cdmake.txt
9 * According to his website, this file was released into the public domain
10 * by Philip J. Erdelsky.
13 * COPYRIGHT: See COPYING in the top level directory
14 * PROJECT: ReactOS CD-ROM Maker
15 * FILE: tools/cdmake/cdmake.c
16 * PURPOSE: CD-ROM Premastering Utility
17 * PROGRAMMERS: Eric Kohl
21 * Hermes Belusca-Maito
29 * by Casper S. Hornstrup
30 * chorns@users.sourceforge.net
36 * - No Joliet file name validations
37 * - Very bad ISO file name generation
39 * Convert long filename to ISO9660 file name by Magnus Olsen
40 * magnus@greatlord.com
50 # define WIN32_LEAN_AND_MEAN
58 # if defined(__FreeBSD__) || defined(__APPLE__)
62 # endif // __FreeBSD__
64 # include <sys/types.h>
75 // FIXME! FIXME! Do it in a portable way!!
76 typedef unsigned char BYTE
;
77 typedef unsigned short WORD
;
78 typedef unsigned long DWORD
;
81 // file system parameters
84 #define MAX_NAME_LENGTH 64
85 #define MAX_CDNAME_LENGTH 8
86 #define MAX_EXTENSION_LENGTH 10
87 #define MAX_CDEXTENSION_LENGTH 3
88 #define SECTOR_SIZE 2048
89 #define BUFFER_SIZE (8 * SECTOR_SIZE)
92 #define DIRECTORY_FLAG 2
98 DWORD sector
; // sector to receive next byte
99 int offset
; // offset of next byte in sector
100 int count
; // number of bytes in buffer
105 typedef struct _boot_validation_header
109 } BOOT_VALIDATION_HEADER
, *PBOOT_VALIDATION_HEADER
;
111 typedef struct boot_entry
113 struct boot_entry
*next_entry
;
118 WORD sector_count
; // boot_image_size
119 DWORD load_rba
; // boot_image_sector
122 } BOOT_ENTRY
, *PBOOT_ENTRY
;
124 typedef struct boot_header
126 struct boot_header
*next_header
;
130 // char id_string[28];
131 PBOOT_ENTRY entry_list
;
132 } BOOT_HEADER
, *PBOOT_HEADER
;
134 typedef struct date_and_time
142 } DATE_AND_TIME
, *PDATE_AND_TIME
;
144 typedef struct directory_record
146 struct directory_record
*next_in_directory
;
147 struct directory_record
*next_in_path_table
; /* directory record only */
148 struct directory_record
*next_in_memory
;
149 struct directory_record
*first_record
; /* directory record only */
150 struct directory_record
*parent
;
152 char name
[MAX_NAME_LENGTH
+1];
153 char name_on_cd
[MAX_CDNAME_LENGTH
+1];
154 char extension
[MAX_EXTENSION_LENGTH
+1];
155 char extension_on_cd
[MAX_CDEXTENSION_LENGTH
+1];
157 const char *orig_name
;
158 DATE_AND_TIME date_and_time
;
163 unsigned level
; /* directory record only */
164 WORD path_table_index
; /* directory record only */
165 } DIR_RECORD
, *PDIR_RECORD
;
167 typedef enum directory_record_type
173 } DIR_RECORD_TYPE
, *PDIR_RECORD_TYPE
;
176 PDIR_RECORD
sort_linked_list(PDIR_RECORD
,
177 unsigned, int (*)(PDIR_RECORD
, PDIR_RECORD
));
180 static char DIRECTORY_TIMESTAMP
[] = "~Y$'KOR$.3K&";
182 static struct cd_image cd
;
185 char volume_label
[32];
189 enum {QUIET
, NORMAL
, VERBOSE
} verbosity
;
192 BOOL scan_files_only
= FALSE
;
193 // BOOL compute_crc = FALSE;
196 * ISO 9660 information
199 BOOL accept_punctuation_marks
;
202 DWORD path_table_size
;
203 DWORD little_endian_path_table_sector
;
204 DWORD big_endian_path_table_sector
;
210 DWORD number_of_files
;
211 DWORD bytes_in_files
;
212 DWORD unused_bytes_at_ends_of_files
;
213 DWORD number_of_directories
;
214 DWORD bytes_in_directories
;
216 struct target_dir_hash specified_files
;
220 * El-Torito boot information
222 BOOL eltorito
; // TRUE/FALSE: bootable/non-bootable CD-ROM
223 BOOL multi_boot
; // TRUE/FALSE: multi/single-boot CD-ROM
224 DWORD boot_catalog_sector
;
225 BOOT_VALIDATION_HEADER boot_validation_header
;
226 BOOT_ENTRY default_boot_entry
;
227 PBOOT_HEADER boot_header_list
;
233 DWORD joliet_path_table_size
;
234 DWORD joliet_little_endian_path_table_sector
;
235 DWORD joliet_big_endian_path_table_sector
;
240 BOOL make_bridged_udf
= TRUE
; // TRUE for "UDF-Bridge" (aka. UDF/ISO); FALSE for pure UDF.
243 /*-----------------------------------------------------------------------------
244 This function edits a 32-bit unsigned number into a comma-delimited form, such
245 as 4,294,967,295 for the largest possible number, and returns a pointer to a
246 static buffer containing the result. It suppresses leading zeros and commas,
247 but optionally pads the result with blanks at the left so the result is always
248 exactly 13 characters long (excluding the terminating zero).
250 CAUTION: A statement containing more than one call on this function, such as
251 printf("%s, %s", edit_with_commas(a), edit_with_commas(b)), will produce
252 incorrect results because all calls use the same static bufffer.
253 -----------------------------------------------------------------------------*/
255 static char *edit_with_commas(DWORD x
, BOOL pad
)
261 if (i
% 4 == 2) s
[--i
] = ',';
262 s
[--i
] = (char)(x
% 10 + '0');
263 } while ((x
/=10) != 0);
266 while (i
> 0) s
[--i
] = ' ';
271 /*-----------------------------------------------------------------------------
272 This function releases all allocated memory blocks.
273 -----------------------------------------------------------------------------*/
275 static void release_memory(void)
277 while (boot_header_list
)
279 PBOOT_HEADER next_header
= boot_header_list
->next_header
;
281 while (boot_header_list
->entry_list
)
283 PBOOT_ENTRY next_entry
= boot_header_list
->entry_list
->next_entry
;
284 free(boot_header_list
->entry_list
);
285 boot_header_list
->entry_list
= next_entry
;
288 free(boot_header_list
);
289 boot_header_list
= next_header
;
292 while (root
.next_in_memory
!= NULL
)
294 PDIR_RECORD next
= root
.next_in_memory
->next_in_memory
;
296 free(root
.next_in_memory
->joliet_name
);
297 free(root
.next_in_memory
);
298 root
.next_in_memory
= next
;
301 free(root
.joliet_name
);
303 if (cd
.buffer
!= NULL
)
310 /*-----------------------------------------------------------------------------
311 This function edits and displays an error message and then jumps back to the
312 error exit point in main().
313 -----------------------------------------------------------------------------*/
315 static void error_exit(const char* fmt
, ...)
329 /*-----------------------------------------------------------------------------
330 This function, which is called only on the second pass, and only when the
331 buffer is not empty, flushes the buffer to the CD-ROM image.
332 -----------------------------------------------------------------------------*/
334 static void flush_buffer(void)
336 if (fwrite(cd
.buffer
, cd
.count
, 1, cd
.file
) < 1)
337 error_exit("File write error");
342 edit_with_commas((total_sectors
- cd
.sector
) * SECTOR_SIZE
, TRUE
));
346 /*-----------------------------------------------------------------------------
347 This function writes a single byte to the CD-ROM image. On the first pass (in
348 which cd.handle < 0), it does not actually write anything but merely updates
349 the file pointer as though the byte had been written.
350 -----------------------------------------------------------------------------*/
352 static void write_byte(BYTE x
)
356 cd
.buffer
[cd
.count
] = x
;
357 if (++cd
.count
== BUFFER_SIZE
)
360 if (++cd
.offset
== SECTOR_SIZE
)
367 /*-----------------------------------------------------------------------------
368 These functions write a word or double word to the CD-ROM image with the
370 -----------------------------------------------------------------------------*/
372 static void write_little_endian_word(WORD x
)
375 write_byte((BYTE
)(x
>> 8));
378 static void write_big_endian_word(WORD x
)
380 write_byte((BYTE
)(x
>> 8));
384 static void write_both_endian_word(WORD x
)
386 write_little_endian_word(x
);
387 write_big_endian_word(x
);
390 static void write_little_endian_dword(DWORD x
)
393 write_byte((BYTE
)(x
>> 8));
394 write_byte((BYTE
)(x
>> 16));
395 write_byte((BYTE
)(x
>> 24));
398 static void write_big_endian_dword(DWORD x
)
400 write_byte((BYTE
)(x
>> 24));
401 write_byte((BYTE
)(x
>> 16));
402 write_byte((BYTE
)(x
>> 8));
406 static void write_both_endian_dword(DWORD x
)
408 write_little_endian_dword(x
);
409 write_big_endian_dword(x
);
412 /*-----------------------------------------------------------------------------
413 This function writes enough zeros to fill out the end of a sector, and leaves
414 the file pointer at the beginning of the next sector. If the file pointer is
415 already at the beginning of a sector, it writes nothing.
416 -----------------------------------------------------------------------------*/
418 static void fill_sector(void)
420 while (cd
.offset
!= 0)
424 /*-----------------------------------------------------------------------------
425 This function writes a string to the CD-ROM image. The terminating \0 is not
427 -----------------------------------------------------------------------------*/
429 static void write_string(char *s
)
435 static void write_bytecounted_string(unsigned bytecount
, char *s
, char padding
)
437 while (*s
!= 0 && bytecount
!= 0)
442 while (bytecount
!= 0)
449 /*-----------------------------------------------------------------------------
450 This function writes a ansi string as a big endian unicode string to the CD-ROM
451 image. The terminating \0 is not written.
452 -----------------------------------------------------------------------------*/
454 static void write_string_as_big_endian_unicode(char *s
)
458 write_big_endian_word(*s
++);
462 static void write_bytecounted_string_as_big_endian_unicode(unsigned bytecount
, char *s
, char padding
)
464 unsigned wordcount
= bytecount
/ 2;
466 while (*s
!= 0 && wordcount
!= 0)
468 write_big_endian_word(*s
++);
471 while (wordcount
!= 0)
473 write_big_endian_word(padding
);
477 if (bytecount
% 2 != 0)
481 /*-----------------------------------------------------------------------------
482 This function writes a block of identical bytes to the CD-ROM image.
483 -----------------------------------------------------------------------------*/
485 static void write_block(unsigned count
, BYTE value
)
494 /*-----------------------------------------------------------------------------
495 This function writes a block of identical big endian words to the CD-ROM image.
496 -----------------------------------------------------------------------------*/
498 static void write_word_block(unsigned count
, WORD value
)
502 write_big_endian_word(value
);
507 /*-----------------------------------------------------------------------------
508 This function writes a directory record to the CD_ROM image.
509 -----------------------------------------------------------------------------*/
512 write_directory_record(PDIR_RECORD d
,
513 DIR_RECORD_TYPE DirType
,
516 unsigned identifier_size
;
517 unsigned record_size
;
521 if (DirType
== DOT_RECORD
|| DirType
== DOT_DOT_RECORD
)
524 identifier_size
= strlen(d
->joliet_name
) * 2;
534 case SUBDIRECTORY_RECORD
:
535 /*printf("Subdir: %s\n", d->name_on_cd);*/
536 identifier_size
= strlen(d
->name_on_cd
);
539 /*printf("File: %s.%s -> %s.%s\n", d->name, d->extension, d->name_on_cd, d->extension_on_cd);*/
540 identifier_size
= strlen(d
->name_on_cd
) + 2;
541 if (d
->extension_on_cd
[0] != 0)
542 identifier_size
+= 1 + strlen(d
->extension_on_cd
);
549 record_size
= 33 + identifier_size
;
550 if ((identifier_size
& 1) == 0)
552 if (cd
.offset
+ record_size
> SECTOR_SIZE
)
554 write_byte((BYTE
)record_size
);
555 write_byte(0); // number of sectors in extended attribute record
558 write_both_endian_dword(d
->joliet_sector
);
559 write_both_endian_dword(d
->joliet_size
);
563 write_both_endian_dword(d
->sector
);
564 write_both_endian_dword(d
->size
);
566 write_byte((BYTE
)(d
->date_and_time
.year
- 1900));
567 write_byte(d
->date_and_time
.month
);
568 write_byte(d
->date_and_time
.day
);
569 write_byte(d
->date_and_time
.hour
);
570 write_byte(d
->date_and_time
.minute
);
571 write_byte(d
->date_and_time
.second
);
572 write_byte(0); // GMT offset
573 write_byte(d
->flags
);
574 write_byte(0); // file unit size for an interleaved file
575 write_byte(0); // interleave gap size for an interleaved file
576 write_both_endian_word(1); // volume sequence number
577 write_byte((BYTE
)identifier_size
);
586 case SUBDIRECTORY_RECORD
:
588 write_string_as_big_endian_unicode(d
->joliet_name
);
590 write_string(d
->name_on_cd
);
595 write_string_as_big_endian_unicode(d
->joliet_name
);
599 write_string(d
->name_on_cd
);
600 if (d
->extension_on_cd
[0] != 0)
603 write_string(d
->extension_on_cd
);
609 if ((identifier_size
& 1) == 0)
613 /*-----------------------------------------------------------------------------
614 This function converts the date and time words from an ffblk structure and
615 puts them into a date_and_time structure.
616 -----------------------------------------------------------------------------*/
618 static void convert_date_and_time(PDATE_AND_TIME dt
, time_t *time
)
621 timedef
= gmtime(time
);
623 dt
->second
= timedef
->tm_sec
;
624 dt
->minute
= timedef
->tm_min
;
625 dt
->hour
= timedef
->tm_hour
;
626 dt
->day
= timedef
->tm_mday
;
627 dt
->month
= timedef
->tm_mon
+ 1;
628 dt
->year
= timedef
->tm_year
+ 1900;
631 /*-----------------------------------------------------------------------------
632 This function checks the specified character, if necessary, and
633 generates an error if it is a punctuation mark other than an underscore.
634 It also converts small letters to capital letters and returns the
636 -----------------------------------------------------------------------------*/
638 static int check_for_punctuation(int c
, const char *name
)
640 c
= toupper(c
& 0xFF);
641 if (!accept_punctuation_marks
&& !isalnum(c
) && c
!= '_')
642 error_exit("Punctuation mark in %s", name
);
646 /*-----------------------------------------------------------------------------
647 This function checks to see if there's a cdname conflict.
648 -----------------------------------------------------------------------------*/
650 #if defined(_WIN32) && !defined(strcasecmp)
651 #define strcasecmp stricmp
654 static BOOL
cdname_exists(PDIR_RECORD d
)
656 PDIR_RECORD p
= d
->parent
->first_record
;
660 && !strcasecmp(p
->name_on_cd
, d
->name_on_cd
)
661 && !strcasecmp(p
->extension_on_cd
, d
->extension_on_cd
) )
663 p
= p
->next_in_directory
;
668 static void parse_filename_into_dirrecord(const char* filename
, PDIR_RECORD d
, BOOL dir
)
670 const char *s
= filename
;
671 char *t
= d
->name_on_cd
;
674 int filename_counter
;
675 filename_counter
= 1;
684 if ((size_t)(t
-d
->name_on_cd
) < sizeof(d
->name_on_cd
)-1)
685 *t
++ = check_for_punctuation(*s
, filename
);
687 error_exit("'%s' is not ISO-9660, aborting...", filename
);
689 if ((size_t)(n
-d
->name
) < sizeof(d
->name
)-1)
692 error_exit("'%s' is not ISO-9660, aborting...", filename
);
695 // Check for extension length
696 if (!joliet
&& strlen(s
) > MAX_EXTENSION_LENGTH
)
698 error_exit("'%s' has too long extension, aborting...", filename
);
701 strcpy(d
->extension
, s
);
702 t
= d
->extension_on_cd
;
705 if ((size_t)(t
-d
->extension_on_cd
) < sizeof(d
->extension_on_cd
)-1)
706 *t
++ = check_for_punctuation(*s
, filename
);
708 error_exit("'%s' is not ISO-9660, aborting...", filename
);
716 if (d
->extension
[0] != 0)
719 error_exit("Directory with extension '%s'", filename
);
721 d
->flags
= DIRECTORY_FLAG
;
727 filename_counter
= 1;
728 while (cdname_exists(d
))
730 // the file name must be at least 8 chars long
731 if (strlen(d
->name_on_cd
)<8)
732 error_exit("'%s' is a duplicate file name, aborting...", filename
);
734 if ((d
->name_on_cd
[8] == '.') && (strlen(d
->name_on_cd
) < 13))
735 error_exit("'%s' is a duplicate file name, aborting...", filename
);
737 // max 255 times for equal short filename
738 if (filename_counter
>255)
739 error_exit("'%s' is a duplicate file name, aborting...", filename
);
741 d
->name_on_cd
[8] = '~';
742 memset(&d
->name_on_cd
[9],0,5);
743 sprintf(&d
->name_on_cd
[9],"%d",filename_counter
);
749 joliet_length
= strlen(filename
);
750 if (joliet_length
> 64)
751 error_exit("'%s' is not Joliet, aborting...", filename
);
752 d
->joliet_name
= malloc(joliet_length
+ 1);
753 if (d
->joliet_name
== NULL
)
754 error_exit("Insufficient memory");
755 strcpy(d
->joliet_name
, filename
);
759 /*-----------------------------------------------------------------------------
760 This function creates a new directory record with the information from the
761 specified ffblk. It links it into the beginning of the directory list
762 for the specified parent and returns a pointer to the new record.
763 -----------------------------------------------------------------------------*/
769 new_directory_record(struct _finddata_t
*f
,
774 d
= calloc(1, sizeof(*d
));
776 error_exit("Insufficient memory");
777 d
->next_in_memory
= root
.next_in_memory
;
778 root
.next_in_memory
= d
;
780 /* I need the parent set before calling parse_filename_into_dirrecord(),
781 because that functions checks for duplicate file names*/
783 parse_filename_into_dirrecord(f
->name
, d
, f
->attrib
& _A_SUBDIR
);
785 convert_date_and_time(&d
->date_and_time
, &f
->time_write
);
786 d
->flags
|= f
->attrib
& _A_HIDDEN
? HIDDEN_FLAG
: 0;
787 d
->size
= d
->joliet_size
= f
->size
;
788 d
->next_in_directory
= parent
->first_record
;
789 parent
->first_record
= d
;
797 new_directory_record(struct dirent
*entry
,
803 d
= calloc(1, sizeof(*d
));
805 error_exit("Insufficient memory");
806 d
->next_in_memory
= root
.next_in_memory
;
807 root
.next_in_memory
= d
;
809 /* I need the parent set before calling parse_filename_into_dirrecord(),
810 because that functions checks for duplicate file names*/
813 parse_filename_into_dirrecord(entry
->d_name
, d
, entry
->d_type
== DT_DIR
);
815 parse_filename_into_dirrecord(entry
->d_name
, d
, S_ISDIR(stbuf
->st_mode
));
818 convert_date_and_time(&d
->date_and_time
, &stbuf
->st_mtime
);
819 d
->flags
|= entry
->d_name
[0] == '.' ? HIDDEN_FLAG
: 0;
820 d
->size
= d
->joliet_size
= stbuf
->st_size
;
821 d
->next_in_directory
= parent
->first_record
;
822 parent
->first_record
= d
;
828 /*-----------------------------------------------------------------------------
829 This function compares two directory records according to the ISO9660 rules
830 for directory sorting and returns a negative value if p is before q, or a
831 positive value if p is after q.
832 -----------------------------------------------------------------------------*/
834 static int compare_directory_order(PDIR_RECORD p
, PDIR_RECORD q
)
836 int n
= strcmp(p
->name_on_cd
, q
->name_on_cd
);
838 n
= strcmp(p
->extension_on_cd
, q
->extension_on_cd
);
842 /*-----------------------------------------------------------------------------
843 This function compares two directory records (which must represent
844 directories) according to the ISO9660 rules for path table sorting and returns
845 a negative value if p is before q, or a positive vlaue if p is after q.
846 -----------------------------------------------------------------------------*/
848 static int compare_path_table_order(PDIR_RECORD p
, PDIR_RECORD q
)
850 int n
= p
->level
- q
->level
;
855 n
= compare_path_table_order(p
->parent
, q
->parent
);
857 n
= compare_directory_order(p
, q
);
862 /*-----------------------------------------------------------------------------
863 This function appends the specified string to the buffer source[].
864 -----------------------------------------------------------------------------*/
866 static void append_string_to_source(char *s
)
869 *end_source
++ = *s
++;
872 /*-----------------------------------------------------------------------------
873 This function scans all files from the current source[] (which must end in \,
874 and represents a directory already in the database as d),
875 and puts the appropriate directory records into the database in memory, with
876 the specified root. It calls itself recursively to scan all subdirectories.
877 -----------------------------------------------------------------------------*/
882 make_directory_records(PDIR_RECORD d
)
885 struct _finddata_t f
;
886 char *old_end_source
;
889 d
->first_record
= NULL
;
890 strcpy(end_source
, "*.*");
892 findhandle
= _findfirst(source
, &f
);
897 if ((f
.attrib
& (_A_HIDDEN
| _A_SUBDIR
)) == 0 && f
.name
[0] != '.')
899 if (strcmp(f
.name
, DIRECTORY_TIMESTAMP
) == 0)
901 convert_date_and_time(&d
->date_and_time
, &f
.time_write
);
905 if (verbosity
== VERBOSE
)
907 old_end_source
= end_source
;
908 strcpy(end_source
, f
.name
);
909 printf("%d: file %s\n", d
->level
, source
);
910 end_source
= old_end_source
;
912 (void) new_directory_record(&f
, d
);
916 while (_findnext(findhandle
, &f
) == 0);
918 _findclose(findhandle
);
921 strcpy(end_source
, "*.*");
922 findhandle
= _findfirst(source
, &f
);
927 if (f
.attrib
& _A_SUBDIR
&& f
.name
[0] != '.')
929 old_end_source
= end_source
;
930 append_string_to_source(f
.name
);
931 *end_source
++ = DIR_SEPARATOR_CHAR
;
932 if (verbosity
== VERBOSE
)
935 printf("%d: directory %s\n", d
->level
+ 1, source
);
937 if (d
->level
< MAX_LEVEL
)
939 new_d
= new_directory_record(&f
, d
);
940 new_d
->next_in_path_table
= root
.next_in_path_table
;
941 root
.next_in_path_table
= new_d
;
942 new_d
->level
= d
->level
+ 1;
943 make_directory_records(new_d
);
947 error_exit("Directory is nested too deep");
949 end_source
= old_end_source
;
952 while (_findnext(findhandle
, &f
) == 0);
954 _findclose(findhandle
);
958 d
->first_record
= sort_linked_list(d
->first_record
, 0, compare_directory_order
);
965 make_directory_records(PDIR_RECORD d
)
969 struct dirent
*entry
;
970 char *old_end_source
;
974 d
->first_record
= NULL
;
977 dirp
= opendir(source
);
980 while ((entry
= readdir(dirp
)) != NULL
)
982 if (strcmp(entry
->d_name
, ".") == 0 || strcmp(entry
->d_name
, "..") == 0)
983 continue; // skip self and parent
985 if (entry
->d_type
== DT_REG
) // normal file
987 // Check for an absolute path
988 if (source
[0] == DIR_SEPARATOR_CHAR
)
991 strcat(buf
, DIR_SEPARATOR_STRING
);
992 strcat(buf
, entry
->d_name
);
996 if (!getcwd(buf
, sizeof(buf
)))
997 error_exit("Cannot get CWD: %s\n", strerror(errno
));
998 strcat(buf
, DIR_SEPARATOR_STRING
);
1000 strcat(buf
, entry
->d_name
);
1003 if (stat(buf
, &stbuf
) == -1)
1005 error_exit("Cannot access '%s' (%s)\n", buf
, strerror(errno
));
1009 if (strcmp(entry
->d_name
, DIRECTORY_TIMESTAMP
) == 0)
1011 convert_date_and_time(&d
->date_and_time
, &stbuf
.st_ctime
);
1015 if (verbosity
== VERBOSE
)
1017 printf("%d: file %s\n", d
->level
, buf
);
1019 (void) new_directory_record(entry
, &stbuf
, d
);
1027 error_exit("Cannot open '%s'\n", source
);
1031 dirp
= opendir(source
);
1034 while ((entry
= readdir(dirp
)) != NULL
)
1036 if (strcmp(entry
->d_name
, ".") == 0 || strcmp(entry
->d_name
, "..") == 0)
1037 continue; // skip self and parent
1039 if (entry
->d_type
== DT_DIR
) // directory
1041 old_end_source
= end_source
;
1042 append_string_to_source(entry
->d_name
);
1043 *end_source
++ = DIR_SEPARATOR_CHAR
;
1045 if (verbosity
== VERBOSE
)
1047 printf("%d: directory %s\n", d
->level
+ 1, source
);
1049 if (d
->level
< MAX_LEVEL
)
1051 // Check for an absolute path
1052 if (source
[0] == DIR_SEPARATOR_CHAR
)
1054 strcpy(buf
, source
);
1058 if (!getcwd(buf
, sizeof(buf
)))
1059 error_exit("Cannot get CWD: %s\n", strerror(errno
));
1060 strcat(buf
, DIR_SEPARATOR_STRING
);
1061 strcat(buf
, source
);
1064 if (stat(buf
, &stbuf
) == -1)
1066 error_exit("Cannot access '%s' (%s)\n", buf
, strerror(errno
));
1069 new_d
= new_directory_record(entry
, &stbuf
, d
);
1070 new_d
->next_in_path_table
= root
.next_in_path_table
;
1071 root
.next_in_path_table
= new_d
;
1072 new_d
->level
= d
->level
+ 1;
1073 make_directory_records(new_d
);
1077 error_exit("Directory is nested too deep");
1079 end_source
= old_end_source
;
1087 error_exit("Cannot open '%s'\n", source
);
1093 dirp
= opendir(source
);
1096 while ((entry
= readdir(dirp
)) != NULL
)
1098 if (strcmp(entry
->d_name
, ".") == 0 || strcmp(entry
->d_name
, "..") == 0)
1099 continue; // skip self and parent
1101 // Check for an absolute path
1102 if (source
[0] == DIR_SEPARATOR_CHAR
)
1104 strcpy(buf
, source
);
1105 strcat(buf
, DIR_SEPARATOR_STRING
);
1106 strcat(buf
, entry
->d_name
);
1110 if (!getcwd(buf
, sizeof(buf
)))
1111 error_exit("Cannot get CWD: %s\n", strerror(errno
));
1112 strcat(buf
, DIR_SEPARATOR_STRING
);
1113 strcat(buf
, source
);
1114 strcat(buf
, entry
->d_name
);
1117 if (stat(buf
, &stbuf
) == -1)
1119 error_exit("Cannot access '%s' (%s)\n", buf
, strerror(errno
));
1123 if (S_ISDIR(stbuf
.st_mode
))
1125 old_end_source
= end_source
;
1126 append_string_to_source(entry
->d_name
);
1127 *end_source
++ = DIR_SEPARATOR_CHAR
;
1129 if (verbosity
== VERBOSE
)
1131 printf("%d: directory %s\n", d
->level
+ 1, source
);
1134 if (d
->level
< MAX_LEVEL
)
1136 new_d
= new_directory_record(entry
, &stbuf
, d
);
1137 new_d
->next_in_path_table
= root
.next_in_path_table
;
1138 root
.next_in_path_table
= new_d
;
1139 new_d
->level
= d
->level
+ 1;
1140 make_directory_records(new_d
);
1144 error_exit("Directory is nested too deep");
1147 end_source
= old_end_source
;
1150 else if (S_ISREG(stbuf
.st_mode
))
1152 if (strcmp(entry
->d_name
, DIRECTORY_TIMESTAMP
) == 0)
1154 convert_date_and_time(&d
->date_and_time
, &stbuf
.st_ctime
);
1158 if (verbosity
== VERBOSE
)
1160 printf("%d: file %s\n", d
->level
, buf
);
1162 (void) new_directory_record(entry
, &stbuf
, d
);
1170 error_exit("Cannot open '%s'\n", source
);
1177 d
->first_record
= sort_linked_list(d
->first_record
, 0, compare_directory_order
);
1183 new_empty_dirrecord(PDIR_RECORD d
, BOOL directory
)
1186 new_d
= calloc(1, sizeof(*new_d
));
1188 new_d
->level
= d
->level
+ 1;
1189 new_d
->next_in_directory
= d
->first_record
;
1190 d
->first_record
= new_d
;
1191 new_d
->next_in_memory
= root
.next_in_memory
;
1192 root
.next_in_memory
= new_d
;
1193 new_d
->date_and_time
= d
->date_and_time
;
1196 new_d
->flags
|= DIRECTORY_FLAG
;
1197 new_d
->next_in_path_table
= root
.next_in_path_table
;
1198 root
.next_in_path_table
= new_d
;
1205 get_cd_file_time(HANDLE handle
, PDATE_AND_TIME cd_time_info
)
1208 SYSTEMTIME sys_time
;
1210 if (!GetFileTime(handle
, NULL
, NULL
, &file_time
))
1213 FileTimeToSystemTime(&file_time
, &sys_time
);
1214 memset(cd_time_info
, 0, sizeof(*cd_time_info
));
1216 cd_time_info
->year
= sys_time
.wYear
;
1217 cd_time_info
->month
= sys_time
.wMonth
;
1218 cd_time_info
->day
= sys_time
.wDay
;
1219 cd_time_info
->hour
= sys_time
.wHour
;
1220 cd_time_info
->minute
= sys_time
.wMinute
;
1221 cd_time_info
->second
= sys_time
.wSecond
;
1228 scan_specified_files(PDIR_RECORD d
, struct target_dir_entry
*dir
)
1233 LARGE_INTEGER file_size
;
1237 struct target_file
*file
;
1238 struct target_dir_entry
*child
;
1240 d
->first_record
= NULL
;
1242 for (file
= dir
->head
; file
; file
= file
->next
)
1244 if (strcmp(file
->target_name
, DIRECTORY_TIMESTAMP
) == 0)
1247 if ((open_file
= CreateFileA(file
->source_name
,
1252 FILE_ATTRIBUTE_NORMAL
,
1253 NULL
)) == INVALID_HANDLE_VALUE
)
1255 error_exit("Cannot open timestamp file '%s'\n", file
->source_name
);
1258 if (!get_cd_file_time(open_file
, &d
->date_and_time
))
1260 error_exit("Cannot stat timestamp file '%s'\n", file
->source_name
);
1262 CloseHandle(open_file
);
1264 if (stat(file
->target_name
, &stbuf
) == -1)
1266 error_exit("Cannot stat timestamp file '%s'\n", file
->source_name
);
1268 convert_date_and_time(&d
->date_and_time
, &stbuf
.st_ctime
);
1273 if (verbosity
== VERBOSE
)
1275 printf("%d: file %s (from %s)\n",
1280 new_d
= new_empty_dirrecord(d
, FALSE
);
1281 parse_filename_into_dirrecord(file
->target_name
, new_d
, FALSE
);
1283 if ((open_file
= CreateFileA(file
->source_name
,
1288 FILE_ATTRIBUTE_NORMAL
,
1289 NULL
)) == INVALID_HANDLE_VALUE
)
1291 error_exit("Cannot open file '%s'\n", file
->source_name
);
1293 if (!get_cd_file_time(open_file
, &new_d
->date_and_time
))
1295 error_exit("Cannot stat file '%s'\n", file
->source_name
);
1297 if (!GetFileSizeEx(open_file
, &file_size
))
1299 error_exit("Cannot get file size of '%s'\n", file
->source_name
);
1301 new_d
->size
= new_d
->joliet_size
= file_size
.QuadPart
;
1302 new_d
->orig_name
= file
->source_name
;
1303 CloseHandle(open_file
);
1305 if (stat(file
->source_name
, &stbuf
) == -1)
1307 error_exit("Cannot find '%s' (target '%s')\n",
1311 convert_date_and_time(&new_d
->date_and_time
, &stbuf
.st_mtime
);
1312 new_d
->size
= new_d
->joliet_size
= stbuf
.st_size
;
1313 new_d
->orig_name
= file
->source_name
;
1318 for (child
= dir
->child
; child
; child
= child
->next
)
1320 if (verbosity
== VERBOSE
)
1322 printf("%d: directory %s\n", d
->level
, child
->case_name
);
1324 new_d
= new_empty_dirrecord(d
, TRUE
);
1325 parse_filename_into_dirrecord(child
->case_name
, new_d
, TRUE
);
1326 scan_specified_files(new_d
, child
);
1329 /* sort directory */
1330 d
->first_record
= sort_linked_list(d
->first_record
,
1332 compare_directory_order
);
1334 end_source
= source
;
1337 /*-----------------------------------------------------------------------------
1338 This function loads the file specifications for the file or directory
1339 corresponding to the specified directory record into the source[] buffer. It
1341 -----------------------------------------------------------------------------*/
1343 static void get_file_specifications(PDIR_RECORD d
)
1347 get_file_specifications(d
->parent
);
1348 if (d
->joliet_name
== NULL
)
1349 append_string_to_source(d
->name
);
1351 append_string_to_source(d
->joliet_name
);
1353 if (((d
->flags
& DIRECTORY_FLAG
) == 0 || joliet
) && d
->extension
[0] != 0)
1355 if (d
->joliet_name
== NULL
)
1357 *end_source
++ = '.';
1358 append_string_to_source(d
->extension
);
1361 if (d
->flags
& DIRECTORY_FLAG
)
1362 *end_source
++ = DIR_SEPARATOR_CHAR
;
1366 static void get_time_string(char *str
)
1368 sprintf(str
, "%04d%02d%02d%02d%02d%02d00",
1369 root
.date_and_time
.year
,
1370 root
.date_and_time
.month
,
1371 root
.date_and_time
.day
,
1372 root
.date_and_time
.hour
,
1373 root
.date_and_time
.minute
,
1374 root
.date_and_time
.second
);
1377 static BOOL
write_from_file(FILE *file
, DWORD size
)
1379 if (cd
.file
!= NULL
)
1383 fseek(file
, 0, SEEK_SET
);
1386 n
= BUFFER_SIZE
- cd
.count
;
1387 if ((DWORD
)n
> size
)
1390 if (fread(cd
.buffer
+ cd
.count
, n
, 1, file
) < 1)
1394 if (cd
.count
== BUFFER_SIZE
)
1396 cd
.sector
+= n
/ SECTOR_SIZE
;
1397 cd
.offset
+= n
% SECTOR_SIZE
;
1403 cd
.sector
+= size
/ SECTOR_SIZE
;
1404 cd
.offset
+= size
% SECTOR_SIZE
;
1410 static void pass(void)
1414 unsigned int name_length
;
1416 DWORD number_of_sectors
;
1417 char *old_end_source
;
1420 PBOOT_HEADER header
;
1423 char timestring
[17];
1425 get_time_string(timestring
);
1427 // first 16 sectors are zeros
1428 write_block(16 * SECTOR_SIZE
, 0);
1431 // Primary Volume Descriptor
1432 if (make_bridged_udf
)
1434 write_string("\1CD001\1");
1436 write_bytecounted_string(32, "", ' '); // system identifier
1437 write_bytecounted_string(32, volume_label
, ' '); // volume label
1440 write_both_endian_dword(total_sectors
);
1442 write_both_endian_word(1); // volume set size
1443 write_both_endian_word(1); // volume sequence number
1444 write_both_endian_word(2048); // sector size
1445 write_both_endian_dword(path_table_size
);
1446 write_little_endian_dword(little_endian_path_table_sector
);
1447 write_little_endian_dword(0); // second little endian path table
1448 write_big_endian_dword(big_endian_path_table_sector
);
1449 write_big_endian_dword(0); // second big endian path table
1450 write_directory_record(&root
, DOT_RECORD
, FALSE
);
1452 write_bytecounted_string(128, volume_label
, ' '); // volume set identifier
1453 write_bytecounted_string(128, PUBLISHER_ID
, ' '); // publisher identifier
1454 write_bytecounted_string(128, DATA_PREP_ID
, ' '); // data preparer identifier
1455 write_bytecounted_string(128, APP_ID
, ' '); // application identifier
1457 write_bytecounted_string(37, "", ' '); // copyright file identifier
1458 write_bytecounted_string(37, "", ' '); // abstract file identifier
1459 write_bytecounted_string(37, "", ' '); // bibliographic file identifier
1461 write_string(timestring
); // volume creation
1463 write_string(timestring
); // most recent modification
1465 write_string("0000000000000000"); // volume expires
1467 write_string("0000000000000000"); // volume is effective
1474 // Boot Volume Descriptor
1477 write_byte(0); // Boot record ID
1478 write_string("CD001\1");
1479 write_bytecounted_string(32, "EL TORITO SPECIFICATION", 0); // El-Torito identifier
1480 write_block(32, 0); // unused
1481 write_little_endian_dword(boot_catalog_sector
); // pointer to boot catalog
1485 // Supplementary Volume Descriptor
1488 write_string("\2CD001\1");
1490 write_bytecounted_string_as_big_endian_unicode(32, "", ' '); // system identifier
1491 write_bytecounted_string_as_big_endian_unicode(32, volume_label
, ' '); // volume label
1494 write_both_endian_dword(total_sectors
);
1495 write_string("%/E");
1497 write_both_endian_word(1); // volume set size
1498 write_both_endian_word(1); // volume sequence number
1499 write_both_endian_word(2048); // sector size
1500 write_both_endian_dword(joliet_path_table_size
);
1501 write_little_endian_dword(joliet_little_endian_path_table_sector
);
1502 write_little_endian_dword(0); // second little endian path table
1503 write_big_endian_dword(joliet_big_endian_path_table_sector
);
1504 write_big_endian_dword(0); // second big endian path table
1505 write_directory_record(&root
, DOT_RECORD
, TRUE
);
1507 write_bytecounted_string_as_big_endian_unicode(128, volume_label
, ' '); // volume set identifier
1508 write_bytecounted_string_as_big_endian_unicode(128, PUBLISHER_ID
, ' '); // publisher identifier
1509 write_bytecounted_string_as_big_endian_unicode(128, DATA_PREP_ID
, ' '); // data preparer identifier
1510 write_bytecounted_string_as_big_endian_unicode(128, APP_ID
, ' '); // application identifier
1512 write_bytecounted_string_as_big_endian_unicode(37, "", ' '); // copyright file identifier
1513 write_bytecounted_string_as_big_endian_unicode(37, "", ' '); // abstract file identifier
1514 write_bytecounted_string_as_big_endian_unicode(37, "", ' '); // bibliographic file identifier
1516 write_string(timestring
); // volume creation
1518 write_string(timestring
); // most recent modification
1520 write_string("0000000000000000"); // volume expires
1522 write_string("0000000000000000"); // volume is effective
1529 // Volume Descriptor Set Terminator
1530 if (make_bridged_udf
)
1532 write_string("\377CD001\1");
1536 // TODO: Add UDF support!
1541 boot_catalog_sector
= cd
.sector
;
1543 // Validation entry header
1544 write_byte(boot_validation_header
.header_id
);
1545 write_byte(boot_validation_header
.platform_id
);
1546 write_little_endian_word(0); // reserved
1547 write_bytecounted_string(24, MANUFACTURER_ID
, 0); // Manufacturer identifier
1548 write_little_endian_word(0x62E); // checksum // FIXME: This is hardcoded!!
1549 write_little_endian_word(0xAA55); // signature
1552 write_byte(default_boot_entry
.boot_id
);
1553 write_byte(default_boot_entry
.boot_emu_type
);
1554 write_little_endian_word(default_boot_entry
.load_segment
);
1555 write_byte(0); // partition type
1556 write_byte(0); // unused
1557 write_little_endian_word(default_boot_entry
.sector_count
);
1558 write_little_endian_dword(default_boot_entry
.load_rba
);
1559 write_block(20, 0); // unused
1561 // Loop through each boot header
1562 header
= boot_header_list
;
1565 write_byte(header
->header_id
);
1566 write_byte(header
->platform_id
);
1567 write_little_endian_word(header
->num_entries
);
1568 write_block(28, 0); // Identifier string (unused)
1570 // Loop through each boot entry
1571 entry
= header
->entry_list
;
1574 write_byte(entry
->boot_id
);
1575 write_byte(entry
->boot_emu_type
);
1576 write_little_endian_word(entry
->load_segment
);
1577 write_byte(0); // partition type
1578 write_byte(0); // unused
1579 write_little_endian_word(entry
->sector_count
);
1580 write_little_endian_dword(entry
->load_rba
);
1581 write_block(20, 0); // Selection criteria (unused)
1583 entry
= entry
->next_entry
;
1586 header
= header
->next_header
;
1596 default_boot_entry
.load_rba
= cd
.sector
;
1598 file
= fopen(default_boot_entry
.bootimage
, "rb");
1600 error_exit("Cannot open '%s'\n", default_boot_entry
.bootimage
);
1601 fseek(file
, 0, SEEK_END
);
1603 if (size
== 0 || (size
% 2048))
1606 error_exit("Invalid boot image size (%lu bytes)\n", size
);
1608 // Sector count in 512 byte sectors and rounded up
1609 default_boot_entry
.sector_count
= (size
+ 511) / 512;
1610 if (!write_from_file(file
, size
))
1613 error_exit("Read error in file '%s'\n", default_boot_entry
.bootimage
);
1617 // Loop through each boot header
1618 header
= boot_header_list
;
1621 // Loop through each boot entry
1622 entry
= header
->entry_list
;
1625 entry
->load_rba
= cd
.sector
;
1627 file
= fopen(entry
->bootimage
, "rb");
1629 error_exit("Cannot open '%s'\n", entry
->bootimage
);
1630 fseek(file
, 0, SEEK_END
);
1632 if (size
== 0 || (size
% 2048))
1635 error_exit("Invalid boot image size (%lu bytes)\n", size
);
1637 // Sector count in 512 byte sectors and rounded up
1638 entry
->sector_count
= (size
+ 511) / 512;
1639 if (!write_from_file(file
, size
))
1642 error_exit("Read error in file '%s'\n", entry
->bootimage
);
1646 entry
= entry
->next_entry
;
1649 header
= header
->next_header
;
1656 // Little Endian Path Table
1658 little_endian_path_table_sector
= cd
.sector
;
1660 write_byte(0); // number of sectors in extended attribute record
1661 write_little_endian_dword(root
.sector
);
1662 write_little_endian_word(1);
1667 root
.path_table_index
= 1;
1668 for (d
= root
.next_in_path_table
; d
!= NULL
; d
= d
->next_in_path_table
)
1670 name_length
= strlen(d
->name_on_cd
);
1671 write_byte((BYTE
)name_length
);
1672 write_byte(0); // number of sectors in extended attribute record
1673 write_little_endian_dword(d
->sector
);
1674 write_little_endian_word(d
->parent
->path_table_index
);
1675 write_string(d
->name_on_cd
);
1676 if (name_length
& 1)
1678 d
->path_table_index
= ++index
;
1681 path_table_size
= (cd
.sector
- little_endian_path_table_sector
) *
1682 SECTOR_SIZE
+ cd
.offset
;
1686 // Big Endian Path Table
1688 big_endian_path_table_sector
= cd
.sector
;
1690 write_byte(0); // number of sectors in extended attribute record
1691 write_big_endian_dword(root
.sector
);
1692 write_big_endian_word(1);
1696 for (d
= root
.next_in_path_table
; d
!= NULL
; d
= d
->next_in_path_table
)
1698 name_length
= strlen(d
->name_on_cd
);
1699 write_byte((BYTE
)name_length
);
1700 write_byte(0); // number of sectors in extended attribute record
1701 write_big_endian_dword(d
->sector
);
1702 write_big_endian_word(d
->parent
->path_table_index
);
1703 write_string(d
->name_on_cd
);
1704 if (name_length
& 1)
1711 // Little Endian Path Table
1713 joliet_little_endian_path_table_sector
= cd
.sector
;
1715 write_byte(0); // number of sectors in extended attribute record
1716 write_little_endian_dword(root
.joliet_sector
);
1717 write_little_endian_word(1);
1721 for (d
= root
.next_in_path_table
; d
!= NULL
; d
= d
->next_in_path_table
)
1723 name_length
= strlen(d
->joliet_name
) * 2;
1724 write_byte((BYTE
)name_length
);
1725 write_byte(0); // number of sectors in extended attribute record
1726 write_little_endian_dword(d
->joliet_sector
);
1727 write_little_endian_word(d
->parent
->path_table_index
);
1728 write_string_as_big_endian_unicode(d
->joliet_name
);
1731 joliet_path_table_size
= (cd
.sector
- joliet_little_endian_path_table_sector
) *
1732 SECTOR_SIZE
+ cd
.offset
;
1735 // Big Endian Path Table
1737 joliet_big_endian_path_table_sector
= cd
.sector
;
1739 write_byte(0); // number of sectors in extended attribute record
1740 write_big_endian_dword(root
.joliet_sector
);
1741 write_big_endian_word(1);
1745 for (d
= root
.next_in_path_table
; d
!= NULL
; d
= d
->next_in_path_table
)
1747 name_length
= strlen(d
->joliet_name
) * 2;
1748 write_byte((BYTE
)name_length
);
1749 write_byte(0); // number of sectors in extended attribute record
1750 write_big_endian_dword(d
->joliet_sector
);
1751 write_big_endian_word(d
->parent
->path_table_index
);
1752 write_string_as_big_endian_unicode(d
->joliet_name
);
1757 // TODO: Add UDF support!
1763 // TODO: Add CRC block for header!
1765 // Directories and files
1766 for (d
= &root
; d
!= NULL
; d
= d
->next_in_path_table
)
1769 d
->sector
= cd
.sector
;
1770 write_directory_record(d
, DOT_RECORD
, FALSE
);
1771 write_directory_record(d
== &root
? d
: d
->parent
, DOT_DOT_RECORD
, FALSE
);
1772 for (q
= d
->first_record
; q
!= NULL
; q
= q
->next_in_directory
)
1774 write_directory_record(q
,
1775 q
->flags
& DIRECTORY_FLAG
? SUBDIRECTORY_RECORD
: FILE_RECORD
,
1779 d
->size
= (cd
.sector
- d
->sector
) * SECTOR_SIZE
;
1781 // write directory for joliet
1784 d
->joliet_sector
= cd
.sector
;
1785 write_directory_record(d
, DOT_RECORD
, TRUE
);
1786 write_directory_record(d
== &root
? d
: d
->parent
, DOT_DOT_RECORD
, TRUE
);
1787 for (q
= d
->first_record
; q
!= NULL
; q
= q
->next_in_directory
)
1789 write_directory_record(q
,
1790 q
->flags
& DIRECTORY_FLAG
? SUBDIRECTORY_RECORD
: FILE_RECORD
,
1794 d
->joliet_size
= (cd
.sector
- d
->joliet_sector
) * SECTOR_SIZE
;
1795 bytes_in_directories
+= d
->joliet_size
;
1798 number_of_directories
++;
1799 bytes_in_directories
+= d
->size
;
1802 for (q
= d
->first_record
; q
!= NULL
; q
= q
->next_in_directory
)
1804 if ((q
->flags
& DIRECTORY_FLAG
) == 0)
1806 q
->sector
= q
->joliet_sector
= cd
.sector
;
1808 if (cd
.file
== NULL
)
1810 number_of_sectors
= (size
+ SECTOR_SIZE
- 1) / SECTOR_SIZE
;
1811 cd
.sector
+= number_of_sectors
;
1813 bytes_in_files
+= size
;
1814 unused_bytes_at_ends_of_files
+=
1815 number_of_sectors
* SECTOR_SIZE
- size
;
1819 const char *file_source
;
1820 old_end_source
= end_source
;
1823 get_file_specifications(q
);
1825 file_source
= source
;
1829 file_source
= q
->orig_name
;
1831 if (verbosity
== VERBOSE
)
1832 printf("Writing contents of %s\n", file_source
);
1833 file
= fopen(file_source
, "rb");
1835 error_exit("Cannot open '%s'\n", file_source
);
1836 if (!write_from_file(file
, size
))
1839 error_exit("Read error in file '%s'\n", file_source
);
1842 end_source
= old_end_source
;
1849 // TODO: Add final CRC block!
1851 total_sectors
= (DWORD
)cd
.sector
;
1854 static char HELP
[] =
1856 "CDMAKE CD-ROM Premastering Utility\n"
1857 "Copyright (C) 1997 Philip J. Erdelsky\n"
1858 "Copyright (C) 2003-2016 ReactOS Team\n"
1860 "CDMAKE [-vN] [-p] [-s N] [-m] [-j] [-q] "
1862 "[-pN] [-eN] [-b bootimage]\n"
1863 " [-bootdata:N#<defaultBootEntry>#<bootEntry1>#...#<bootEntryN>]\n"
1864 " source volume image\n"
1866 "Mandatory options:\n"
1867 " source Specifications of base directory containing all files to be\n"
1868 " written to CD-ROM image. Can be a directory, or a path to a\n"
1869 " file list (in which case the path must start with a '@').\n"
1870 " volume Volume label.\n"
1871 " image Image file or device.\n"
1873 "General options:\n"
1874 " -vN Verbosity level. Valid values for 'N' are:\n"
1875 " 0: Quiet mode - display nothing but error messages.\n"
1876 " 1: Normal mode (default).\n"
1877 " 2: Verbose mode - display file information as files are\n"
1878 " scanned and written. Overrides the -p option.\n"
1879 " -p Show progress while writing.\n"
1880 " -s N Abort operation before beginning write if image will be larger\n"
1881 " than N megabytes (i.e. 1024*1024*N bytes).\n"
1882 " -q Only scan the source files; do not create an image.\n"
1883 // " -x Compute and encode the AutoCRC value in the image.\n"
1885 "ISO 9660 and Joliet options:\n"
1886 " -m Accept punctuation marks other than underscores in names and\n"
1888 " -j Generate Joliet filename records.\n"
1891 // " Not implemented yet!\n"
1893 "El-Torito boot options:\n"
1894 " -pN Boot platform ID in hex format (default: 00 for a BIOS system).\n"
1895 " -eN Boot media emulation. Valid values for 'N' are:\n"
1896 " 0 (or nothing): No emulation.\n"
1897 " 1: 1.2 MB diskette.\n"
1898 " 2: 1.44MB diskette.\n"
1899 " 3: 2.88MB diskette.\n"
1901 " -b bootimage Create a single-boot El-Torito image.\n"
1902 " -bootdata: Create a multi-boot El-Torito image. This option cannot be\n"
1903 " combined with the -b option.\n"
1905 " -bootdata:N#<defaultBootEntry>#<bootEntry2>#...#<bootEntryN>\n"
1906 " 'N': number of boot entries following.\n"
1907 " defaultBootEntry: The default boot entry, needed in all cases.\n"
1908 " Used by BIOSes which do not support additional boot entries.\n"
1909 " bootEntryX: Additional boot entries.\n"
1910 " - Do not use spaces.\n"
1911 " - Each multi-boot entry must be delimited with a hash symbol (#).\n"
1912 " - Each option for a boot entry must be delimited with a comma (,).\n"
1913 " - Each boot entry must specify the platform ID.\n";
1915 /*-----------------------------------------------------------------------------
1916 Program execution starts here.
1917 -----------------------------------------------------------------------------*/
1919 #if (defined(__GNUC__) || (_MSC_VER < 1900))
1920 char* strtok_s(char *str
, const char *delim
, char **ctx
)
1922 if (delim
== NULL
|| ctx
== NULL
|| (str
== NULL
&& *ctx
== NULL
))
1930 while (*str
&& strchr(delim
, *str
))
1936 while (**ctx
&& !strchr(delim
, **ctx
))
1946 init_boot_entry(PBOOT_ENTRY boot_entry
,
1947 BYTE boot_emu_type
, WORD load_segment
,
1950 boot_entry
->boot_id
= 0x88; // Bootable entry
1951 boot_entry
->boot_emu_type
= boot_emu_type
; // 0: No emulation, etc...
1952 boot_entry
->load_segment
= load_segment
; // If 0 then use default 0x07C0
1954 boot_entry
->bootimage
[0] = '\0';
1955 strncpy(boot_entry
->bootimage
, bootimage
, sizeof(boot_entry
->bootimage
));
1956 boot_entry
->bootimage
[sizeof(boot_entry
->bootimage
)-1] = '\0';
1959 int main(int argc
, char **argv
)
1961 time_t timestamp
= time(NULL
);
1971 // Initialize CD-ROM write buffer
1974 cd
.filespecs
[0] = 0;
1976 cd
.buffer
= malloc(BUFFER_SIZE
);
1977 if (cd
.buffer
== NULL
)
1978 error_exit("Insufficient memory");
1980 // Initialize root directory
1982 memset(&root
, 0, sizeof(root
));
1984 root
.flags
= DIRECTORY_FLAG
;
1985 convert_date_and_time(&root
.date_and_time
, ×tamp
);
1987 // Initialize parameters
1989 scan_files_only
= FALSE
;
1990 make_bridged_udf
= TRUE
;
1991 // compute_crc = FALSE;
1994 show_progress
= FALSE
;
1996 accept_punctuation_marks
= FALSE
;
1998 volume_label
[0] = 0;
2000 // Initialize boot information
2003 boot_validation_header
.header_id
= 1; // Validation header ID
2004 boot_validation_header
.platform_id
= 0; // x86/64 BIOS system
2005 init_boot_entry(&default_boot_entry
,
2007 0, // Use default 0x07C0
2009 default_boot_entry
.sector_count
= 0;
2010 default_boot_entry
.load_rba
= 0;
2011 boot_header_list
= NULL
;
2013 // Scan command line arguments
2015 for (i
= 1; i
< argc
; i
++)
2017 if (strncmp(argv
[i
], "-v", 2) == 0)
2020 if (*t
== 0) // Normal verbosity level.
2022 else // Verbosity level in decimal
2023 verbosity
= strtoul(t
, NULL
, 10);
2025 // Check for validity
2026 if (verbosity
> VERBOSE
)
2029 // Disable by default, unless we are in normal verbosity level.
2030 // If progress is still wanted, use '-p'.
2031 if (verbosity
== QUIET
|| verbosity
== VERBOSE
)
2032 show_progress
= FALSE
;
2034 else if (strcmp(argv
[i
], "-p") == 0)
2035 show_progress
= TRUE
;
2036 else if (strncmp(argv
[i
], "-s", 2) == 0)
2044 error_exit("Missing size limit parameter");
2046 // size_limit = strtoul(t, NULL, 10);
2048 size_limit
= size_limit
* 10 + *t
++ - '0';
2049 if (size_limit
< 1 || size_limit
> 800)
2050 error_exit("Invalid size limit");
2051 size_limit
<<= 9; // convert megabyte to sector count
2053 else if (strcmp(argv
[i
], "-m") == 0)
2054 accept_punctuation_marks
= TRUE
;
2055 else if (strcmp(argv
[i
], "-j") == 0)
2057 else if (strncmp(argv
[i
], "-e", 2) == 0)
2059 // Check whether the multi-boot option '-bootdata:' was already set.
2060 // If so, print an error and bail out.
2061 if (eltorito
&& multi_boot
)
2062 error_exit("Single-boot and multi-boot entries cannot be combined");
2068 if (*t
== 0) // No emulation
2069 default_boot_entry
.boot_emu_type
= 0;
2070 else // ID in decimal
2071 default_boot_entry
.boot_emu_type
= (BYTE
)strtoul(t
, NULL
, 10);
2073 else if (strncmp(argv
[i
], "-p", 2) == 0)
2075 // Check whether the multi-boot option '-bootdata:' was already set.
2076 // If so, print an error and bail out.
2077 if (eltorito
&& multi_boot
)
2078 error_exit("Single-boot and multi-boot entries cannot be combined");
2083 // Platform ID in hexadecimal
2084 boot_validation_header
.platform_id
= (BYTE
)strtoul(argv
[i
] + 2, NULL
, 16);
2086 else if (strcmp(argv
[i
], "-b") == 0)
2088 // Check whether the multi-boot option '-bootdata:' was already set.
2089 // If so, print an error and bail out.
2090 if (eltorito
&& multi_boot
)
2091 error_exit("Single-boot and multi-boot entries cannot be combined");
2096 strncpy(default_boot_entry
.bootimage
, argv
[++i
], sizeof(default_boot_entry
.bootimage
));
2097 default_boot_entry
.bootimage
[sizeof(default_boot_entry
.bootimage
)-1] = '\0';
2099 else if (strncmp(argv
[i
], "-bootdata:", sizeof("-bootdata:") - 1) == 0)
2101 char *bootdata
, *entry_ctx
, *option_ctx
;
2102 DWORD num_boot_entries
= 0;
2104 BOOL default_entry
= TRUE
; // Start by setting the default boot entry
2105 PBOOT_HEADER boot_header
= NULL
; // Current boot header
2106 PBOOT_ENTRY boot_entry
= NULL
; // The last boot entry in the current boot header
2107 BYTE platform_id
, old_platform_id
= 0;
2110 char bootimage
[512];
2112 // Check whether the single-boot option '-b' was already set.
2113 // If so, print an error and bail out.
2114 if (eltorito
&& !multi_boot
)
2115 error_exit("Single-boot and multi-boot entries cannot be combined");
2117 t
= argv
[i
] + (sizeof("-bootdata:") - 1);
2118 bootdata
= strdup(t
);
2119 if (bootdata
== NULL
)
2120 error_exit("Insufficient memory");
2125 // FIXME: Paths containing '#' or ',' or ' ' are not yet supported!!
2128 t
= strtok_s(bootdata
, "#", &entry_ctx
);
2132 error_exit("Malformed bootdata command");
2135 num_boot_entries
= strtoul(t
, NULL
, 10);
2137 while (num_boot_entries
--)
2139 // Reset to default values
2140 platform_id
= 0; // x86/64 BIOS system
2141 boot_emu_type
= 0; // No emulation
2142 load_segment
= 0; // Use default 0x07C0
2143 bootimage
[0] = '\0';
2145 t
= strtok_s(NULL
, "#", &entry_ctx
);
2149 error_exit("Malformed bootdata command");
2152 t
= strtok_s(t
, ",", &option_ctx
);
2157 case 'b': // Boot sector file
2161 // Searches for any of the valid separators:
2162 // '#' starts a new boot entry;
2163 // ',' starts a new boot option;
2164 // ' ' finishes the bootdata command.
2165 q
= strpbrk(t
, "#, ");
2166 if (!q
) q
= t
+ strlen(t
);
2167 strncpy(bootimage
, t
, q
- t
+ 1);
2171 case 'p': // Platform ID
2173 // Platform ID in hexadecimal
2174 platform_id
= (BYTE
)strtoul(t
, NULL
, 16);
2178 case 'e': // No floppy-disk emulation
2180 if (*t
== 0) // No emulation
2182 else // ID in decimal
2183 boot_emu_type
= (BYTE
)strtoul(t
, NULL
, 10);
2188 case 't': // Loading segment
2190 if (*t
== 0) // Not specified: use default 0x07C0
2192 else // Segment in hexadecimal
2193 load_segment
= (BYTE
)strtoul(t
, NULL
, 16);
2200 error_exit("Malformed bootdata command");
2203 t
= strtok_s(NULL
, ",", &option_ctx
);
2206 // Create a new entry and possibly a boot header
2209 // Initialize the default boot entry and header
2211 boot_validation_header
.header_id
= 1; // Validation header ID
2212 boot_validation_header
.platform_id
= platform_id
;
2214 init_boot_entry(&default_boot_entry
, boot_emu_type
, load_segment
, bootimage
);
2216 // Default entry is now initialized.
2217 default_entry
= FALSE
;
2221 // Initialize a new boot entry
2222 PBOOT_ENTRY old_boot_entry
= boot_entry
;
2224 boot_entry
= calloc(1, sizeof(*boot_entry
));
2225 if (boot_entry
== NULL
)
2226 error_exit("Insufficient memory");
2227 // boot_entry->next_entry = NULL;
2229 init_boot_entry(boot_entry
, boot_emu_type
, load_segment
, bootimage
);
2231 // Create a new boot header if we don't have one yet
2232 if (boot_header
== NULL
)
2234 boot_header
= calloc(1, sizeof(*boot_header
));
2235 if (boot_header
== NULL
)
2236 error_exit("Insufficient memory");
2238 boot_header
->header_id
= 0x91; // So far this is the last boot header
2239 boot_header
->platform_id
= platform_id
;
2240 // boot_header->next_header = NULL;
2241 // boot_header->num_entries = 0;
2242 // boot_header->entry_list = NULL;
2244 old_boot_entry
= NULL
;
2245 old_platform_id
= platform_id
;
2247 boot_header_list
= boot_header
;
2251 // Create a new boot header if we change the platform ID
2252 if (old_platform_id
!= platform_id
)
2254 PBOOT_HEADER prev_boot_header
= boot_header
;
2256 boot_header
= calloc(1, sizeof(*boot_header
));
2257 if (boot_header
== NULL
)
2258 error_exit("Insufficient memory");
2260 boot_header
->header_id
= 0x91; // So far this is the last boot header
2261 boot_header
->platform_id
= platform_id
;
2262 // boot_header->next_header = NULL;
2263 // boot_header->num_entries = 0;
2264 // boot_header->entry_list = NULL;
2266 old_boot_entry
= NULL
;
2267 old_platform_id
= platform_id
;
2269 // Link into the header list
2270 prev_boot_header
->header_id
= 0x90; // The previous boot header was not the last one
2271 prev_boot_header
->next_header
= boot_header
;
2275 // Add the entry into the header
2276 ++boot_header
->num_entries
;
2277 if (old_boot_entry
== NULL
)
2278 boot_header
->entry_list
= boot_entry
;
2280 old_boot_entry
->next_entry
= boot_entry
;
2286 else if (strcmp(argv
[i
], "-q") == 0)
2287 scan_files_only
= TRUE
;
2288 // else if (strcmp(argv[i], "-x") == 0)
2289 // compute_crc = TRUE;
2290 else if (i
+ 2 < argc
)
2292 strcpy(source
, argv
[i
++]);
2293 strncpy(volume_label
, argv
[i
++], sizeof(volume_label
) - 1);
2294 strcpy(cd
.filespecs
, argv
[i
]);
2297 error_exit("Missing command line argument");
2301 error_exit("Missing source directory");
2302 if (volume_label
[0] == 0)
2303 error_exit("Missing volume label");
2304 if (cd
.filespecs
[0] == 0)
2305 error_exit("Missing image file specifications");
2307 if (source
[0] != '@')
2309 /* set source[] and end_source to source directory,
2310 * with a terminating directory separator */
2311 end_source
= source
+ strlen(source
);
2312 if (end_source
[-1] == ':')
2313 *end_source
++ = '.';
2314 if (end_source
[-1] != DIR_SEPARATOR_CHAR
)
2315 *end_source
++ = DIR_SEPARATOR_CHAR
;
2317 /* scan all files and create directory structure in memory */
2318 make_directory_records(&root
);
2322 char *trimmedline
, *targetname
, *normdir
, *srcname
, *eq
;
2323 char lineread
[1024];
2325 FILE *f
= fopen(source
+1, "r");
2328 error_exit("Cannot open CD-ROM file description '%s'\n", source
+1);
2330 while (fgets(lineread
, sizeof(lineread
), f
))
2332 /* We treat these characters as line endings */
2333 trimmedline
= strtok(lineread
, "\t\r\n;");
2334 eq
= strchr(trimmedline
, '=');
2337 /* Treat this as a directory name */
2338 targetname
= trimmedline
;
2339 normdir
= strdup(targetname
);
2340 normalize_dirname(normdir
);
2341 dir_hash_create_dir(&specified_files
, targetname
, normdir
);
2346 targetname
= strtok(lineread
, "=");
2347 srcname
= strtok(NULL
, "");
2350 if (_access(srcname
, R_OK
) == 0)
2352 if (access(srcname
, R_OK
) == 0)
2355 if (!dir_hash_add_file(&specified_files
, srcname
, targetname
))
2356 error_exit("Target '%s' (file '%s') is invalid\n", targetname
, srcname
);
2359 error_exit("Cannot access file '%s' (target '%s')\n", srcname
, targetname
);
2364 /* scan all files and create directory structure in memory */
2365 scan_specified_files(&root
, &specified_files
.root
);
2368 /* sort path table entries */
2369 root
.next_in_path_table
= sort_linked_list(root
.next_in_path_table
,
2371 compare_path_table_order
);
2373 // initialize CD-ROM write buffer
2380 // make non-writing pass over directory structure to obtain the proper
2381 // sector numbers and offsets and to determine the size of the image
2383 number_of_files
= bytes_in_files
= number_of_directories
=
2384 bytes_in_directories
= unused_bytes_at_ends_of_files
= 0;
2387 if (verbosity
>= NORMAL
)
2389 printf("%s bytes ", edit_with_commas(bytes_in_files
, TRUE
));
2390 printf("in %s files\n", edit_with_commas(number_of_files
, FALSE
));
2391 printf("%s unused bytes at ends of files\n",
2392 edit_with_commas(unused_bytes_at_ends_of_files
, TRUE
));
2393 printf("%s bytes ", edit_with_commas(bytes_in_directories
, TRUE
));
2394 printf("in %s directories\n",
2395 edit_with_commas(number_of_directories
, FALSE
));
2396 printf("%s other bytes\n", edit_with_commas(root
.sector
* SECTOR_SIZE
, TRUE
));
2397 puts("-------------");
2398 printf("%s total bytes\n",
2399 edit_with_commas(total_sectors
* SECTOR_SIZE
, TRUE
));
2400 puts("=============");
2403 if (size_limit
!= 0 && total_sectors
> size_limit
)
2404 error_exit("Size limit exceeded");
2406 if (!scan_files_only
)
2408 // re-initialize CD-ROM write buffer
2410 cd
.file
= fopen(cd
.filespecs
, "w+b");
2411 if (cd
.file
== NULL
)
2412 error_exit("Cannot open image file '%s'", cd
.filespecs
);
2418 // make writing pass over directory structure
2426 if (fclose(cd
.file
) != 0)
2429 error_exit("File write error in image file '%s'", cd
.filespecs
);
2432 if (verbosity
>= NORMAL
)
2433 puts("CD-ROM image made successfully");
2436 dir_hash_destroy(&specified_files
);