sdk: const correctness
[sdk] / ear / cmd / ear.ec
1 #ifdef __APPLE__
2 #define __unix__
3 #endif
4
5 #if defined(__unix__) || defined(__APPLE__)
6 #define uint _uint
7 #include <sys/stat.h>
8 #undef uint
9 #endif
10
11 import "ecere"
12
13 static void ShowSyntax()
14 {
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");
32    Log($"Options:\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");
38 }
39
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
49
50 static void ViewArchive(const char * path)
51 {
52    FileListing listing { path };
53    char string[MAX_LOCATION];
54    const char * directory;
55
56    SplitArchivePath(path, string, &directory);
57
58    if(directory[0])
59    {
60       strcpy(string, directory);
61       if(!strcmp(directory, "/") || !strcmp(directory, "\\"))
62          strcpy(string, DIR_SEPS);
63       else
64          strcat(string, DIR_SEPS);
65       strcat(string, "\n");
66       Log(string);
67    }
68
69    while(listing.Find())
70    {
71       char timeString[100]; //28]; I18n strings take up more characters
72       strcpy(string, directory);
73       if(string[0])
74       {
75          if(!strcmp(directory, "/") || !strcmp(directory, "\\"))
76             strcpy(string, DIR_SEPS);
77          else
78             strcat(string, DIR_SEPS);
79       }
80       PathCat(string, listing.name);
81
82       ((DateTime)listing.stats.modified).local.OnGetString(timeString, null, null);
83
84       strcat(string, $"\n   Modified: ");
85       strcat(string, timeString);
86       strcat(string, "\n");
87
88       if(listing.stats.attribs.isDirectory)
89          ViewArchive(listing.path);
90       else
91          Log(string);
92    }
93 }
94 #define BUFFERSIZE 0x10000
95
96 static void ExtractFileFromArchive(const char * path, const char * outputFile)
97 {
98    char fileName[MAX_LOCATION];
99    FileAttribs exists = FileExists(path);
100    bool setTime = false;
101    FileStats stats;
102
103    if(exists.isDirectory)
104    {
105       FileListing listing { path };
106
107       if(outputFile[0])
108       {
109          if(MakeDir(outputFile))
110          {
111             setTime = true;
112             FileGetStats(path, &stats);
113          }
114       }
115
116       while(listing.Find())
117       {
118          strcpy(fileName, outputFile);
119
120          // Tweak file name if out
121          if(outputFile[0])
122          {
123             if(!strcmp(fileName, ".")) fileName[0] = '\0';
124             if(listing.name[0] == '/' || listing.name[0] == '\\')
125             {
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);
131             }
132             else if(listing.name[1] == ':')
133             {
134                char letter[10];
135                sprintf(letter, "_%cdrive", toupper(listing.name[0]));
136                PathCat(fileName, letter);
137                PathCat(fileName, listing.name[2] ? (listing.name + 3) : (listing.name + 2));
138             }
139             else
140                PathCat(fileName, listing.name);
141          }
142          else
143             PathCat(fileName, listing.name);
144          if(!strcmp(fileName, "/") || !strcmp(fileName, "\\"))
145             strcpy(fileName, DIR_SEPS);
146          ExtractFileFromArchive(listing.path, fileName);
147       }
148    }
149    else if(exists)
150    {
151       File input = FileOpen(path, read);
152       if(input)
153       {
154          File output = FileOpen(outputFile, write);
155          if(output)
156          {
157             FileSize dataSize, c;
158             static byte buffer[BUFFERSIZE];
159             FileGetSize(path, &dataSize);
160             if(!quiet)
161                Logf($"Extracting %s...\n", outputFile);
162             for(c = 0; c<dataSize; c += BUFFERSIZE)
163             {
164                uint size = (dataSize > c + BUFFERSIZE) ? BUFFERSIZE : (dataSize - c);
165                input.Read(buffer, 1, size);
166                output.Write(buffer, 1, size);
167             }
168             delete output;
169             setTime = true;
170             FileGetStats(path, &stats);
171          }
172          delete input;
173       }
174    }
175    if(setTime)
176       FileSetTime(outputFile, stats.created, 0, stats.modified);
177 }
178
179 static bool AddToArchive(Archive archive, ArchiveDir parentDir, const char * name, const char * path, ArchiveAddMode addMode, int compression)
180 {
181    bool result = true;
182    FileAttribs exists = FileExists(path);
183    if(exists.isDirectory)
184    {
185       ArchiveDir directory;
186       if(name[0] || !parentDir)
187       {
188          FileStats stats;
189          FileGetStats(path, &stats);
190          if(parentDir)
191             directory = parentDir.OpenDirectory(name, &stats, addMode);
192          else
193             directory = archive.OpenDirectory(name, &stats, addMode);
194       }
195       else
196          directory = parentDir;
197       if(directory)
198       {
199          FileListing listing { path };
200          while(listing.Find())
201          {
202             if(!AddToArchive(archive, directory, listing.name, listing.path, addMode, compression))
203             {
204                result = false;
205                break;
206             }
207          }
208          if(directory != parentDir)
209             delete directory;
210       }
211    }
212    else if(exists)
213    {
214       int ratio;
215       uint newPosition;
216       if(!quiet)
217          Logf($"Adding %s...", name);
218       if(parentDir.Add(name, path, addMode, compression, &ratio, &newPosition))
219       {
220          if(newPosition)
221          {
222             if(ratio && !quiet)
223                Logf("(%2d.%1d%%)", ratio / 10, ratio % 10);
224             if(!quiet)
225                Log("\n");
226          }
227          else
228             Logf($"Skipped%s%s.\n", quiet ? " " : "", quiet ? name : "");
229       }
230       else
231       {
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;
234          result = false;
235       }
236    }
237    return result;
238 }
239
240 static void MoveFileInArchive(Archive* archive, const char * sourcePath, const char * outputDirectory)
241 {
242    // Verify if source file/directory exists and figure its kind
243    FileAttribs exists = FileExists(sourcePath);
244    if(exists)
245    {
246       char sourceFileName[MAX_FILENAME], sourceDirectory[MAX_LOCATION];
247       char archiveName[MAX_LOCATION];
248       const char * source;
249       char existingFilePath[MAX_LOCATION], * existingFile;
250       bool rootMoving = false;
251       FileAttribs outputExists;
252       bool doMove = false;
253
254       SplitArchivePath(sourcePath, archiveName, &source);
255
256       GetLastDirectory(source, sourceFileName);
257       StripLastDirectory(source, sourceDirectory);
258
259       sprintf(existingFilePath, "<%s>", archiveName);
260       existingFile = existingFilePath + strlen(existingFilePath);
261       PathCat(existingFile, outputDirectory);
262
263       if(!sourceDirectory[0] &&
264          ((sourceFileName[0] && sourceFileName[1] == ':') ||
265          sourceFileName[0] == '\\' || sourceFileName[0] == '/'))
266          rootMoving = true;
267       else
268          PathCat(existingFile, sourceFileName);
269
270       if(rootMoving || fstrcmp(outputDirectory, sourceDirectory))
271       {
272          // If directory exists in destination directory, move files and then delete
273          outputExists = FileExists(existingFilePath);
274
275          // If source is a directory
276          if(exists.isDirectory)
277          {
278             // Check if destination directory is within the source directory
279             bool within = true;
280             char outputPart[MAX_FILENAME], outputRest[MAX_LOCATION];
281             char sourcePart[MAX_FILENAME], sourceRest[MAX_LOCATION];
282
283             strcpy(outputRest, outputDirectory);
284             strcpy(sourceRest, source);
285
286             for(;sourceRest[0];)
287             {
288                SplitDirectory(outputRest, outputPart, outputRest);
289                SplitDirectory(sourceRest, sourcePart, sourceRest);
290                if(fstrcmp(sourcePart, outputPart) || !outputPart[0])
291                {
292                   within = false;
293                   break;
294                }
295             }
296
297             // Proceed with the move
298             if(!within || !source[0])
299             {
300                // If directory exists in destination directory, move files and then delete
301                if(outputExists.isDirectory || rootMoving || !source[0])
302                {
303                   ArchiveDir input;
304                   FileListing listing { sourcePath };
305
306                   if(!source[0])
307                   {
308                      ArchiveDir dir;
309                      Logf($"Moving files in root to %s.\n", outputDirectory[0] ? outputDirectory : "root");
310                      dir = archive->OpenDirectory(outputDirectory, null, 0);
311                      if(dir)
312                      {
313                         char archiveName[MAX_LOCATION];
314                         const char * archiveFile;
315                         delete dir;
316                         delete *archive;
317                         SplitArchivePath(sourcePath, archiveName, &archiveFile);
318                         *archive = ArchiveOpen(archiveName, { true });
319                      }
320                   }
321                   else if(!rootMoving)
322                      Logf($"Merging directory %s in %s with %s in %s.\n",
323                         sourceFileName,
324                         sourceDirectory[0] ? sourceDirectory : "root",
325                         sourceFileName,
326                         outputDirectory[0] ? outputDirectory : "root");
327
328                   while(listing.Find())
329                   {
330                      if(strcmp(listing.path, existingFilePath))
331                         MoveFileInArchive(archive, listing.path, existingFile);
332                   }
333
334                   if(source[0])
335                   {
336                      input = archive->OpenDirectory(sourceDirectory, null, 0);
337                      if(input)
338                      {
339                         input.Delete(sourceFileName);
340                         delete input;
341                      }
342                   }
343                }
344                else if(outputExists)
345                   Logf($"A file with the same name already exists (%s).\n", existingFile);
346                else
347                   // Perform operation
348                   doMove = true;
349             }
350             else
351                Logf($"Can't move directory %s inside itself.\n", source);
352          }
353          // If source is a file
354          else if(outputExists.isDirectory)
355             Logf($"A folder with the same name already exists (%s).\n", existingFile);
356          else
357             doMove = true;
358
359          if(doMove)
360          {
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);
364             if(output)
365             {
366                ArchiveDir input = archive->OpenDirectory(sourceDirectory, null, 0);
367                if(input)
368                {
369                   // If a file by the same name exists already, replace it
370                   if(outputExists.isFile)
371                      output.Delete(sourceFileName);
372
373                   Logf($"Moving file %s in directory %s to %s.\n", sourceFileName,
374                      sourceDirectory[0] ? sourceDirectory : "root",
375                      outputDirectory[0] ? outputDirectory : "root");
376
377                   // Perform operation
378                   input.Move(sourceFileName, output);
379
380                   delete input;
381                }
382                delete output;
383             }
384          }
385       }
386       else
387          Logf($"File is already in directory \"%s\".\n",
388             sourceDirectory[0] ? sourceDirectory : "root");
389    }
390 }
391
392 static bool quiet;
393
394 class EARApp : Application
395 {
396    void Main()
397    {
398       bool valid = false;
399       int numFiles = 0;
400       int firstFileArg = 0;
401       int action = 0;
402       bool extractWhere = false;
403       bool addFolders = false;
404       bool selfExtract = false;
405       int compression = 9;
406       ArchiveAddMode addMode = 0;
407       int c;
408
409       quiet = false;
410
411       DumpErrors(false);
412       SetLoggingMode(stdOut, null);
413
414       if(argc > 2)
415       {
416          // First validate command / options
417          char ch;
418
419          valid = true;
420          for(c = 0; (ch = (char)tolower(argv[1][c])) && valid; c++)
421          {
422             int command = 0;
423             switch(ch)
424             {
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;
435                case 's':
436                   if(action == ARCHIVE_ACTION_ADD && addMode == replace)
437                      selfExtract = true;
438                   else if(!action)
439                      command = ARCHIVE_ACTION_SELFEXT;
440                   else
441                      valid = true;
442                   break;
443                case 'f':
444                   if(action == ARCHIVE_ACTION_ADD)
445                      addFolders = true;
446                   else
447                      valid = false;
448                   break;
449                case 'w':
450                   if(action == ARCHIVE_ACTION_EXTRACT || action == ARCHIVE_ACTION_ADD)
451                      extractWhere = true;
452                   else
453                      valid = false;
454                   break;
455                case 'q':
456                   quiet = true;
457                   break;
458                default:
459                   if(action == ARCHIVE_ACTION_ADD &&
460                      ch >= '0' && ch <= '9' && compression == 9)
461                      compression = ch - '0';
462                   else
463                      valid = false;
464             }
465             if(command)
466             {
467                if(action)
468                   valid = false;
469                else
470                   action = command;
471             }
472          }
473
474          if(valid)
475          {
476             // Validate commands needing parameters
477             switch(action)
478             {
479                case ARCHIVE_ACTION_VIEW:
480                   numFiles = argc - 3;
481                   firstFileArg = 3;
482                   break;
483                case ARCHIVE_ACTION_EXTRACT:
484                   firstFileArg = 3;
485                   if(extractWhere)
486                      numFiles = argc - 4;
487                   else
488                      numFiles = argc - 3;
489                   valid = numFiles > 0;
490                   break;
491                case ARCHIVE_ACTION_ADD:
492                   firstFileArg = 3;
493                   if(extractWhere)
494                      numFiles = argc - 4;
495                   else
496                      numFiles = argc - 3;
497                   valid = numFiles > 0;
498                   break;
499                case ARCHIVE_ACTION_MOVE:
500                   firstFileArg = 3;
501                   numFiles = argc - 4;
502                   valid = numFiles > 0;
503                   break;
504                case ARCHIVE_ACTION_RENAME:
505                   valid = argc > 4;
506                   break;
507                case ARCHIVE_ACTION_DELETE:
508                   firstFileArg = 3;
509                   numFiles = argc - 3;
510                   valid = numFiles > 0;
511                   break;
512                case ARCHIVE_ACTION_SELFEXT:
513                   valid = argc > 3;
514                   break;
515             }
516          }
517       }
518
519       if(valid)
520       {
521          char archivePath[MAX_LOCATION];
522          int archivePathLen;
523
524          sprintf(archivePath, "<%s>", argv[2]);
525          archivePathLen = strlen(archivePath);
526
527          if(action != ARCHIVE_ACTION_ADD)
528          {
529             FileSize size;
530             if(!FileExists(argv[2]))
531             {
532                Logf($"Archive file not found: %s\n", argv[2]);
533                action = 0;
534             }
535             else if(FileGetSize(argv[2], &size) && !size)
536             {
537                Logf($"Archive file is empty: %s\n", argv[2]);
538                action = 0;
539             }
540             else
541             {
542                Archive archive = ArchiveOpen(argv[2], { false });
543                if(archive)
544                   delete archive;
545                else
546                {
547                   Logf($"File is not a valid ECERE archive: %s\n", argv[2]);
548                   action = 0;
549                }
550             }
551          }
552          else if(selfExtract)
553          {
554    #ifdef __WIN32__
555             ExtractFileFromArchive(":extract.exe", argv[2]);
556    #else
557             ExtractFileFromArchive(":extract", argv[2]);
558             chmod(argv[2], 0755);
559    #endif
560          }
561
562          switch(action)
563          {
564             case ARCHIVE_ACTION_VIEW:
565             {
566                if(!numFiles)
567                   ViewArchive(archivePath);
568                else
569                {
570                   for(c = firstFileArg; c<numFiles + firstFileArg; c++)
571                   {
572                      char archive[MAX_LOCATION], fileName[MAX_LOCATION];
573                      const char * name;
574                      FileAttribs exists;
575
576                      strcpy(fileName, archivePath);
577                      PathCat(fileName, argv[c]);
578                      FileFixCase(fileName);
579                      SplitArchivePath(fileName, archive, &name);
580
581                      if((exists = FileExists(fileName)))
582                      {
583                         if(exists.isDirectory)
584                            ViewArchive(fileName);
585                         else
586                            Logf("%s\n", fileName + archivePathLen);
587                      }
588                      else
589                         Logf($"File Not Found: %s\n", name);
590                   }
591                }
592                break;
593             }
594             case ARCHIVE_ACTION_XTRACTALL:
595                ExtractFileFromArchive(archivePath, (argc > 3) ? argv[3] : "");
596                break;
597             case ARCHIVE_ACTION_EXTRACT:
598             {
599                for(c = firstFileArg; c<firstFileArg + numFiles; c++)
600                {
601                   char directory[MAX_LOCATION];
602                   char fileName[MAX_LOCATION];
603                   char outputFile[MAX_LOCATION] = "";
604
605                   strcpy(fileName, archivePath);
606                   strcat(fileName, argv[c]);
607                   if(FileExists(fileName))
608                   {
609                      if(extractWhere)
610                      {
611                         strcpy(outputFile, argv[argc-1]);
612                         if(!strcmp(outputFile, ".")) outputFile[0] = '\0';
613                         if(argv[c][0] == '/' || argv[c][0] == '\\')
614                         {
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);
620                         }
621                         else if(argv[c][1] == ':')
622                         {
623                            char letter[10];
624                            sprintf(letter, "_%cdrive", toupper(argv[c][0]));
625                            PathCat(outputFile, letter);
626                            PathCat(outputFile, argv[c][2] ? (argv[c] + 3) : (argv[c] + 2));
627                         }
628                      }
629                      else
630                         PathCat(outputFile, argv[c]);
631
632                      if(!strcmp(outputFile, "/") || !strcmp(outputFile, "\\"))
633                         strcpy(outputFile, DIR_SEPS);
634
635                      StripLastDirectory(outputFile, directory);
636
637                      MakeDir(directory);
638
639                      ExtractFileFromArchive(fileName, outputFile);
640                   }
641                }
642                break;
643             }
644             case ARCHIVE_ACTION_ADD:
645             {
646                Archive archive = ArchiveOpen(argv[2], { true, waitLock = true });
647                if(archive)
648                {
649                   if(selfExtract)
650                      archive.totalSize = 0;
651                   if(addFolders)
652                   {
653                      for(c = firstFileArg; c<firstFileArg + numFiles; c++)
654                      {
655                         if(!AddToArchive(archive, null, extractWhere ? argv[argc-1] : "", argv[c], addMode, compression))
656                            break;
657                      }
658                   }
659                   else
660                   {
661                      for(c = firstFileArg; c<firstFileArg + numFiles; c++)
662                      {
663                         FileAttribs exists = FileExists(argv[c]);
664                         if(exists)
665                         {
666                            ArchiveDir dir = null;
667                            char file[MAX_LOCATION], directory[MAX_LOCATION] = "";
668                            FileStats stats;
669                            GetLastDirectory(argv[c], file);
670                            if(extractWhere)
671                            {
672                               strcpy(directory, argv[argc-1]);
673                               FileGetStats(directory, &stats);
674                            }
675                            else
676                               StripLastDirectory(argv[c], directory);
677
678                            dir = archive.OpenDirectory(directory, extractWhere ? &stats : null, addMode);
679                            if(dir)
680                            {
681                               if(!AddToArchive(archive, dir, file, argv[c], addMode, compression))
682                               {
683                                  exitCode = 2;
684                                  Logf($"Failed to add %s to archive!\n", argv[c]);
685                                  delete dir;
686                                  break;
687                               }
688                               delete dir;
689                            }
690                            else
691                            {
692                               exitCode = 3;
693                               Logf($"Failed to open the internal directory of archive %s!\n", argv[2]);
694                            }
695                         }
696                      }
697                   }
698                   delete archive;
699                }
700                else
701                {
702                   Logf($"Failed to open archive %s for writing!\n", argv[2]);
703                   exitCode = 4;
704                }
705                break;
706             }
707             case ARCHIVE_ACTION_CLEAR:
708             {
709                // Strip the archive from a file (useful for data embedded with executable code)
710                Archive archive = ArchiveOpen(argv[2], { true });
711                if(archive)
712                {
713                   archive.Clear();
714                   delete archive;
715                   Logf($"Archive cleared: %s.\n", argv[2]);
716                }
717                break;
718             }
719             case ARCHIVE_ACTION_DELETE:
720             {
721                Archive archive = ArchiveOpen(argv[2], { true });
722                if(archive)
723                {
724                   for(c = firstFileArg; c<firstFileArg + numFiles; c++)
725                   {
726                      char fileName[MAX_LOCATION];
727                      FileAttribs exists;
728
729                      strcpy(fileName, archivePath);
730                      PathCat(fileName, argv[c]);
731                      FileFixCase(fileName);
732                      exists = FileExists(fileName);
733                      if(exists)
734                      {
735                         ArchiveDir dir;
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);
740                         if(dir)
741                         {
742                            Logf($"Deleting file %s in directory %s.\n", file,
743                               directory[0] ? directory : "root");
744                            dir.Delete(file);
745                            delete dir;
746                         }
747                      }
748                   }
749                   delete archive;
750                }
751                break;
752             }
753             case ARCHIVE_ACTION_MOVE:
754             {
755                Archive archive = ArchiveOpen(argv[2], { true });
756                if(archive)
757                {
758                   char fileName[MAX_LOCATION];
759
760                   for(c = firstFileArg; c<firstFileArg + numFiles; c++)
761                   {
762                      strcpy(fileName, archivePath);
763                      PathCat(fileName, argv[c]);
764                      MoveFileInArchive(&archive, fileName, argv[argc-1]);
765                   }
766                   delete archive;
767                }
768                break;
769             }
770             case ARCHIVE_ACTION_RENAME:
771             {
772                Archive archive = ArchiveOpen(argv[2], { true });
773                if(archive)
774                {
775                   char fileName[MAX_LOCATION];
776                   FileAttribs exists;
777                   strcpy(fileName, archivePath);
778                   PathCat(fileName, argv[3]);
779                   exists = FileExists(fileName);
780                   if(exists)
781                   {
782                      char newName[MAX_FILENAME] = "", rest[MAX_FILENAME];
783                      SplitDirectory(argv[4], newName, rest);
784                      if(!rest[0])
785                      {
786                         ArchiveDir dir;
787                         char name[MAX_LOCATION], directory[MAX_LOCATION] = "";
788
789                         GetLastDirectory(argv[3], name);
790                         StripLastDirectory(argv[3], directory);
791                         if(!directory[0] ||
792                            ((!newName[0] || newName[1] != ':') &&
793                             newName[0] != '\\' && newName[0] != '/'))
794                         {
795                            char existingFilePath[MAX_LOCATION], * existingFile;
796                            FileAttribs outputExists;
797
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);
804
805                            outputExists = FileExists(existingFilePath);
806                            if(outputExists.isDirectory)
807                            {
808                               FileListing listing;
809                               while(listing.Find())
810                                  MoveFileInArchive(&archive, listing.path, existingFile);
811                               dir = archive.OpenDirectory(directory, null, 0);
812                               if(dir)
813                               {
814                                  dir.Delete(name);
815                                  delete dir;
816                               }
817                            }
818                            else
819                            {
820                               dir = archive.OpenDirectory(directory, null, 0);
821                               if(dir)
822                               {
823                                  if(outputExists)
824                                     dir.Delete(newName);
825                                  dir.Rename(name, newName);
826                                  delete dir;
827                               }
828                            }
829                         }
830                         else
831                            Logf($"Drive letters and %s only valid at root.\n", DIR_SEPS);
832                      }
833                      else
834                         Log($"New name contains directory structure.\n");
835                   }
836                   delete archive;
837                }
838                break;
839             }
840             case ARCHIVE_ACTION_SELFEXT:
841             {
842                Archive archive;
843    #ifdef __WIN32__
844                ExtractFileFromArchive(":extract.exe", argv[3]);
845    #else
846                ExtractFileFromArchive(":extract", argv[3]);
847                chmod(argv[3], 0755);
848    #endif
849                archive = ArchiveOpen(argv[3], { true });
850                if(archive)
851                {
852                   archive.totalSize = 0;
853                   AddToArchive(archive, null, "", archivePath, replace, 9);
854                   delete archive;
855                }
856                break;
857             }
858          }
859       }
860       else
861          ShowSyntax();
862    }
863 }