5 #if defined(__unix__) || defined(__APPLE__)
13 static void ShowSyntax()
15 Log("ECERE Archiver v0.1\n");
16 Log("Copyright (c) 2003 Jerome Jacovella-St-Louis\n\n");
17 Log($"General Syntax:\n");
18 Log($" eAR <command> <archive> <parameters>\n");
19 Log($"Extraction Commands:\n");
20 Log($" v (View) <archive> [files...]\n");
21 Log($" x (Extract All) <archive> [where]\n");
22 Log($" e (Extract) <archive> <files...>\n");
23 Log($"Modification Commands:\n");
24 Log($" a (Add) <archive> <files...>\n");
25 Log($" r (Refresh) <archive> <files...>\n");
26 Log($" u (Update) <archive> <files...>\n");
27 Log($" m (Move) <archive> <files...> <to>\n");
28 Log($" n (Rename) <archive> <file> <new name>\n");
29 Log($" d (Delete) <archive> <files...>\n");
30 Log($" c (Clear) <archive>\n");
31 Log($" s (Self Extract) <archive> <self-extractable> (With a: overwrite)\n");
33 Log($"(aru) f Treat <files> as folders to pack at the root of the archive\n");
34 Log($"(aru) 0 No Compression\n");
35 Log($"(aru) 1 ... 9 (Fastest Compression ... Best Compression (default = 9))\n");
36 Log($"(earu) w Specify an output directory after <files>\n");
37 Log($"(xearu) q Quiet mode\n");
40 #define ARCHIVE_ACTION_VIEW 1
41 #define ARCHIVE_ACTION_XTRACTALL 2
42 #define ARCHIVE_ACTION_EXTRACT 4
43 #define ARCHIVE_ACTION_ADD 5
44 #define ARCHIVE_ACTION_MOVE 6
45 #define ARCHIVE_ACTION_RENAME 7
46 #define ARCHIVE_ACTION_DELETE 8
47 #define ARCHIVE_ACTION_CLEAR 9
48 #define ARCHIVE_ACTION_SELFEXT 10
50 static void ViewArchive(const char * path)
52 FileListing listing { path };
53 char string[MAX_LOCATION];
54 const char * directory;
56 SplitArchivePath(path, string, &directory);
60 strcpy(string, directory);
61 if(!strcmp(directory, "/") || !strcmp(directory, "\\"))
62 strcpy(string, DIR_SEPS);
64 strcat(string, DIR_SEPS);
71 char timeString[100]; //28]; I18n strings take up more characters
72 strcpy(string, directory);
75 if(!strcmp(directory, "/") || !strcmp(directory, "\\"))
76 strcpy(string, DIR_SEPS);
78 strcat(string, DIR_SEPS);
80 PathCat(string, listing.name);
82 ((DateTime)listing.stats.modified).local.OnGetString(timeString, null, null);
84 strcat(string, $"\n Modified: ");
85 strcat(string, timeString);
88 if(listing.stats.attribs.isDirectory)
89 ViewArchive(listing.path);
94 #define BUFFERSIZE 0x10000
96 static void ExtractFileFromArchive(const char * path, const char * outputFile)
98 char fileName[MAX_LOCATION];
99 FileAttribs exists = FileExists(path);
100 bool setTime = false;
103 if(exists.isDirectory)
105 FileListing listing { path };
109 if(MakeDir(outputFile))
112 FileGetStats(path, &stats);
116 while(listing.Find())
118 strcpy(fileName, outputFile);
120 // Tweak file name if out
123 if(!strcmp(fileName, ".")) fileName[0] = '\0';
124 if(listing.name[0] == '/' || listing.name[0] == '\\')
126 char * afterSlash, rest[MAX_LOCATION];
127 for(afterSlash = fileName; *afterSlash == '/' || *afterSlash == '\\'; afterSlash++);
128 strcpy(rest, afterSlash);
129 PathCat(fileName, "_root");
130 PathCat(fileName, rest);
132 else if(listing.name[1] == ':')
135 sprintf(letter, "_%cdrive", toupper(listing.name[0]));
136 PathCat(fileName, letter);
137 PathCat(fileName, listing.name[2] ? (listing.name + 3) : (listing.name + 2));
140 PathCat(fileName, listing.name);
143 PathCat(fileName, listing.name);
144 if(!strcmp(fileName, "/") || !strcmp(fileName, "\\"))
145 strcpy(fileName, DIR_SEPS);
146 ExtractFileFromArchive(listing.path, fileName);
151 File input = FileOpen(path, read);
154 File output = FileOpen(outputFile, write);
157 FileSize dataSize, c;
158 static byte buffer[BUFFERSIZE];
159 FileGetSize(path, &dataSize);
161 Logf($"Extracting %s...\n", outputFile);
162 for(c = 0; c<dataSize; c += BUFFERSIZE)
164 uint size = (dataSize > c + BUFFERSIZE) ? BUFFERSIZE : (dataSize - c);
165 input.Read(buffer, 1, size);
166 output.Write(buffer, 1, size);
170 FileGetStats(path, &stats);
176 FileSetTime(outputFile, stats.created, 0, stats.modified);
179 static bool AddToArchive(Archive archive, ArchiveDir parentDir, const char * name, const char * path, ArchiveAddMode addMode, int compression)
182 FileAttribs exists = FileExists(path);
183 if(exists.isDirectory)
185 ArchiveDir directory;
186 if(name[0] || !parentDir)
189 FileGetStats(path, &stats);
191 directory = parentDir.OpenDirectory(name, &stats, addMode);
193 directory = archive.OpenDirectory(name, &stats, addMode);
196 directory = parentDir;
199 FileListing listing { path };
200 while(listing.Find())
202 if(!AddToArchive(archive, directory, listing.name, listing.path, addMode, compression))
208 if(directory != parentDir)
217 Logf($"Adding %s...", name);
218 if(parentDir.Add(name, path, addMode, compression, &ratio, &newPosition))
223 Logf("(%2d.%1d%%)", ratio / 10, ratio % 10);
228 Logf($"Skipped%s%s.\n", quiet ? " " : "", quiet ? name : "");
232 Logf($"Out of disk space.\nError: Ran out of disk space while archiving%s%s.\n", quiet ? " " : "", quiet ? name : "");
233 ((GuiApplication)__thisModule).exitCode = 1;
240 static void MoveFileInArchive(Archive* archive, const char * sourcePath, const char * outputDirectory)
242 // Verify if source file/directory exists and figure its kind
243 FileAttribs exists = FileExists(sourcePath);
246 char sourceFileName[MAX_FILENAME], sourceDirectory[MAX_LOCATION];
247 char archiveName[MAX_LOCATION];
249 char existingFilePath[MAX_LOCATION], * existingFile;
250 bool rootMoving = false;
251 FileAttribs outputExists;
254 SplitArchivePath(sourcePath, archiveName, &source);
256 GetLastDirectory(source, sourceFileName);
257 StripLastDirectory(source, sourceDirectory);
259 sprintf(existingFilePath, "<%s>", archiveName);
260 existingFile = existingFilePath + strlen(existingFilePath);
261 PathCat(existingFile, outputDirectory);
263 if(!sourceDirectory[0] &&
264 ((sourceFileName[0] && sourceFileName[1] == ':') ||
265 sourceFileName[0] == '\\' || sourceFileName[0] == '/'))
268 PathCat(existingFile, sourceFileName);
270 if(rootMoving || fstrcmp(outputDirectory, sourceDirectory))
272 // If directory exists in destination directory, move files and then delete
273 outputExists = FileExists(existingFilePath);
275 // If source is a directory
276 if(exists.isDirectory)
278 // Check if destination directory is within the source directory
280 char outputPart[MAX_FILENAME], outputRest[MAX_LOCATION];
281 char sourcePart[MAX_FILENAME], sourceRest[MAX_LOCATION];
283 strcpy(outputRest, outputDirectory);
284 strcpy(sourceRest, source);
288 SplitDirectory(outputRest, outputPart, outputRest);
289 SplitDirectory(sourceRest, sourcePart, sourceRest);
290 if(fstrcmp(sourcePart, outputPart) || !outputPart[0])
297 // Proceed with the move
298 if(!within || !source[0])
300 // If directory exists in destination directory, move files and then delete
301 if(outputExists.isDirectory || rootMoving || !source[0])
304 FileListing listing { sourcePath };
309 Logf($"Moving files in root to %s.\n", outputDirectory[0] ? outputDirectory : "root");
310 dir = archive->OpenDirectory(outputDirectory, null, 0);
313 char archiveName[MAX_LOCATION];
314 const char * archiveFile;
317 SplitArchivePath(sourcePath, archiveName, &archiveFile);
318 *archive = ArchiveOpen(archiveName, { true });
322 Logf($"Merging directory %s in %s with %s in %s.\n",
324 sourceDirectory[0] ? sourceDirectory : "root",
326 outputDirectory[0] ? outputDirectory : "root");
328 while(listing.Find())
330 if(strcmp(listing.path, existingFilePath))
331 MoveFileInArchive(archive, listing.path, existingFile);
336 input = archive->OpenDirectory(sourceDirectory, null, 0);
339 input.Delete(sourceFileName);
344 else if(outputExists)
345 Logf($"A file with the same name already exists (%s).\n", existingFile);
351 Logf($"Can't move directory %s inside itself.\n", source);
353 // If source is a file
354 else if(outputExists.isDirectory)
355 Logf($"A folder with the same name already exists (%s).\n", existingFile);
361 // It is important for the output directory to be opened first, as it might
362 // interfere with the input directory while its path is created.
363 ArchiveDir output = archive->OpenDirectory(outputDirectory, null, 0);
366 ArchiveDir input = archive->OpenDirectory(sourceDirectory, null, 0);
369 // If a file by the same name exists already, replace it
370 if(outputExists.isFile)
371 output.Delete(sourceFileName);
373 Logf($"Moving file %s in directory %s to %s.\n", sourceFileName,
374 sourceDirectory[0] ? sourceDirectory : "root",
375 outputDirectory[0] ? outputDirectory : "root");
378 input.Move(sourceFileName, output);
387 Logf($"File is already in directory \"%s\".\n",
388 sourceDirectory[0] ? sourceDirectory : "root");
394 class EARApp : Application
400 int firstFileArg = 0;
402 bool extractWhere = false;
403 bool addFolders = false;
404 bool selfExtract = false;
406 ArchiveAddMode addMode = 0;
412 SetLoggingMode(stdOut, null);
416 // First validate command / options
420 for(c = 0; (ch = (char)tolower(argv[1][c])) && valid; c++)
425 case 'v': command = ARCHIVE_ACTION_VIEW; break;
426 case 'x': command = ARCHIVE_ACTION_XTRACTALL; break;
427 case 'e': command = ARCHIVE_ACTION_EXTRACT; break;
428 case 'a': command = ARCHIVE_ACTION_ADD; addMode = replace; break;
429 case 'r': command = ARCHIVE_ACTION_ADD; addMode = refresh; break;
430 case 'u': command = ARCHIVE_ACTION_ADD; addMode = update; break;
431 case 'm': command = ARCHIVE_ACTION_MOVE; break;
432 case 'n': command = ARCHIVE_ACTION_RENAME; break;
433 case 'd': command = ARCHIVE_ACTION_DELETE; break;
434 case 'c': command = ARCHIVE_ACTION_CLEAR; break;
436 if(action == ARCHIVE_ACTION_ADD && addMode == replace)
439 command = ARCHIVE_ACTION_SELFEXT;
444 if(action == ARCHIVE_ACTION_ADD)
450 if(action == ARCHIVE_ACTION_EXTRACT || action == ARCHIVE_ACTION_ADD)
459 if(action == ARCHIVE_ACTION_ADD &&
460 ch >= '0' && ch <= '9' && compression == 9)
461 compression = ch - '0';
476 // Validate commands needing parameters
479 case ARCHIVE_ACTION_VIEW:
483 case ARCHIVE_ACTION_EXTRACT:
489 valid = numFiles > 0;
491 case ARCHIVE_ACTION_ADD:
497 valid = numFiles > 0;
499 case ARCHIVE_ACTION_MOVE:
502 valid = numFiles > 0;
504 case ARCHIVE_ACTION_RENAME:
507 case ARCHIVE_ACTION_DELETE:
510 valid = numFiles > 0;
512 case ARCHIVE_ACTION_SELFEXT:
521 char archivePath[MAX_LOCATION];
524 sprintf(archivePath, "<%s>", argv[2]);
525 archivePathLen = strlen(archivePath);
527 if(action != ARCHIVE_ACTION_ADD)
530 if(!FileExists(argv[2]))
532 Logf($"Archive file not found: %s\n", argv[2]);
535 else if(FileGetSize(argv[2], &size) && !size)
537 Logf($"Archive file is empty: %s\n", argv[2]);
542 Archive archive = ArchiveOpen(argv[2], { false });
547 Logf($"File is not a valid ECERE archive: %s\n", argv[2]);
555 ExtractFileFromArchive(":extract.exe", argv[2]);
557 ExtractFileFromArchive(":extract", argv[2]);
558 chmod(argv[2], 0755);
564 case ARCHIVE_ACTION_VIEW:
567 ViewArchive(archivePath);
570 for(c = firstFileArg; c<numFiles + firstFileArg; c++)
572 char archive[MAX_LOCATION], fileName[MAX_LOCATION];
576 strcpy(fileName, archivePath);
577 PathCat(fileName, argv[c]);
578 FileFixCase(fileName);
579 SplitArchivePath(fileName, archive, &name);
581 if((exists = FileExists(fileName)))
583 if(exists.isDirectory)
584 ViewArchive(fileName);
586 Logf("%s\n", fileName + archivePathLen);
589 Logf($"File Not Found: %s\n", name);
594 case ARCHIVE_ACTION_XTRACTALL:
595 ExtractFileFromArchive(archivePath, (argc > 3) ? argv[3] : "");
597 case ARCHIVE_ACTION_EXTRACT:
599 for(c = firstFileArg; c<firstFileArg + numFiles; c++)
601 char directory[MAX_LOCATION];
602 char fileName[MAX_LOCATION];
603 char outputFile[MAX_LOCATION] = "";
605 strcpy(fileName, archivePath);
606 strcat(fileName, argv[c]);
607 if(FileExists(fileName))
611 strcpy(outputFile, argv[argc-1]);
612 if(!strcmp(outputFile, ".")) outputFile[0] = '\0';
613 if(argv[c][0] == '/' || argv[c][0] == '\\')
615 char * afterSlash, rest[MAX_LOCATION];
616 for(afterSlash = fileName; *afterSlash == '/' || *afterSlash == '\\'; afterSlash++);
617 strcpy(rest, afterSlash);
618 PathCat(fileName, "_root");
619 PathCat(fileName, rest);
621 else if(argv[c][1] == ':')
624 sprintf(letter, "_%cdrive", toupper(argv[c][0]));
625 PathCat(outputFile, letter);
626 PathCat(outputFile, argv[c][2] ? (argv[c] + 3) : (argv[c] + 2));
630 PathCat(outputFile, argv[c]);
632 if(!strcmp(outputFile, "/") || !strcmp(outputFile, "\\"))
633 strcpy(outputFile, DIR_SEPS);
635 StripLastDirectory(outputFile, directory);
639 ExtractFileFromArchive(fileName, outputFile);
644 case ARCHIVE_ACTION_ADD:
646 Archive archive = ArchiveOpen(argv[2], { true, waitLock = true });
650 archive.totalSize = 0;
653 for(c = firstFileArg; c<firstFileArg + numFiles; c++)
655 if(!AddToArchive(archive, null, extractWhere ? argv[argc-1] : "", argv[c], addMode, compression))
661 for(c = firstFileArg; c<firstFileArg + numFiles; c++)
663 FileAttribs exists = FileExists(argv[c]);
666 ArchiveDir dir = null;
667 char file[MAX_LOCATION], directory[MAX_LOCATION] = "";
669 GetLastDirectory(argv[c], file);
672 strcpy(directory, argv[argc-1]);
673 FileGetStats(directory, &stats);
676 StripLastDirectory(argv[c], directory);
678 dir = archive.OpenDirectory(directory, extractWhere ? &stats : null, addMode);
681 if(!AddToArchive(archive, dir, file, argv[c], addMode, compression))
684 Logf($"Failed to add %s to archive!\n", argv[c]);
693 Logf($"Failed to open the internal directory of archive %s!\n", argv[2]);
702 Logf($"Failed to open archive %s for writing!\n", argv[2]);
707 case ARCHIVE_ACTION_CLEAR:
709 // Strip the archive from a file (useful for data embedded with executable code)
710 Archive archive = ArchiveOpen(argv[2], { true });
715 Logf($"Archive cleared: %s.\n", argv[2]);
719 case ARCHIVE_ACTION_DELETE:
721 Archive archive = ArchiveOpen(argv[2], { true });
724 for(c = firstFileArg; c<firstFileArg + numFiles; c++)
726 char fileName[MAX_LOCATION];
729 strcpy(fileName, archivePath);
730 PathCat(fileName, argv[c]);
731 FileFixCase(fileName);
732 exists = FileExists(fileName);
736 char file[MAX_LOCATION], directory[MAX_LOCATION] = "";
737 GetLastDirectory(argv[c], file);
738 StripLastDirectory(argv[c], directory);
739 dir = archive.OpenDirectory(directory, null, 0);
742 Logf($"Deleting file %s in directory %s.\n", file,
743 directory[0] ? directory : "root");
753 case ARCHIVE_ACTION_MOVE:
755 Archive archive = ArchiveOpen(argv[2], { true });
758 char fileName[MAX_LOCATION];
760 for(c = firstFileArg; c<firstFileArg + numFiles; c++)
762 strcpy(fileName, archivePath);
763 PathCat(fileName, argv[c]);
764 MoveFileInArchive(&archive, fileName, argv[argc-1]);
770 case ARCHIVE_ACTION_RENAME:
772 Archive archive = ArchiveOpen(argv[2], { true });
775 char fileName[MAX_LOCATION];
777 strcpy(fileName, archivePath);
778 PathCat(fileName, argv[3]);
779 exists = FileExists(fileName);
782 char newName[MAX_FILENAME] = "", rest[MAX_FILENAME];
783 SplitDirectory(argv[4], newName, rest);
787 char name[MAX_LOCATION], directory[MAX_LOCATION] = "";
789 GetLastDirectory(argv[3], name);
790 StripLastDirectory(argv[3], directory);
792 ((!newName[0] || newName[1] != ':') &&
793 newName[0] != '\\' && newName[0] != '/'))
795 char existingFilePath[MAX_LOCATION], * existingFile;
796 FileAttribs outputExists;
798 Logf($"Renaming %s in directory %s to %s.\n", name,
799 directory[0] ? directory : "root", newName);
800 strcpy(existingFilePath, archivePath);
801 existingFile = existingFilePath + strlen(existingFilePath);
802 PathCat(existingFile, directory);
803 PathCat(existingFile, newName);
805 outputExists = FileExists(existingFilePath);
806 if(outputExists.isDirectory)
809 while(listing.Find())
810 MoveFileInArchive(&archive, listing.path, existingFile);
811 dir = archive.OpenDirectory(directory, null, 0);
820 dir = archive.OpenDirectory(directory, null, 0);
825 dir.Rename(name, newName);
831 Logf($"Drive letters and %s only valid at root.\n", DIR_SEPS);
834 Log($"New name contains directory structure.\n");
840 case ARCHIVE_ACTION_SELFEXT:
844 ExtractFileFromArchive(":extract.exe", argv[3]);
846 ExtractFileFromArchive(":extract", argv[3]);
847 chmod(argv[3], 0755);
849 archive = ArchiveOpen(argv[3], { true });
852 archive.totalSize = 0;
853 AddToArchive(archive, null, "", archivePath, replace, 9);