update for HEAD-2003050101
[reactos.git] / tools / cdmake / cdmake.c
1 /* $Id$ */
2 /* CD-ROM Maker
3    by Philip J. Erdelsky
4    pje@acm.org
5    http://www.alumni.caltech.edu/~pje/
6
7   ElTorito-Support
8   by Eric Kohl
9   ekohl@rz-online.de
10
11   Linux port
12   by Casper S. Hornstrup
13   chorns@users.sourceforge.net
14   */
15
16 /* According to his website, this file was released into the public domain by Phillip J. Erdelsky */
17
18 #include <stdio.h>
19 #include <fcntl.h>
20 #include <sys/stat.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #ifdef WIN32
24 #include <io.h>
25 #include <dos.h>
26 #else
27 #include <sys/io.h>
28 #include <errno.h>
29 #include <sys/types.h> 
30 #include <dirent.h>
31 #endif
32 #include <ctype.h>
33 #include <setjmp.h>
34 #include <time.h>
35 #ifndef WIN32
36 #ifndef MAX_PATH
37 #define MAX_PATH 260
38 #endif
39 #define DIR_SEPARATOR_CHAR '/'
40 #define DIR_SEPARATOR_STRING "/"
41 #else
42 #define DIR_SEPARATOR_CHAR '\\'
43 #define DIR_SEPARATOR_STRING "\\"
44 #endif
45
46
47 typedef unsigned char BYTE;
48 typedef unsigned short WORD;
49 typedef unsigned long DWORD;
50 typedef int BOOL;
51
52 const BOOL TRUE  = 1;
53 const BOOL FALSE = 0;
54
55 // file system parameters
56
57 #define MAX_LEVEL               8
58 #define MAX_NAME_LENGTH         8
59 #define MAX_EXTENSION_LENGTH    3
60 #define SECTOR_SIZE             2048
61 #define BUFFER_SIZE             (8 * SECTOR_SIZE)
62
63 const BYTE HIDDEN_FLAG    = 1;
64 const BYTE DIRECTORY_FLAG = 2;
65
66
67 struct cd_image
68 {
69   FILE *file;
70   DWORD sector;         // sector to receive next byte
71   int offset;           // offset of next byte in sector
72   int count;            // number of bytes in buffer
73   char filespecs[128];
74   BYTE *buffer;
75 };
76
77 typedef struct date_and_time
78 {
79   BYTE second;
80   BYTE minute;
81   BYTE hour;
82   BYTE day;
83   BYTE month;
84   WORD year;
85 } DATE_AND_TIME, *PDATE_AND_TIME;
86
87 typedef struct directory_record
88 {
89   struct directory_record *next_in_directory;
90   struct directory_record *next_in_path_table; /* directory record only */
91   struct directory_record *next_in_memory;
92   struct directory_record *first_record;       /* directory record only */
93   struct directory_record *parent;
94   BYTE flags;
95   char name[MAX_NAME_LENGTH+1];
96   char name_on_cd[MAX_NAME_LENGTH+1];
97   char extension[MAX_EXTENSION_LENGTH+1];
98   char extension_on_cd[MAX_EXTENSION_LENGTH+1];
99   DATE_AND_TIME date_and_time;
100   DWORD sector;
101   DWORD size;
102   unsigned level;                             /* directory record only */
103   WORD path_table_index;                      /* directory record only */
104 } DIR_RECORD, *PDIR_RECORD;
105
106
107 typedef enum directory_record_type
108 {
109   DOT_RECORD,
110   DOT_DOT_RECORD,
111   SUBDIRECTORY_RECORD,
112   FILE_RECORD
113 } DIR_RECORD_TYPE, *PDIR_RECORD_TYPE;
114
115
116 PDIR_RECORD sort_linked_list(PDIR_RECORD,
117     unsigned, int (*)(PDIR_RECORD, PDIR_RECORD));
118
119
120 static char DIRECTORY_TIMESTAMP[] = "~Y$'KOR$.3K&";
121
122 static jmp_buf error;
123 static struct cd_image cd;
124
125 char volume_label[32];
126 DIR_RECORD root;
127 char source[512];
128 char *end_source;
129 enum {QUIET, NORMAL, VERBOSE} verbosity;
130 BOOL show_progress;
131 DWORD size_limit;
132 BOOL accept_punctuation_marks;
133
134 DWORD total_sectors;
135 DWORD path_table_size;
136 DWORD little_endian_path_table_sector;
137 DWORD big_endian_path_table_sector;
138 DWORD number_of_files;
139 DWORD bytes_in_files;
140 DWORD unused_bytes_at_ends_of_files;
141 DWORD number_of_directories;
142 DWORD bytes_in_directories;
143
144 char bootimage[512];
145 BOOL eltorito;
146 DWORD boot_catalog_sector;
147 DWORD boot_image_sector;
148
149 /*-----------------------------------------------------------------------------
150 This function edits a 32-bit unsigned number into a comma-delimited form, such
151 as 4,294,967,295 for the largest possible number, and returns a pointer to a
152 static buffer containing the result. It suppresses leading zeros and commas,
153 but optionally pads the result with blanks at the left so the result is always
154 exactly 13 characters long (excluding the terminating zero).
155
156 CAUTION: A statement containing more than one call on this function, such as
157 printf("%s, %s", edit_with_commas(a), edit_with_commas(b)), will produce
158 incorrect results because all calls use the same static bufffer.
159 -----------------------------------------------------------------------------*/
160
161 static char *edit_with_commas(DWORD x, BOOL pad)
162 {
163   static char s[14];
164   unsigned i = 13;
165   do
166   {
167     if (i % 4 == 2) s[--i] = ',';
168     s[--i] = x % 10 + '0';
169   } while ((x/=10) != 0);
170   if (pad)
171   {
172     while (i > 0) s[--i] = ' ';
173   }
174   return s + i;
175 }
176
177 /*-----------------------------------------------------------------------------
178 This function releases all allocated memory blocks.
179 -----------------------------------------------------------------------------*/
180
181 static void release_memory(void)
182 {
183   while (root.next_in_memory != NULL)
184   {
185     struct directory_record *next =
186       root.next_in_memory->next_in_memory;
187     free (root.next_in_memory);
188     root.next_in_memory = next;
189   }
190   if (cd.buffer != NULL)
191   {
192     free (cd.buffer);
193     cd.buffer = NULL;
194   }
195 }
196
197 /*-----------------------------------------------------------------------------
198 This function edits and displays an error message and then jumps back to the
199 error exit point in main().
200 -----------------------------------------------------------------------------*/
201
202 #define error_exit(fmt,args...) \
203 { \
204   printf(fmt,##args); \
205   printf("\n"); \
206   if (cd.file != NULL) \
207     fclose(cd.file); \
208   release_memory(); \
209   exit(1); \
210 }
211
212 /*-----------------------------------------------------------------------------
213 This function, which is called only on the second pass, and only when the
214 buffer is not empty, flushes the buffer to the CD-ROM image.
215 -----------------------------------------------------------------------------*/
216
217 static void flush_buffer(void)
218 {
219   if (fwrite(cd.buffer, cd.count, 1, cd.file) < 1)
220     error_exit("File write error");
221   cd.count = 0;
222   if (show_progress)
223   {
224     printf("\r%s ",
225       edit_with_commas((total_sectors - cd.sector) * SECTOR_SIZE, TRUE));
226   }
227 }
228
229 /*-----------------------------------------------------------------------------
230 This function writes a single byte to the CD-ROM image. On the first pass (in
231 which cd.handle < 0), it does not actually write anything but merely updates
232 the file pointer as though the byte had been written.
233 -----------------------------------------------------------------------------*/
234
235 static void write_byte(BYTE x)
236 {
237   if (cd.file != NULL)
238   {
239     cd.buffer[cd.count] = x;
240     if (++cd.count == BUFFER_SIZE)
241       flush_buffer();
242   }
243   if (++cd.offset == SECTOR_SIZE)
244   {
245     cd.sector++;
246     cd.offset = 0;
247   }
248 }
249
250 /*-----------------------------------------------------------------------------
251 These functions write a word or double word to the CD-ROM image with the
252 specified endianity.
253 -----------------------------------------------------------------------------*/
254
255 static void write_little_endian_word(WORD x)
256 {
257   write_byte(x);
258   write_byte(x >> 8);
259 }
260
261 static void write_big_endian_word(WORD x)
262 {
263   write_byte(x >> 8);
264   write_byte(x);
265 }
266
267 static void write_both_endian_word(WORD x)
268 {
269   write_little_endian_word(x);
270   write_big_endian_word(x);
271 }
272
273 static void write_little_endian_dword(DWORD x)
274 {
275   write_byte(x);
276   write_byte(x >> 8);
277   write_byte(x >> 16);
278   write_byte(x >> 24);
279 }
280
281 static void write_big_endian_dword(DWORD x)
282 {
283   write_byte(x >> 24);
284   write_byte(x >> 16);
285   write_byte(x >> 8);
286   write_byte(x);
287 }
288
289 static void write_both_endian_dword(DWORD x)
290 {
291   write_little_endian_dword(x);
292   write_big_endian_dword(x);
293 }
294
295 /*-----------------------------------------------------------------------------
296 This function writes enough zeros to fill out the end of a sector, and leaves
297 the file pointer at the beginning of the next sector. If the file pointer is
298 already at the beginning of a sector, it writes nothing.
299 -----------------------------------------------------------------------------*/
300
301 static void fill_sector(void)
302 {
303   while (cd.offset != 0)
304     write_byte(0);
305 }
306
307 /*-----------------------------------------------------------------------------
308 This function writes a string to the CD-ROM image. The terminating \0 is not
309 written.
310 -----------------------------------------------------------------------------*/
311
312 static void write_string(char *s)
313 {
314   while (*s != 0)
315     write_byte(*s++);
316 }
317
318 /*-----------------------------------------------------------------------------
319 This function writes a block of identical bytes to the CD-ROM image.
320 -----------------------------------------------------------------------------*/
321
322 static void write_block(unsigned count, BYTE value)
323 {
324   while (count != 0)
325     {
326       write_byte(value);
327       count--;
328     }
329 }
330
331 /*-----------------------------------------------------------------------------
332 This function writes a directory record to the CD_ROM image.
333 -----------------------------------------------------------------------------*/
334
335 static void
336 write_directory_record(PDIR_RECORD d,
337                        DIR_RECORD_TYPE  DirType)
338 {
339   unsigned identifier_size;
340   unsigned record_size;
341
342   switch (DirType)
343   {
344     case DOT_RECORD:
345     case DOT_DOT_RECORD:
346       identifier_size = 1;
347       break;
348     case SUBDIRECTORY_RECORD:
349       identifier_size = strlen(d->name_on_cd);
350       break;
351     case FILE_RECORD:
352       identifier_size = strlen(d->name_on_cd) + 2;
353       if (d->extension_on_cd[0] != 0)
354         identifier_size += 1 + strlen(d->extension_on_cd);
355       break;
356   }
357   record_size = 33 + identifier_size;
358   if ((identifier_size & 1) == 0)
359     record_size++;
360   if (cd.offset + record_size > SECTOR_SIZE)
361     fill_sector();
362   write_byte(record_size);
363   write_byte(0); // number of sectors in extended attribute record
364   write_both_endian_dword(d->sector);
365   write_both_endian_dword(d->size);
366   write_byte(d->date_and_time.year - 1900);
367   write_byte(d->date_and_time.month);
368   write_byte(d->date_and_time.day);
369   write_byte(d->date_and_time.hour);
370   write_byte(d->date_and_time.minute);
371   write_byte(d->date_and_time.second);
372   write_byte(0);  // GMT offset
373   write_byte(d->flags);
374   write_byte(0);  // file unit size for an interleaved file
375   write_byte(0);  // interleave gap size for an interleaved file
376   write_both_endian_word((WORD) 1); // volume sequence number
377   write_byte(identifier_size);
378   switch (DirType)
379   {
380     case DOT_RECORD:
381       write_byte(0);
382       break;
383     case DOT_DOT_RECORD:
384       write_byte(1);
385       break;
386     case SUBDIRECTORY_RECORD:
387       write_string(d->name_on_cd);
388       break;
389     case FILE_RECORD:
390       write_string(d->name_on_cd);
391       if (d->extension_on_cd[0] != 0)
392       {
393         write_byte('.');
394         write_string(d->extension_on_cd);
395       }
396       write_string(";1");
397       break;
398   }
399   if ((identifier_size & 1) == 0)
400     write_byte(0);
401 }
402
403 /*-----------------------------------------------------------------------------
404 This function converts the date and time words from an ffblk structure and
405 puts them into a date_and_time structure.
406 -----------------------------------------------------------------------------*/
407
408 static void convert_date_and_time(PDATE_AND_TIME dt, time_t *time)
409 {
410   struct tm *timedef;
411
412   timedef = localtime(time);
413
414   dt->second = timedef->tm_sec;
415   dt->minute = timedef->tm_min;
416   dt->hour = timedef->tm_hour;
417   dt->day = timedef->tm_mday;
418   dt->month = timedef->tm_mon;
419   dt->year = timedef->tm_year + 1900;
420 }
421
422 /*-----------------------------------------------------------------------------
423 This function checks the specified character, if necessary, and
424 generates an error if it is a punctuation mark other than an underscore.
425 It also converts small letters to capital letters and returns the
426 result.
427 -----------------------------------------------------------------------------*/
428
429 static int check_for_punctuation(int c, char *name)
430 {
431   c = toupper(c & 0xFF);
432   if (!accept_punctuation_marks && !isalnum(c) && c != '_')
433     error_exit("Punctuation mark in %s", name);
434   return c;
435 }
436
437 /*-----------------------------------------------------------------------------
438 This function creates a new directory record with the information from the
439 specified ffblk. It links it into the beginning of the directory list
440 for the specified parent and returns a pointer to the new record.
441 -----------------------------------------------------------------------------*/
442
443 #if WIN32
444
445 /* Win32 version */
446 PDIR_RECORD
447 new_directory_record (struct _finddata_t *f,
448                       PDIR_RECORD parent)
449 {
450   PDIR_RECORD d;
451   char *s;
452   char *t;
453   char *n;
454
455   d = malloc(sizeof(DIR_RECORD));
456   if (d == NULL)
457     error_exit("Insufficient memory");
458   d->next_in_memory = root.next_in_memory;
459   root.next_in_memory = d;
460   {
461     s = f->name;
462     t = d->name_on_cd;
463     n = d->name;
464
465     while (*s != 0)
466     {
467       if (*s == '.')
468       {
469         s++;
470         break;
471       }
472
473       *t++ = check_for_punctuation(*s, f->name);
474       *n = *s;
475       s++;
476       n++;
477     }
478     *t = 0;
479     strcpy(d->extension, s);
480     t = d->extension_on_cd;
481     while (*s != 0)
482       *t++ = check_for_punctuation(*s++, f->name);
483     *t = 0;
484     *n = 0;
485   }
486   convert_date_and_time(&d->date_and_time, &f->time_create);
487   if (f->attrib & _A_SUBDIR)
488   {
489     if (d->extension[0] != 0)
490       error_exit("Directory with extension %s", f->name);
491     d->flags = DIRECTORY_FLAG;
492   }
493   else
494     d->flags = f->attrib & _A_HIDDEN ? HIDDEN_FLAG : 0;
495   d->size = f->size;
496   d->next_in_directory = parent->first_record;
497   parent->first_record = d;
498   d->parent = parent;
499   return d;
500 }
501
502 #else
503
504 /* Linux version */
505 PDIR_RECORD
506 new_directory_record (struct dirent *entry,
507                       struct stat *stbuf,
508                       PDIR_RECORD parent)
509 {
510   PDIR_RECORD d;
511   char *s;
512   char *t;
513   char *n;
514
515   d = malloc(sizeof(DIR_RECORD));
516   if (d == NULL)
517     error_exit("Insufficient memory");
518   d->next_in_memory = root.next_in_memory;
519   root.next_in_memory = d;
520   {
521     s = entry->d_name;
522     t = d->name_on_cd;
523     n = d->name;
524
525     while (*s != 0)
526     {
527       if (*s == '.')
528       {
529         s++;
530         break;
531       }
532
533       *t++ = check_for_punctuation(*s, entry->d_name);
534       *n = *s;
535       s++;
536       n++;
537     }
538     *t = 0;
539     strcpy(d->extension, s);
540     t = d->extension_on_cd;
541     while (*s != 0)
542       *t++ = check_for_punctuation(*s++, entry->d_name);
543     *t = 0;
544     *n = 0;
545   }
546
547   convert_date_and_time(&d->date_and_time, &stbuf->st_mtime);
548   if (entry->d_type == DT_DIR)
549   {
550     if (d->extension[0] != 0)
551       error_exit("Directory with extension %s", entry->d_name);
552     d->flags = DIRECTORY_FLAG;
553   }
554   else
555     d->flags = entry->d_name[0] == '.' ? HIDDEN_FLAG : 0;
556   d->size = stbuf->st_size;
557   d->next_in_directory = parent->first_record;
558   parent->first_record = d;
559   d->parent = parent;
560   return d;
561 }
562
563 #endif
564
565 /*-----------------------------------------------------------------------------
566 This function compares two directory records according to the ISO9660 rules
567 for directory sorting and returns a negative value if p is before q, or a
568 positive value if p is after q.
569 -----------------------------------------------------------------------------*/
570
571 static int compare_directory_order(PDIR_RECORD p, PDIR_RECORD q)
572 {
573   int n = strcmp(p->name_on_cd, q->name_on_cd);
574   if (n == 0)
575     n = strcmp(p->extension_on_cd, q->extension_on_cd);
576   return n;
577 }
578
579 /*-----------------------------------------------------------------------------
580 This function compares two directory records (which must represent
581 directories) according to the ISO9660 rules for path table sorting and returns
582 a negative value if p is before q, or a positive vlaue if p is after q.
583 -----------------------------------------------------------------------------*/
584
585 static int compare_path_table_order(PDIR_RECORD p, PDIR_RECORD q)
586 {
587   int n = p->level - q->level;
588   if (p == q)
589     return 0;
590   if (n == 0)
591   {
592     n = compare_path_table_order(p->parent, q->parent);
593     if (n == 0)
594       n = compare_directory_order(p, q);
595   }
596   return n;
597 }
598
599 /*-----------------------------------------------------------------------------
600 This function appends the specified string to the buffer source[].
601 -----------------------------------------------------------------------------*/
602
603 static void append_string_to_source(char *s)
604 {
605   while (*s != 0)
606     *end_source++ = *s++;
607 }
608
609 /*-----------------------------------------------------------------------------
610 This function scans all files from the current source[] (which must end in \,
611 and represents a directory already in the database as d),
612 and puts the appropriate directory records into the database in memory, with
613 the specified root. It calls itself recursively to scan all subdirectories.
614 -----------------------------------------------------------------------------*/
615
616 #ifdef WIN32
617
618 static void
619 make_directory_records (PDIR_RECORD d)
620 {
621   PDIR_RECORD new_d;
622   struct _finddata_t f;
623   char *old_end_source;
624   int findhandle;
625
626   d->first_record = NULL;
627   strcpy(end_source, "*.*");
628
629   findhandle =_findfirst(source, &f);
630   if (findhandle != 0)
631     {
632       do
633         {
634           if ((f.attrib & (_A_HIDDEN | _A_SUBDIR)) == 0 && f.name[0] != '.')
635             {
636               if (strcmp(f.name, DIRECTORY_TIMESTAMP) == 0)
637                 {
638                   convert_date_and_time(&d->date_and_time, &f.time_create);
639                 }
640               else
641                 {
642                   if (verbosity == VERBOSE)
643                     {
644                       old_end_source = end_source;
645                       strcpy(end_source, f.name);
646                       printf("%d: file %s\n", d->level, source);
647                       end_source = old_end_source;
648                     }
649                   (void) new_directory_record(&f, d);
650                 }
651             }
652         }
653        while (_findnext(findhandle, &f) == 0);
654
655       _findclose(findhandle);
656     }
657
658   strcpy(end_source, "*.*");
659   findhandle= _findfirst(source, &f);
660   if (findhandle)
661     {
662       do
663         {
664           if (f.attrib & _A_SUBDIR && f.name[0] != '.')
665             {
666               old_end_source = end_source;
667               append_string_to_source(f.name);
668               *end_source++ = DIR_SEPARATOR_CHAR;
669               if (verbosity == VERBOSE)
670                 {
671                   *end_source = 0;
672                   printf("%d: directory %s\n", d->level + 1, source);
673                 }
674               if (d->level < MAX_LEVEL)
675                 {
676                   new_d = new_directory_record(&f, d);
677                   new_d->next_in_path_table = root.next_in_path_table;
678                   root.next_in_path_table = new_d;
679                   new_d->level = d->level + 1;
680                   make_directory_records(new_d);
681                 }
682               else
683                 {
684                   error_exit("Directory is nested too deep");
685                 }
686               end_source = old_end_source;
687             }
688         }
689        while (_findnext(findhandle, &f) == 0);
690
691       _findclose(findhandle);
692     }
693
694   // sort directory
695   d->first_record = sort_linked_list(d->first_record, 0, compare_directory_order);
696 }
697
698 #else
699
700 /* Linux version */
701 static void
702 make_directory_records (PDIR_RECORD d)
703 {
704   PDIR_RECORD new_d;
705   DIR *dirp;
706   struct dirent *entry;
707   char *old_end_source;
708   struct stat stbuf;
709   char buf[MAX_PATH];
710
711   d->first_record = NULL;
712
713   dirp = opendir(source); 
714
715   if (dirp != NULL)
716     {
717       while ((entry = readdir (dirp)) != NULL)
718           {
719         if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
720           continue; // skip self and parent
721
722         if (entry->d_type == DT_REG) // normal file
723                     {
724               // Check for an absolute path
725               if (source[0] == DIR_SEPARATOR_CHAR)
726                 {
727                   strcpy(buf, source);
728                   strcat(buf, DIR_SEPARATOR_STRING);
729                   strcat(buf, entry->d_name);
730                 }
731               else
732                 {
733                   getcwd(buf, sizeof(buf));
734                   strcat(buf, DIR_SEPARATOR_STRING);
735                   strcat(buf, source);
736                   strcat(buf, entry->d_name);
737                 }
738               if (stat(buf, &stbuf) == -1)
739                 {
740                   error_exit("Can't access '%s' (%s)\n", buf, strerror(errno));
741                   return;
742                 }
743
744                       if (strcmp(entry->d_name, DIRECTORY_TIMESTAMP) == 0)
745                         {
746                           convert_date_and_time(&d->date_and_time, &stbuf.st_size);
747                         }
748                       else
749                         {
750                           if (verbosity == VERBOSE)
751                             {
752                               printf("%d: file %s\n", d->level, buf);
753                             }
754                           (void) new_directory_record(entry, &stbuf, d);
755                         }
756          }
757        }
758       closedir (dirp);
759     }
760   else
761     {
762       error_exit("Can't open %s\n", source);
763       return;
764     }
765
766   dirp = opendir(source); 
767   if (dirp != NULL)
768     {
769       while ((entry = readdir (dirp)) != NULL)
770         {
771         if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
772           continue; // skip self and parent
773
774           if (entry->d_type == DT_DIR) // directory
775             {
776               old_end_source = end_source;
777               append_string_to_source(entry->d_name);
778               *end_source++ = DIR_SEPARATOR_CHAR;
779               if (verbosity == VERBOSE)
780                 {
781                   *end_source = 0;
782                   printf("%d: directory %s\n", d->level + 1, source);
783                 }
784               if (d->level < MAX_LEVEL)
785                 {
786           // Check for an absolute path
787           if (source[0] == DIR_SEPARATOR_CHAR)
788             {
789               strcpy(buf, source);
790             }
791           else
792             {
793               getcwd(buf, sizeof(buf));
794               strcat(buf, DIR_SEPARATOR_STRING);
795               strcat(buf, source);
796             }
797           if (stat(buf, &stbuf) == -1)
798             {
799               error_exit("Can't access '%s' (%s)\n", buf, strerror(errno));
800               return;
801             }
802                   new_d = new_directory_record(entry, &stbuf, d);
803                   new_d->next_in_path_table = root.next_in_path_table;
804                   root.next_in_path_table = new_d;
805                   new_d->level = d->level + 1;
806                   make_directory_records(new_d);
807                 }
808               else
809                 {
810                   error_exit("Directory is nested too deep");
811                 }
812               end_source = old_end_source;
813             }
814        }
815       closedir (dirp);
816     }
817
818   // sort directory
819   d->first_record = sort_linked_list(d->first_record, 0, compare_directory_order);
820 }
821
822 #endif
823
824 /*-----------------------------------------------------------------------------
825 This function loads the file specifications for the file or directory
826 corresponding to the specified directory record into the source[] buffer. It
827 is recursive.
828 -----------------------------------------------------------------------------*/
829
830 static void get_file_specifications(PDIR_RECORD d)
831 {
832   if (d != &root)
833   {
834     get_file_specifications(d->parent);
835     append_string_to_source(d->name);
836     if ((d->flags & DIRECTORY_FLAG) == 0 && d->extension[0] != 0)
837     {
838       *end_source++ = '.';
839       append_string_to_source(d->extension);
840     }
841     if (d->flags & DIRECTORY_FLAG)
842       *end_source++ = DIR_SEPARATOR_CHAR;
843   }
844 }
845
846 static void pass(void)
847 {
848   PDIR_RECORD d;
849   PDIR_RECORD q;
850   unsigned int i;
851   char *t;
852   unsigned int index;
853   unsigned int name_length;
854   DWORD size;
855   DWORD number_of_sectors;
856   char *old_end_source;
857   int n;
858   FILE *file;
859
860   // first 16 sectors are zeros
861
862   write_block(16 * SECTOR_SIZE, 0);
863
864   // Primary Volume Descriptor
865
866   write_string("\1CD001\1");
867   write_byte(0);
868   write_block(32, ' ');  // system identifier
869
870   t = volume_label;
871   for (i = 0; i < 32; i++)
872     write_byte(*t != 0 ? toupper(*t++) : ' ');
873
874   write_block(8, 0);
875   write_both_endian_dword(total_sectors);
876   write_block(32, 0);
877   write_both_endian_word((WORD) 1); // volume set size
878   write_both_endian_word((WORD) 1); // volume sequence number
879   write_both_endian_word((WORD) 2048); // sector size
880   write_both_endian_dword(path_table_size);
881   write_little_endian_dword(little_endian_path_table_sector);
882   write_little_endian_dword((DWORD) 0);  // second little endian path table
883   write_big_endian_dword(big_endian_path_table_sector);
884   write_big_endian_dword((DWORD) 0);  // second big endian path table
885   write_directory_record(&root, DOT_RECORD);
886   write_block(128, ' ');      // volume set identifier
887   write_block(128, ' ');      // publisher identifier
888   write_block(128, ' ');      // data preparer identifier
889   write_block(128, ' ');      // application identifier
890   write_block(37, ' ');       // copyright file identifier
891   write_block(37, ' ');       // abstract file identifier
892   write_block(37, ' ');       // bibliographic file identifier
893   write_string("0000000000000000");  // volume creation
894   write_byte(0);
895   write_string("0000000000000000");  // most recent modification
896   write_byte(0);
897   write_string("0000000000000000");  // volume expires
898   write_byte(0);
899   write_string("0000000000000000");  // volume is effective
900   write_byte(0);
901   write_byte(1);
902   write_byte(0);
903   fill_sector();
904
905
906   // Boot Volume Descriptor
907
908   if (eltorito == TRUE)
909     {
910       write_byte(0);
911       write_string("CD001\1");
912       write_string("EL TORITO SPECIFICATION");  // identifier
913       write_block(9, 0);  // padding
914       write_block(32, 0);  // unused
915       write_little_endian_dword(boot_catalog_sector);  // pointer to boot catalog
916       fill_sector();
917     }
918
919
920   // Volume Descriptor Set Terminator
921
922   write_string("\377CD001\1");
923   fill_sector();
924
925
926   // Boot Catalog
927
928   if (eltorito == TRUE)
929     {
930       boot_catalog_sector = cd.sector;
931
932       // Validation entry
933       write_byte(1);
934       write_byte(0);  // x86 boot code
935       write_little_endian_word(0);  // reserved
936       write_string("ReactOS Foundation");
937       write_block(6, 0); // padding
938       write_little_endian_word(0x62E);  // checksum
939       write_little_endian_word(0xAA55);  // signature
940
941       // default entry
942       write_byte(0x88);  // bootable
943       write_byte(0);  // no emulation
944       write_big_endian_word(0);  // load segment = default (0x07c0)
945       write_byte(0);  // partition type
946       write_byte(0);  // unused
947       write_little_endian_word(4);  // sector count
948       write_little_endian_dword(boot_image_sector);  // sector count
949
950       fill_sector();
951     }
952
953
954   // Boot Image
955
956   if (eltorito == TRUE)
957     {
958       boot_image_sector = cd.sector;
959
960       file = fopen(bootimage, "rb");
961       if (file == NULL)
962         error_exit("Can't open %s\n", bootimage);
963       fseek(file, 0, SEEK_END);
964       size = ftell(file);
965       fseek(file, 0, SEEK_SET);
966       while (size > 0)
967       {
968         n = BUFFER_SIZE - cd.count;
969         if ((DWORD) n > size)
970           n = size;
971         if (fread (cd.buffer + cd.count, n, 1, file) < 1)
972         {
973           fclose(file);
974           error_exit("Read error in file %s\n", bootimage);
975         }
976         cd.count += n;
977         if (cd.count == BUFFER_SIZE)
978           flush_buffer();
979         cd.sector += n / SECTOR_SIZE;
980         cd.offset += n % SECTOR_SIZE;
981         size -= n;
982       }
983       fclose(file);
984 //      fill_sector();
985     }
986
987
988   // Little Endian Path Table
989
990   little_endian_path_table_sector = cd.sector;
991   write_byte(1);
992   write_byte(0);  // number of sectors in extended attribute record
993   write_little_endian_dword(root.sector);
994   write_little_endian_word((WORD) 1);
995   write_byte(0);
996   write_byte(0);
997
998   index = 1;
999   root.path_table_index = 1;
1000   for (d = root.next_in_path_table; d != NULL; d = d->next_in_path_table)
1001     {
1002       name_length = strlen(d->name_on_cd);
1003       write_byte(name_length);
1004       write_byte(0);  // number of sectors in extended attribute record
1005       write_little_endian_dword(d->sector);
1006       write_little_endian_word(d->parent->path_table_index);
1007       write_string(d->name_on_cd);
1008       if (name_length & 1)
1009         write_byte(0);
1010       d->path_table_index = ++index;
1011     }
1012
1013   path_table_size = (cd.sector - little_endian_path_table_sector) *
1014       SECTOR_SIZE + cd.offset;
1015   fill_sector();
1016
1017   // Big Endian Path Table
1018
1019   big_endian_path_table_sector = cd.sector;
1020   write_byte(1);
1021   write_byte(0);  // number of sectors in extended attribute record
1022   write_big_endian_dword(root.sector);
1023   write_big_endian_word((WORD) 1);
1024   write_byte(0);
1025   write_byte(0);
1026
1027   for (d = root.next_in_path_table; d != NULL; d = d->next_in_path_table)
1028     {
1029       name_length = strlen(d->name_on_cd);
1030       write_byte(name_length);
1031       write_byte(0);  // number of sectors in extended attribute record
1032       write_big_endian_dword(d->sector);
1033       write_big_endian_word(d->parent->path_table_index);
1034       write_string(d->name_on_cd);
1035       if (name_length & 1)
1036         write_byte(0);
1037     }
1038   fill_sector();
1039
1040
1041   // directories and files
1042
1043   for (d = &root; d != NULL; d = d->next_in_path_table)
1044     {
1045       // write directory
1046
1047       d->sector = cd.sector;
1048       write_directory_record(d, DOT_RECORD);
1049       write_directory_record(d == &root ? d : d->parent, DOT_DOT_RECORD);
1050       for (q = d->first_record; q != NULL; q = q->next_in_directory)
1051         {
1052           write_directory_record(q,
1053                                  q->flags & DIRECTORY_FLAG ? SUBDIRECTORY_RECORD : FILE_RECORD);
1054         }
1055       fill_sector();
1056       d->size = (cd.sector - d->sector) * SECTOR_SIZE;
1057       number_of_directories++;
1058       bytes_in_directories += d->size;
1059
1060       // write file data
1061
1062       for (q = d->first_record; q != NULL; q = q->next_in_directory)
1063       {
1064         if ((q->flags & DIRECTORY_FLAG) == 0)
1065         {
1066           q->sector = cd.sector;
1067           size = q->size;
1068           if (cd.file == NULL)
1069           {
1070             number_of_sectors = (size + SECTOR_SIZE - 1) / SECTOR_SIZE;
1071             cd.sector += number_of_sectors;
1072             number_of_files++;
1073             bytes_in_files += size;
1074             unused_bytes_at_ends_of_files +=
1075               number_of_sectors * SECTOR_SIZE - size;
1076           }
1077           else
1078           {
1079             old_end_source = end_source;
1080             get_file_specifications(q);
1081             *end_source = 0;
1082             if (verbosity == VERBOSE)
1083               printf("Writing %s\n", source);
1084             file = fopen(source, "rb");
1085             if (file == NULL)
1086               error_exit("Can't open %s\n", source);
1087             fseek(file, 0, SEEK_SET);
1088             while (size > 0)
1089             {
1090               n = BUFFER_SIZE - cd.count;
1091               if ((DWORD) n > size)
1092                 n = size;
1093               if (fread (cd.buffer + cd.count, n, 1, file) < 1)
1094               {
1095                 fclose(file);
1096                 error_exit("Read error in file %s\n", source);
1097               }
1098               cd.count += n;
1099               if (cd.count == BUFFER_SIZE)
1100                 flush_buffer();
1101               cd.sector += n / SECTOR_SIZE;
1102               cd.offset += n % SECTOR_SIZE;
1103               size -= n;
1104             }
1105             fclose(file);
1106             end_source = old_end_source;
1107             fill_sector();
1108           }
1109         }
1110       }
1111     }
1112
1113   total_sectors = (DWORD)cd.sector;
1114 }
1115
1116 static char HELP[] =
1117   "CDMAKE  [-q] [-v] [-p] [-s N] [-m] [-b bootimage]  source  volume  image\n"
1118   "\n"
1119   "  source        specifications of base directory containing all files to\n"
1120   "                be written to CD-ROM image\n"
1121   "  volume        volume label\n"
1122   "  image         image file or device\n"
1123   "  -q            quiet mode - display nothing but error messages\n"
1124   "  -v            verbose mode - display file information as files are\n"
1125   "                scanned and written - overrides -p option\n"
1126   "  -p            show progress while writing\n"
1127   "  -s N          abort operation before beginning write if image will be\n"
1128   "                larger than N megabytes (i.e. 1024*1024*N bytes)\n"
1129   "  -m            accept punctuation marks other than underscores in\n"
1130   "                names and extensions\n"
1131   "  -b bootimage  create bootable ElTorito CD-ROM using 'no emulation' mode\n";
1132
1133 /*-----------------------------------------------------------------------------
1134 Program execution starts here.
1135 -----------------------------------------------------------------------------*/
1136
1137 int main(int argc, char **argv)
1138 {
1139   BOOL q_option = FALSE;
1140   BOOL v_option = FALSE;
1141   int i;
1142   char *t;
1143
1144   if (argc < 2)
1145   {
1146     puts(HELP);
1147     return 1;
1148   }
1149
1150   if (setjmp(error))
1151     return 1;
1152
1153   // initialize root directory
1154
1155   cd.buffer = malloc (BUFFER_SIZE);
1156   if (cd.buffer == NULL)
1157     error_exit("Insufficient memory");
1158
1159   memset(&root, 0, sizeof(root));
1160   root.level = 1;
1161   root.flags = DIRECTORY_FLAG;
1162
1163   // initialize CD-ROM write buffer
1164
1165   cd.file = NULL;
1166   cd.filespecs[0] = 0;
1167
1168   // initialize parameters
1169
1170   verbosity = NORMAL;
1171   size_limit = 0;
1172   show_progress = FALSE;
1173   accept_punctuation_marks = FALSE;
1174   source[0] = 0;
1175   volume_label[0] = 0;
1176
1177   eltorito = FALSE;
1178
1179   // scan command line arguments
1180
1181   for (i = 1; i < argc; i++)
1182     {
1183       if (memcmp(argv[i], "-s", 2) == 0)
1184       {
1185         t = argv[i] + 2;
1186         if (*t == 0)
1187         {
1188           if (++i < argc)
1189             t = argv[i];
1190           else
1191             error_exit("Missing size limit parameter");
1192         }
1193         while (isdigit(*t))
1194           size_limit = size_limit * 10 + *t++ - '0';
1195         if (size_limit < 1 || size_limit > 800)
1196           error_exit("Invalid size limit");
1197         size_limit <<= 9;  // convert megabyte to sector count
1198       }
1199       else if (strcmp(argv[i], "-q") == 0)
1200         q_option = TRUE;
1201       else if (strcmp(argv[i], "-v") == 0)
1202         v_option = TRUE;
1203       else if (strcmp(argv[i], "-p") == 0)
1204         show_progress = TRUE;
1205       else if (strcmp(argv[i], "-m") == 0)
1206         accept_punctuation_marks = TRUE;
1207       else if (strcmp(argv[i], "-b") == 0)
1208       {
1209         strcpy(bootimage, argv[++i]);
1210         eltorito = TRUE;
1211       }
1212       else if (i + 2 < argc)
1213       {
1214         strcpy(source, argv[i++]);
1215         strncpy(volume_label, argv[i++], sizeof(volume_label) - 1);
1216         strcpy(cd.filespecs, argv[i]);
1217       }
1218       else
1219         error_exit("Missing command line argument");
1220     }
1221   if (v_option)
1222     {
1223       show_progress = FALSE;
1224       verbosity = VERBOSE;
1225     }
1226   else if (q_option)
1227     {
1228       verbosity = QUIET;
1229       show_progress = FALSE;
1230     }
1231   if (source[0] == 0)
1232     error_exit("Missing source directory");
1233   if (volume_label[0] == 0)
1234     error_exit("Missing volume label");
1235   if (cd.filespecs[0] == 0)
1236     error_exit("Missing image file specifications");
1237
1238
1239   // set source[] and end_source to source directory, with a terminating directory separator
1240
1241   end_source = source + strlen(source);
1242   if (end_source[-1] == ':')
1243     *end_source++ = '.';
1244   if (end_source[-1] != DIR_SEPARATOR_CHAR)
1245     *end_source++ = DIR_SEPARATOR_CHAR;
1246
1247   // scan all files and create directory structure in memory
1248
1249   make_directory_records(&root);
1250
1251   // sort path table entries
1252
1253   root.next_in_path_table = sort_linked_list(root.next_in_path_table, 1,
1254     compare_path_table_order);
1255
1256   // initialize CD-ROM write buffer
1257
1258   cd.file = NULL;
1259   cd.sector = 0;
1260   cd.offset = 0;
1261   cd.count = 0;
1262
1263   // make non-writing pass over directory structure to obtain the proper
1264   // sector numbers and offsets and to determine the size of the image
1265
1266   number_of_files = bytes_in_files = number_of_directories =
1267     bytes_in_directories = unused_bytes_at_ends_of_files =0;
1268   pass();
1269
1270   if (verbosity >= NORMAL)
1271     {
1272       printf("%s bytes ", edit_with_commas(bytes_in_files, TRUE));
1273       printf("in %s files\n", edit_with_commas(number_of_files, FALSE));
1274       printf("%s unused bytes at ends of files\n",
1275         edit_with_commas(unused_bytes_at_ends_of_files, TRUE));
1276       printf("%s bytes ", edit_with_commas(bytes_in_directories, TRUE));
1277       printf("in %s directories\n",
1278         edit_with_commas(number_of_directories, FALSE));
1279       printf("%s other bytes\n", edit_with_commas(root.sector * SECTOR_SIZE, TRUE));
1280       puts("-------------");
1281       printf("%s total bytes\n",
1282         edit_with_commas(total_sectors * SECTOR_SIZE, TRUE));
1283       puts("=============");
1284     }
1285
1286   if (size_limit != 0 && total_sectors > size_limit)
1287     error_exit("Size limit exceeded");
1288
1289   // re-initialize CD-ROM write buffer
1290
1291   cd.file = fopen(cd.filespecs, "w+b");
1292   if (cd.file == NULL)
1293     error_exit("Can't open image file %s", cd.filespecs);
1294   cd.sector = 0;
1295   cd.offset = 0;
1296   cd.count = 0;
1297
1298
1299   // make writing pass over directory structure
1300
1301   pass();
1302
1303   if (cd.count > 0)
1304     flush_buffer();
1305   if (show_progress)
1306     printf("\r             \n");
1307   if (fclose(cd.file) != 0)
1308     {
1309       cd.file = NULL;
1310       error_exit("File write error in image file %s", cd.filespecs);
1311     }
1312
1313   if (verbosity >= NORMAL)
1314     puts("CD-ROM image made successfully");
1315
1316   release_memory();
1317   return 0;
1318 }
1319
1320 /* EOF */