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(char * path)
52 FileListing listing { path };
53 char string[MAX_LOCATION], * directory;
55 SplitArchivePath(path, string, &directory);
59 strcpy(string, directory);
60 if(!strcmp(directory, "/") || !strcmp(directory, "\\"))
61 strcpy(string, DIR_SEPS);
63 strcat(string, DIR_SEPS);
70 char timeString[100]; //28]; I18n strings take up more characters
71 strcpy(string, directory);
74 if(!strcmp(directory, "/") || !strcmp(directory, "\\"))
75 strcpy(string, DIR_SEPS);
77 strcat(string, DIR_SEPS);
79 PathCat(string, listing.name);
81 ((DateTime)listing.stats.modified).local.OnGetString(timeString, null, null);
83 strcat(string, $"\n Modified: ");
84 strcat(string, timeString);
87 if(listing.stats.attribs.isDirectory)
88 ViewArchive(listing.path);
93 #define BUFFERSIZE 0x10000
95 static void ExtractFileFromArchive(char * path, char * outputFile)
97 char fileName[MAX_LOCATION];
98 FileAttribs exists = FileExists(path);
102 if(exists.isDirectory)
104 FileListing listing { path };
108 if(MakeDir(outputFile))
111 FileGetStats(path, &stats);
115 while(listing.Find())
117 strcpy(fileName, outputFile);
119 // Tweak file name if out
122 if(!strcmp(fileName, ".")) fileName[0] = '\0';
123 if(listing.name[0] == '/' || listing.name[0] == '\\')
125 char * afterSlash, rest[MAX_LOCATION];
126 for(afterSlash = fileName; *afterSlash == '/' || *afterSlash == '\\'; afterSlash++);
127 strcpy(rest, afterSlash);
128 PathCat(fileName, "_root");
129 PathCat(fileName, rest);
131 else if(listing.name[1] == ':')
134 sprintf(letter, "_%cdrive", toupper(listing.name[0]));
135 PathCat(fileName, letter);
136 PathCat(fileName, listing.name[2] ? (listing.name + 3) : (listing.name + 2));
139 PathCat(fileName, listing.name);
142 PathCat(fileName, listing.name);
143 if(!strcmp(fileName, "/") || !strcmp(fileName, "\\"))
144 strcpy(fileName, DIR_SEPS);
145 ExtractFileFromArchive(listing.path, fileName);
150 File input = FileOpen(path, read);
153 File output = FileOpen(outputFile, write);
156 FileSize dataSize, c;
157 static byte buffer[BUFFERSIZE];
158 FileGetSize(path, &dataSize);
160 Logf($"Extracting %s...\n", outputFile);
161 for(c = 0; c<dataSize; c += BUFFERSIZE)
163 uint size = (dataSize > c + BUFFERSIZE) ? BUFFERSIZE : (dataSize - c);
164 input.Read(buffer, 1, size);
165 output.Write(buffer, 1, size);
169 FileGetStats(path, &stats);
175 FileSetTime(outputFile, stats.created, 0, stats.modified);
178 static bool AddToArchive(Archive archive, ArchiveDir parentDir, char * name, char * path, ArchiveAddMode addMode, int compression)
181 FileAttribs exists = FileExists(path);
182 if(exists.isDirectory)
184 ArchiveDir directory;
185 if(name[0] || !parentDir)
188 FileGetStats(path, &stats);
190 directory = parentDir.OpenDirectory(name, &stats, addMode);
192 directory = archive.OpenDirectory(name, &stats, addMode);
195 directory = parentDir;
198 FileListing listing { path };
199 while(listing.Find())
201 if(!AddToArchive(archive, directory, listing.name, listing.path, addMode, compression))
207 if(directory != parentDir)
216 Logf($"Adding %s...", name);
217 if(parentDir.Add(name, path, addMode, compression, &ratio, &newPosition))
222 Logf("(%2d.%1d%%)", ratio / 10, ratio % 10);
227 Logf($"Skipped%s%s.\n", quiet ? " " : "", quiet ? name : "");
231 Logf($"Out of disk space.\nError: Ran out of disk space while archiving%s%s.\n", quiet ? " " : "", quiet ? name : "");
232 ((GuiApplication)__thisModule).exitCode = 1;
239 static void MoveFileInArchive(Archive* archive, char * sourcePath, char * outputDirectory)
241 // Verify if source file/directory exists and figure its kind
242 FileAttribs exists = FileExists(sourcePath);
245 char sourceFileName[MAX_FILENAME], sourceDirectory[MAX_LOCATION];
246 char archiveName[MAX_LOCATION], * source;
247 char existingFilePath[MAX_LOCATION], * existingFile;
248 bool rootMoving = false;
249 FileAttribs outputExists;
252 SplitArchivePath(sourcePath, archiveName, &source);
254 GetLastDirectory(source, sourceFileName);
255 StripLastDirectory(source, sourceDirectory);
257 sprintf(existingFilePath, "<%s>", archiveName);
258 existingFile = existingFilePath + strlen(existingFilePath);
259 PathCat(existingFile, outputDirectory);
261 if(!sourceDirectory[0] &&
262 ((sourceFileName[0] && sourceFileName[1] == ':') ||
263 sourceFileName[0] == '\\' || sourceFileName[0] == '/'))
266 PathCat(existingFile, sourceFileName);
268 if(rootMoving || fstrcmp(outputDirectory, sourceDirectory))
270 // If directory exists in destination directory, move files and then delete
271 outputExists = FileExists(existingFilePath);
273 // If source is a directory
274 if(exists.isDirectory)
276 // Check if destination directory is within the source directory
278 char outputPart[MAX_FILENAME], outputRest[MAX_LOCATION];
279 char sourcePart[MAX_FILENAME], sourceRest[MAX_LOCATION];
281 strcpy(outputRest, outputDirectory);
282 strcpy(sourceRest, source);
286 SplitDirectory(outputRest, outputPart, outputRest);
287 SplitDirectory(sourceRest, sourcePart, sourceRest);
288 if(fstrcmp(sourcePart, outputPart) || !outputPart[0])
295 // Proceed with the move
296 if(!within || !source[0])
298 // If directory exists in destination directory, move files and then delete
299 if(outputExists.isDirectory || rootMoving || !source[0])
302 FileListing listing { sourcePath };
307 Logf($"Moving files in root to %s.\n", outputDirectory[0] ? outputDirectory : "root");
308 dir = archive->OpenDirectory(outputDirectory, null, 0);
311 char archiveName[MAX_LOCATION], * archiveFile;
314 SplitArchivePath(sourcePath, archiveName, &archiveFile);
315 *archive = ArchiveOpen(archiveName, { true });
319 Logf($"Merging directory %s in %s with %s in %s.\n",
321 sourceDirectory[0] ? sourceDirectory : "root",
323 outputDirectory[0] ? outputDirectory : "root");
325 while(listing.Find())
327 if(strcmp(listing.path, existingFilePath))
328 MoveFileInArchive(archive, listing.path, existingFile);
333 input = archive->OpenDirectory(sourceDirectory, null, 0);
336 input.Delete(sourceFileName);
341 else if(outputExists)
342 Logf($"A file with the same name already exists (%s).\n", existingFile);
348 Logf($"Can't move directory %s inside itself.\n", source);
350 // If source is a file
351 else if(outputExists.isDirectory)
352 Logf($"A folder with the same name already exists (%s).\n", existingFile);
358 // It is important for the output directory to be opened first, as it might
359 // interfere with the input directory while its path is created.
360 ArchiveDir output = archive->OpenDirectory(outputDirectory, null, 0);
363 ArchiveDir input = archive->OpenDirectory(sourceDirectory, null, 0);
366 // If a file by the same name exists already, replace it
367 if(outputExists.isFile)
368 output.Delete(sourceFileName);
370 Logf($"Moving file %s in directory %s to %s.\n", sourceFileName,
371 sourceDirectory[0] ? sourceDirectory : "root",
372 outputDirectory[0] ? outputDirectory : "root");
375 input.Move(sourceFileName, output);
384 Logf($"File is already in directory \"%s\".\n",
385 sourceDirectory[0] ? sourceDirectory : "root");
391 class EARApp : Application
397 int firstFileArg = 0;
399 bool extractWhere = false;
400 bool addFolders = false;
401 bool selfExtract = false;
403 ArchiveAddMode addMode = 0;
409 SetLoggingMode(stdOut, null);
413 // First validate command / options
417 for(c = 0; (ch = (char)tolower(argv[1][c])) && valid; c++)
422 case 'v': command = ARCHIVE_ACTION_VIEW; break;
423 case 'x': command = ARCHIVE_ACTION_XTRACTALL; break;
424 case 'e': command = ARCHIVE_ACTION_EXTRACT; break;
425 case 'a': command = ARCHIVE_ACTION_ADD; addMode = replace; break;
426 case 'r': command = ARCHIVE_ACTION_ADD; addMode = refresh; break;
427 case 'u': command = ARCHIVE_ACTION_ADD; addMode = update; break;
428 case 'm': command = ARCHIVE_ACTION_MOVE; break;
429 case 'n': command = ARCHIVE_ACTION_RENAME; break;
430 case 'd': command = ARCHIVE_ACTION_DELETE; break;
431 case 'c': command = ARCHIVE_ACTION_CLEAR; break;
433 if(action == ARCHIVE_ACTION_ADD && addMode == replace)
436 command = ARCHIVE_ACTION_SELFEXT;
441 if(action == ARCHIVE_ACTION_ADD)
447 if(action == ARCHIVE_ACTION_EXTRACT || action == ARCHIVE_ACTION_ADD)
456 if(action == ARCHIVE_ACTION_ADD &&
457 ch >= '0' && ch <= '9' && compression == 9)
458 compression = ch - '0';
473 // Validate commands needing parameters
476 case ARCHIVE_ACTION_VIEW:
480 case ARCHIVE_ACTION_EXTRACT:
486 valid = numFiles > 0;
488 case ARCHIVE_ACTION_ADD:
494 valid = numFiles > 0;
496 case ARCHIVE_ACTION_MOVE:
499 valid = numFiles > 0;
501 case ARCHIVE_ACTION_RENAME:
504 case ARCHIVE_ACTION_DELETE:
507 valid = numFiles > 0;
509 case ARCHIVE_ACTION_SELFEXT:
518 char archivePath[MAX_LOCATION];
521 sprintf(archivePath, "<%s>", argv[2]);
522 archivePathLen = strlen(archivePath);
524 if(action != ARCHIVE_ACTION_ADD)
527 if(!FileExists(argv[2]))
529 Logf($"Archive file not found: %s\n", argv[2]);
532 else if(FileGetSize(argv[2], &size) && !size)
534 Logf($"Archive file is empty: %s\n", argv[2]);
539 Archive archive = ArchiveOpen(argv[2], { false });
544 Logf($"File is not a valid ECERE archive: %s\n", argv[2]);
552 ExtractFileFromArchive(":extract.exe", argv[2]);
554 ExtractFileFromArchive(":extract", argv[2]);
555 chmod(argv[2], 0755);
561 case ARCHIVE_ACTION_VIEW:
564 ViewArchive(archivePath);
567 for(c = firstFileArg; c<numFiles + firstFileArg; c++)
569 char *name, archive[MAX_LOCATION], fileName[MAX_LOCATION];
572 strcpy(fileName, archivePath);
573 PathCat(fileName, argv[c]);
574 FileFixCase(fileName);
575 SplitArchivePath(fileName, archive, &name);
577 if((exists = FileExists(fileName)))
579 if(exists.isDirectory)
580 ViewArchive(fileName);
582 Logf("%s\n", fileName + archivePathLen);
585 Logf($"File Not Found: %s\n", name);
590 case ARCHIVE_ACTION_XTRACTALL:
591 ExtractFileFromArchive(archivePath, (argc > 3) ? argv[3] : "");
593 case ARCHIVE_ACTION_EXTRACT:
595 for(c = firstFileArg; c<firstFileArg + numFiles; c++)
597 char directory[MAX_LOCATION];
598 char fileName[MAX_LOCATION];
599 char outputFile[MAX_LOCATION] = "";
601 strcpy(fileName, archivePath);
602 strcat(fileName, argv[c]);
603 if(FileExists(fileName))
607 strcpy(outputFile, argv[argc-1]);
608 if(!strcmp(outputFile, ".")) outputFile[0] = '\0';
609 if(argv[c][0] == '/' || argv[c][0] == '\\')
611 char * afterSlash, rest[MAX_LOCATION];
612 for(afterSlash = fileName; *afterSlash == '/' || *afterSlash == '\\'; afterSlash++);
613 strcpy(rest, afterSlash);
614 PathCat(fileName, "_root");
615 PathCat(fileName, rest);
617 else if(argv[c][1] == ':')
620 sprintf(letter, "_%cdrive", toupper(argv[c][0]));
621 PathCat(outputFile, letter);
622 PathCat(outputFile, argv[c][2] ? (argv[c] + 3) : (argv[c] + 2));
626 PathCat(outputFile, argv[c]);
628 if(!strcmp(outputFile, "/") || !strcmp(outputFile, "\\"))
629 strcpy(outputFile, DIR_SEPS);
631 StripLastDirectory(outputFile, directory);
635 ExtractFileFromArchive(fileName, outputFile);
640 case ARCHIVE_ACTION_ADD:
642 Archive archive = ArchiveOpen(argv[2], { true, waitLock = true });
646 archive.totalSize = 0;
649 for(c = firstFileArg; c<firstFileArg + numFiles; c++)
651 if(!AddToArchive(archive, null, extractWhere ? argv[argc-1] : "", argv[c], addMode, compression))
657 for(c = firstFileArg; c<firstFileArg + numFiles; c++)
659 FileAttribs exists = FileExists(argv[c]);
662 ArchiveDir dir = null;
663 char file[MAX_LOCATION], directory[MAX_LOCATION] = "";
665 GetLastDirectory(argv[c], file);
668 strcpy(directory, argv[argc-1]);
669 FileGetStats(directory, &stats);
672 StripLastDirectory(argv[c], directory);
674 dir = archive.OpenDirectory(directory, extractWhere ? &stats : null, addMode);
677 if(!AddToArchive(archive, dir, file, argv[c], addMode, compression))
680 Logf($"Failed to add %s to archive!\n", argv[c]);
689 Logf($"Failed to open the internal directory of archive %s!\n", argv[2]);
698 Logf($"Failed to open archive %s for writing!\n", argv[2]);
703 case ARCHIVE_ACTION_CLEAR:
705 // Strip the archive from a file (useful for data embedded with executable code)
706 Archive archive = ArchiveOpen(argv[2], { true });
711 Logf($"Archive cleared: %s.\n", argv[2]);
715 case ARCHIVE_ACTION_DELETE:
717 Archive archive = ArchiveOpen(argv[2], { true });
720 for(c = firstFileArg; c<firstFileArg + numFiles; c++)
722 char fileName[MAX_LOCATION];
725 strcpy(fileName, archivePath);
726 PathCat(fileName, argv[c]);
727 FileFixCase(fileName);
728 exists = FileExists(fileName);
732 char file[MAX_LOCATION], directory[MAX_LOCATION] = "";
733 GetLastDirectory(argv[c], file);
734 StripLastDirectory(argv[c], directory);
735 dir = archive.OpenDirectory(directory, null, 0);
738 Logf($"Deleting file %s in directory %s.\n", file,
739 directory[0] ? directory : "root");
749 case ARCHIVE_ACTION_MOVE:
751 Archive archive = ArchiveOpen(argv[2], { true });
754 char fileName[MAX_LOCATION];
756 for(c = firstFileArg; c<firstFileArg + numFiles; c++)
758 strcpy(fileName, archivePath);
759 PathCat(fileName, argv[c]);
760 MoveFileInArchive(&archive, fileName, argv[argc-1]);
766 case ARCHIVE_ACTION_RENAME:
768 Archive archive = ArchiveOpen(argv[2], { true });
771 char fileName[MAX_LOCATION];
773 strcpy(fileName, archivePath);
774 PathCat(fileName, argv[3]);
775 exists = FileExists(fileName);
778 char newName[MAX_FILENAME] = "", rest[MAX_FILENAME];
779 SplitDirectory(argv[4], newName, rest);
783 char name[MAX_LOCATION], directory[MAX_LOCATION] = "";
785 GetLastDirectory(argv[3], name);
786 StripLastDirectory(argv[3], directory);
788 ((!newName[0] || newName[1] != ':') &&
789 newName[0] != '\\' && newName[0] != '/'))
791 char existingFilePath[MAX_LOCATION], * existingFile;
792 FileAttribs outputExists;
794 Logf($"Renaming %s in directory %s to %s.\n", name,
795 directory[0] ? directory : "root", newName);
796 strcpy(existingFilePath, archivePath);
797 existingFile = existingFilePath + strlen(existingFilePath);
798 PathCat(existingFile, directory);
799 PathCat(existingFile, newName);
801 outputExists = FileExists(existingFilePath);
802 if(outputExists.isDirectory)
805 while(listing.Find())
806 MoveFileInArchive(&archive, listing.path, existingFile);
807 dir = archive.OpenDirectory(directory, null, 0);
816 dir = archive.OpenDirectory(directory, null, 0);
821 dir.Rename(name, newName);
827 Logf($"Drive letters and %s only valid at root.\n", DIR_SEPS);
830 Log($"New name contains directory structure.\n");
836 case ARCHIVE_ACTION_SELFEXT:
840 ExtractFileFromArchive(":extract.exe", argv[3]);
842 ExtractFileFromArchive(":extract", argv[3]);
843 chmod(argv[3], 0755);
845 archive = ArchiveOpen(argv[3], { true });
848 archive.totalSize = 0;
849 AddToArchive(archive, null, "", archivePath, replace, 9);