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