xref: /reactos/sdk/tools/fatten/fatten.c (revision 40462c92)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS FAT Image Creator
4  * FILE:            tools/fatten/fatten.c
5  * PURPOSE:         FAT Image Creator (for EFI Boot)
6  * PROGRAMMERS:     David Quintana
7  */
8 #include <stdio.h>
9 #include <string.h>
10 #include <time.h>
11 #include <ctype.h>
12 #include "fatfs/ff.h"
13 #include "fatfs/diskio.h"
14 
15 static FATFS g_Filesystem;
16 static int isMounted = 0;
17 static unsigned char buff[32768];
18 
19 // tool needed by fatfs
20 DWORD get_fattime(void)
21 {
22     /* 31-25: Year(0-127 org.1980), 24-21: Month(1-12), 20-16: Day(1-31) */
23     /* 15-11: Hour(0-23), 10-5: Minute(0-59), 4-0: Second(0-29 *2) */
24 
25     time_t rawtime;
26     struct tm * timeinfo;
27 
28     time(&rawtime);
29     timeinfo = localtime(&rawtime);
30 
31     {
32     union FatTime {
33         struct {
34             DWORD Second : 5; // div 2
35             DWORD Minute : 6;
36             DWORD Hour   : 5;
37             DWORD Day    : 5;
38             DWORD Month  : 4;
39             DWORD Year   : 7; // year-1980
40         };
41         DWORD whole;
42     } myTime = {
43         {
44             timeinfo->tm_sec / 2,
45             timeinfo->tm_min,
46             timeinfo->tm_hour,
47             timeinfo->tm_mday,
48             timeinfo->tm_mon  +  1,
49             timeinfo->tm_year - 80,
50         }
51     };
52 
53     return myTime.whole;
54     }
55 }
56 
57 void print_help(const char* name)
58 {
59     printf("\n");
60     printf("Syntax: %s image_file [list of commands]\n\n", name);
61 #if _WIN32
62     printf("Commands: [Note: both '/' and '-' are accepted as command prefixes.]\n");
63 #else
64     printf("Commands:\n");
65 #endif
66     // printf("    -format <sectors> [<filesystem>] [<custom header label>]\n"
67     printf("    -format <sectors> [<custom header label>]\n"
68            "            Formats the disk image.\n");
69     printf("    -boot <sector file>\n"
70            "            Writes a new boot sector.\n");
71     printf("    -add <src path> <dst path>\n"
72            "            Copies an external file or directory into the image.\n");
73     printf("    -extract <src path> <dst path>\n"
74            "            Copies a file or directory from the image into an external file\n"
75            "            or directory.\n");
76     printf("    -move <src path> <new path>\n"
77            "            Moves/renames a file or directory.\n");
78     printf("    -copy <src path> <new path>\n"
79            "            Copies a file or directory.\n");
80     printf("    -mkdir <src path> <new path>\n"
81            "            Creates a directory.\n");
82     printf("    -rmdir <src path> <new path>\n"
83            "            Creates a directory.\n");
84     printf("    -list [<pattern>]\n"
85            "            Lists files a directory (defaults to root).\n");
86 }
87 
88 #define PRINT_HELP_AND_QUIT() \
89     do { \
90         ret = 1; \
91         print_help(oargv[0]); \
92         goto exit; \
93     } while (0)
94 
95 int is_command(const char* parg)
96 {
97 #if _WIN32
98     return (parg[0] == '/') || (parg[0] == '-');
99 #else
100     return (parg[0] == '-');
101 #endif
102 }
103 
104 #define NEED_PARAMS(_min_, _max_) \
105     do {\
106         if (nargs < _min_) { fprintf(stderr, "Error: Too few args for command %s.\n" , argv[-1]); PRINT_HELP_AND_QUIT(); } \
107         if (nargs > _max_) { fprintf(stderr, "Error: Too many args for command %s.\n", argv[-1]); PRINT_HELP_AND_QUIT(); } \
108     } while(0)
109 
110 int need_mount(void)
111 {
112     int r;
113 
114     if (isMounted)
115         return FR_OK;
116 
117     r = f_mount(&g_Filesystem, "0:", 0);
118     if (r)
119         return r;
120 
121     isMounted = 1;
122     return FR_OK;
123 }
124 
125 #define NEED_MOUNT() \
126     do { ret = need_mount(); if(ret) \
127     {\
128         fprintf(stderr, "Error: Could not mount disk (%d).\n", ret); \
129         goto exit; \
130     } } while(0)
131 
132 int main(int oargc, char* oargv[])
133 {
134     int ret;
135     int    argc = oargc - 1;
136     char** argv = oargv + 1;
137 
138     // first parameter must be the image file.
139     if (argc == 0)
140     {
141         fprintf(stderr, "Error: First parameter must be a filename.\n");
142         PRINT_HELP_AND_QUIT();
143     }
144 
145     if (is_command(argv[0]))
146     {
147         fprintf(stderr, "Error: First parameter must be a filename, found '%s' instead.\n", argv[0]);
148         PRINT_HELP_AND_QUIT();
149     }
150 
151     if (disk_openimage(0, argv[0]))
152     {
153         fprintf(stderr, "Error: Could not open image file '%s'.\n", argv[0]);
154         ret = 1;
155         goto exit;
156     }
157 
158     argc--;
159     argv++;
160 
161     while (argc > 0)
162     {
163         char* parg = *argv;
164         int nargs = 0;
165         int i = 0;
166 
167         if (!is_command(parg))
168         {
169             fprintf(stderr, "Error: Expected a command, found '%s' instead.\n", parg);
170             PRINT_HELP_AND_QUIT();
171         }
172 
173         parg++;
174         argv++;
175         argc--;
176 
177         // find next command, to calculare number of args
178         while ((argv[i] != NULL) && !is_command(argv[i++]))
179             nargs++;
180 
181         if (strcmp(parg, "format") == 0)
182         {
183             // NOTE: The fs driver detects which FAT format fits best based on size
184             int sectors;
185 
186             NEED_PARAMS(1, 2);
187 
188             // Arg 1: number of sectors
189             sectors = atoi(argv[0]);
190 
191             if (sectors <= 0)
192             {
193                 fprintf(stderr, "Error: Sectors must be > 0\n");
194                 ret = 1;
195                 goto exit;
196             }
197 
198             if (disk_ioctl(0, SET_SECTOR_COUNT, &sectors))
199             {
200                 fprintf(stderr, "Error: Failed to set sector count to %d.\n", sectors);
201                 ret = 1;
202                 goto exit;
203             }
204 
205             NEED_MOUNT();
206 
207             ret = f_mkfs("0:", 1, sectors < 4096 ? 1 : 8);
208             if (ret)
209             {
210                 fprintf(stderr, "Error: Formatting drive: %d.\n", ret);
211                 goto exit;
212             }
213 
214             // Arg 2: custom header label (optional)
215             if (nargs > 1)
216             {
217 #define FAT_VOL_LABEL_LEN   11
218                 char vol_label[2 + FAT_VOL_LABEL_LEN + 1]; // Null-terminated buffer
219                 char* label = vol_label + 2; // The first two characters are reserved for the drive number "0:"
220                 char ch;
221 
222                 int i, invalid = 0;
223                 int len = strlen(argv[1]);
224 
225                 if (len <= FAT_VOL_LABEL_LEN)
226                 {
227                     // Verify each character (should be printable ASCII)
228                     // and copy it in uppercase.
229                     for (i = 0; i < len; i++)
230                     {
231                         ch = toupper(argv[1][i]);
232                         if ((ch < 0x20) || !isprint(ch))
233                         {
234                             invalid = 1;
235                             break;
236                         }
237 
238                         label[i] = ch;
239                     }
240 
241                     if (!invalid)
242                     {
243                         // Pad the label with spaces
244                         while (len < FAT_VOL_LABEL_LEN)
245                         {
246                             label[len++] = ' ';
247                         }
248                     }
249                 }
250                 else
251                 {
252                     invalid = 1;
253                 }
254 
255                 if (invalid)
256                 {
257                     fprintf(stderr, "Error: Header label is limited to 11 printable uppercase ASCII symbols.");
258                     ret = 1;
259                     goto exit;
260                 }
261 
262                 if (disk_read(0, buff, 0, 1))
263                 {
264                     fprintf(stderr, "Error: Unable to read existing boot sector from image.");
265                     ret = 1;
266                     goto exit;
267                 }
268 
269                 if (g_Filesystem.fs_type == FS_FAT32)
270                 {
271                     memcpy(buff + 71, label, FAT_VOL_LABEL_LEN);
272                 }
273                 else
274                 {
275                     memcpy(buff + 43, label, FAT_VOL_LABEL_LEN);
276                 }
277 
278                 if (disk_write(0, buff, 0, 1))
279                 {
280                     fprintf(stderr, "Error: Unable to write new boot sector to image.");
281                     ret = 1;
282                     goto exit;
283                 }
284 
285                 // Set also the directory volume label
286                 memcpy(vol_label, "0:", 2);
287                 vol_label[2 + FAT_VOL_LABEL_LEN] = '\0';
288                 if (f_setlabel(vol_label))
289                 {
290                     fprintf(stderr, "Error: Unable to set the volume label.");
291                     ret = 1;
292                     goto exit;
293                 }
294             }
295         }
296         else if (strcmp(parg, "boot") == 0)
297         {
298             FILE* fe;
299             BYTE* temp = buff + 1024;
300 
301             NEED_PARAMS(1, 1);
302 
303             // Arg 1: boot file
304 
305             fe = fopen(argv[0], "rb");
306             if (!fe)
307             {
308                 fprintf(stderr, "Error: Unable to open external file '%s' for reading.", argv[0]);
309                 ret = 1;
310                 goto exit;
311             }
312 
313             if (!fread(buff, 512, 1, fe))
314             {
315                 fprintf(stderr, "Error: Unable to read boot sector from file '%s'.", argv[0]);
316                 fclose(fe);
317                 ret = 1;
318                 goto exit;
319             }
320 
321             fclose(fe);
322 
323             NEED_MOUNT();
324 
325             if (disk_read(0, temp, 0, 1))
326             {
327                 fprintf(stderr, "Error: Unable to read existing boot sector from image.");
328                 ret = 1;
329                 goto exit;
330             }
331 
332             if (g_Filesystem.fs_type == FS_FAT32)
333             {
334                 printf("TODO: Writing boot sectors for FAT32 images not yet supported.");
335                 ret = 1;
336                 goto exit;
337             }
338             else
339             {
340 #define FAT16_HEADER_START 3
341 #define FAT16_HEADER_END 62
342 
343                 memcpy(buff + FAT16_HEADER_START, temp + FAT16_HEADER_START, FAT16_HEADER_END - FAT16_HEADER_START);
344             }
345 
346             if (disk_write(0, buff, 0, 1))
347             {
348                 fprintf(stderr, "Error: Unable to write new boot sector to image.");
349                 ret = 1;
350                 goto exit;
351             }
352         }
353         else if (strcmp(parg, "add") == 0)
354         {
355             FILE* fe;
356             FIL   fv = { 0 };
357             UINT rdlen = 0;
358             UINT wrlen = 0;
359 
360             NEED_PARAMS(2, 2);
361 
362             NEED_MOUNT();
363 
364             // Arg 1: external file to add
365             // Arg 2: virtual filename
366 
367             fe = fopen(argv[0], "rb");
368             if (!fe)
369             {
370                 fprintf(stderr, "Error: Unable to open external file '%s' for reading.", argv[0]);
371                 ret = 1;
372                 goto exit;
373             }
374 
375             if (f_open(&fv, argv[1], FA_WRITE | FA_CREATE_ALWAYS))
376             {
377                 fprintf(stderr, "Error: Unable to open file '%s' for writing.", argv[1]);
378                 fclose(fe);
379                 ret = 1;
380                 goto exit;
381             }
382 
383             while ((rdlen = fread(buff, 1, sizeof(buff), fe)) > 0)
384             {
385                 if (f_write(&fv, buff, rdlen, &wrlen) || wrlen < rdlen)
386                 {
387                     fprintf(stderr, "Error: Unable to write '%d' bytes to disk.", wrlen);
388                     ret = 1;
389                     goto exit;
390                 }
391             }
392 
393             fclose(fe);
394             f_close(&fv);
395         }
396         else if (strcmp(parg, "extract") == 0)
397         {
398             FIL   fe = { 0 };
399             FILE* fv;
400             UINT rdlen = 0;
401             UINT wrlen = 0;
402 
403             NEED_PARAMS(2, 2);
404 
405             NEED_MOUNT();
406 
407             // Arg 1: virtual file to extract
408             // Arg 2: external filename
409 
410             if (f_open(&fe, argv[0], FA_READ))
411             {
412                 fprintf(stderr, "Error: Unable to open file '%s' for reading.", argv[0]);
413                 ret = 1;
414                 goto exit;
415             }
416 
417             fv = fopen(argv[1], "wb");
418             if (!fv)
419             {
420                 fprintf(stderr, "Error: Unable to open external file '%s' for writing.", argv[1]);
421                 f_close(&fe);
422                 ret = 1;
423                 goto exit;
424             }
425 
426             while ((f_read(&fe, buff, sizeof(buff), &rdlen) == 0) && (rdlen > 0))
427             {
428                 if (fwrite(buff, 1, rdlen, fv) < rdlen)
429                 {
430                     fprintf(stderr, "Error: Unable to write '%d' bytes to file.", rdlen);
431                     ret = 1;
432                     goto exit;
433                 }
434             }
435 
436             f_close(&fe);
437             fclose(fv);
438         }
439         else if (strcmp(parg, "move") == 0)
440         {
441             NEED_PARAMS(2, 2);
442 
443             NEED_MOUNT();
444             // Arg 1: src path & filename
445             // Arg 2: new path & filename
446 
447             if (f_rename(argv[0], argv[1]))
448             {
449                 fprintf(stderr, "Error: Unable to move/rename '%s' to '%s'", argv[0], argv[1]);
450                 ret = 1;
451                 goto exit;
452             }
453         }
454         else if (strcmp(parg, "copy") == 0)
455         {
456             FIL fe = { 0 };
457             FIL fv = { 0 };
458             UINT rdlen = 0;
459             UINT wrlen = 0;
460 
461             NEED_PARAMS(2, 2);
462 
463             NEED_MOUNT();
464             // Arg 1: src path & filename
465             // Arg 2: new path & filename
466 
467             if (f_open(&fe, argv[0], FA_READ))
468             {
469                 fprintf(stderr, "Error: Unable to open file '%s' for reading.", argv[0]);
470                 ret = 1;
471                 goto exit;
472             }
473             if (f_open(&fv, argv[1], FA_WRITE | FA_CREATE_ALWAYS))
474             {
475                 fprintf(stderr, "Error: Unable to open file '%s' for writing.", argv[1]);
476                 f_close(&fe);
477                 ret = 1;
478                 goto exit;
479             }
480 
481             while ((f_read(&fe, buff, sizeof(buff), &rdlen) == 0) && (rdlen > 0))
482             {
483                 if (f_write(&fv, buff, rdlen, &wrlen) || wrlen < rdlen)
484                 {
485                     fprintf(stderr, "Error: Unable to write '%d' bytes to disk.", wrlen);
486                     ret = 1;
487                     goto exit;
488                 }
489             }
490 
491             f_close(&fe);
492             f_close(&fv);
493         }
494         else if (strcmp(parg, "mkdir") == 0)
495         {
496             NEED_PARAMS(1, 1);
497 
498             NEED_MOUNT();
499 
500             // Arg 1: folder path
501             if (f_mkdir(argv[0]))
502             {
503                 fprintf(stderr, "Error: Unable to create directory.");
504                 ret = 1;
505                 goto exit;
506             }
507         }
508         else if (strcmp(parg, "delete") == 0)
509         {
510             NEED_PARAMS(1, 1);
511 
512             NEED_MOUNT();
513 
514             // Arg 1: file/folder path (cannot delete non-empty folders)
515             if (f_unlink(argv[0]))
516             {
517                 fprintf(stderr, "Error: Unable to delete file or directory.");
518                 ret = 1;
519                 goto exit;
520             }
521         }
522         else if (strcmp(parg, "list") == 0)
523         {
524             char* root = "/";
525             DIR dir = { 0 };
526             FILINFO info = { 0 };
527             char lfname[257];
528 
529             NEED_PARAMS(0, 1);
530 
531             // Arg 1: folder path (optional)
532 
533             if (nargs == 1)
534             {
535                 root = argv[0];
536             }
537 
538             if (f_opendir(&dir, root))
539             {
540                 fprintf(stderr, "Error: Unable to opening directory '%s' for listing.\n", root);
541                 ret = 1;
542                 goto exit;
543             }
544 
545             printf("Listing directory contents of: %s\n", root);
546 
547             info.lfname = lfname;
548             info.lfsize = sizeof(lfname)-1;
549             while ((!f_readdir(&dir, &info)) && (strlen(info.fname) > 0))
550             {
551                 if (strlen(info.lfname) > 0)
552                     printf(" - %s (%s)\n", info.lfname, info.fname);
553                 else
554                     printf(" - %s\n", info.fname);
555             }
556         }
557         else
558         {
559             fprintf(stderr, "Error: Unknown or invalid command: %s\n", argv[-1]);
560             PRINT_HELP_AND_QUIT();
561         }
562         argv += nargs;
563         argc -= nargs;
564     }
565 
566     ret = 0;
567 
568 exit:
569 
570     disk_cleanup(0);
571 
572     return ret;
573 }
574