1// ArchiveCommandLine.cpp
2
3#include "StdAfx.h"
4#undef printf
5#undef sprintf
6
7#ifdef _WIN32
8#ifndef UNDER_CE
9#include <io.h>
10#endif
11#endif
12#include <stdio.h>
13
14#include "../../../Common/ListFileUtils.h"
15#include "../../../Common/StringConvert.h"
16#include "../../../Common/StringToInt.h"
17
18#include "../../../Windows/FileDir.h"
19#include "../../../Windows/FileName.h"
20#ifdef _WIN32
21#include "../../../Windows/FileMapping.h"
22#include "../../../Windows/Synchronization.h"
23#endif
24
25#include "ArchiveCommandLine.h"
26#include "EnumDirItems.h"
27#include "SortUtils.h"
28#include "Update.h"
29#include "UpdateAction.h"
30
31extern bool g_CaseSensitive;
32
33#ifdef UNDER_CE
34
35#define MY_IS_TERMINAL(x) false;
36
37#else
38
39#if _MSC_VER >= 1400
40#define MY_isatty_fileno(x) _isatty(_fileno(x))
41#else
42#define MY_isatty_fileno(x) isatty(fileno(x))
43#endif
44
45#define MY_IS_TERMINAL(x) (MY_isatty_fileno(x) != 0);
46
47#endif
48
49using namespace NCommandLineParser;
50using namespace NWindows;
51using namespace NFile;
52
53static bool StringToUInt32(const wchar_t *s, UInt32 &v)
54{
55  if (*s == 0)
56    return false;
57  const wchar_t *end;
58  v = ConvertStringToUInt32(s, &end);
59  return *end == 0;
60}
61
62CArcCmdLineException::CArcCmdLineException(const char *a, const wchar_t *u)
63{
64  (*this) += MultiByteToUnicodeString(a);
65  if (u)
66  {
67    this->Add_LF();
68    (*this) += u;
69  }
70}
71
72int g_CodePage = -1;
73
74namespace NKey {
75enum Enum
76{
77  kHelp1 = 0,
78  kHelp2,
79  kHelp3,
80
81  kDisableHeaders,
82  kDisablePercents,
83  kShowTime,
84  kLogLevel,
85
86  kOutStream,
87  kErrStream,
88  kPercentStream,
89
90  kYes,
91
92  kShowDialog,
93  kOverwrite,
94
95  kArchiveType,
96  kExcludedArcType,
97
98  kProperty,
99  kOutputDir,
100  kWorkingDir,
101
102  kInclude,
103  kExclude,
104  kArInclude,
105  kArExclude,
106  kNoArName,
107
108  kUpdate,
109  kVolume,
110  kRecursed,
111
112  kAffinity,
113  kSfx,
114  kEmail,
115  kHash,
116
117  kStdIn,
118  kStdOut,
119
120  kLargePages,
121  kListfileCharSet,
122  kConsoleCharSet,
123  kTechMode,
124
125  kShareForWrite,
126  kCaseSensitive,
127  kArcNameMode,
128
129  kDisableWildcardParsing,
130  kElimDup,
131  kFullPathMode,
132
133  kHardLinks,
134  kSymLinks,
135  kNtSecurity,
136  kAltStreams,
137  kReplaceColonForAltStream,
138  kWriteToAltStreamIfColon,
139
140  kDeleteAfterCompressing,
141  kSetArcMTime
142
143  #ifndef _NO_CRYPTO
144  , kPassword
145  #endif
146};
147
148}
149
150
151static const wchar_t kRecursedIDChar = 'r';
152static const char *kRecursedPostCharSet = "0-";
153
154static const char *k_ArcNameMode_PostCharSet = "sea";
155
156static const char *k_Stream_PostCharSet = "012";
157
158static inline const EArcNameMode ParseArcNameMode(int postCharIndex)
159{
160  switch (postCharIndex)
161  {
162    case 1: return k_ArcNameMode_Exact;
163    case 2: return k_ArcNameMode_Add;
164    default: return k_ArcNameMode_Smart;
165  }
166}
167
168namespace NRecursedPostCharIndex {
169  enum EEnum
170  {
171    kWildcardRecursionOnly = 0,
172    kNoRecursion = 1
173  };
174}
175
176static const char kImmediateNameID = '!';
177static const char kMapNameID = '#';
178static const char kFileListID = '@';
179
180static const char kSomeCludePostStringMinSize = 2; // at least <@|!><N>ame must be
181static const char kSomeCludeAfterRecursedPostStringMinSize = 2; // at least <@|!><N>ame must be
182
183static const char *kOverwritePostCharSet = "asut";
184
185static const NExtract::NOverwriteMode::EEnum k_OverwriteModes[] =
186{
187  NExtract::NOverwriteMode::kOverwrite,
188  NExtract::NOverwriteMode::kSkip,
189  NExtract::NOverwriteMode::kRename,
190  NExtract::NOverwriteMode::kRenameExisting
191};
192
193static const CSwitchForm kSwitchForms[] =
194{
195  { "?" },
196  { "h" },
197  { "-help" },
198
199  { "ba" },
200  { "bd" },
201  { "bt" },
202  { "bb", NSwitchType::kString, false, 0 },
203
204  { "bso", NSwitchType::kChar, false, 1, k_Stream_PostCharSet },
205  { "bse", NSwitchType::kChar, false, 1, k_Stream_PostCharSet },
206  { "bsp", NSwitchType::kChar, false, 1, k_Stream_PostCharSet },
207
208  { "y" },
209
210  { "ad" },
211  { "ao", NSwitchType::kChar, false, 1, kOverwritePostCharSet},
212
213  { "t",  NSwitchType::kString, false, 1 },
214  { "stx", NSwitchType::kString, true, 1 },
215
216  { "m",  NSwitchType::kString, true, 1 },
217  { "o",  NSwitchType::kString, false, 1 },
218  { "w",  NSwitchType::kString },
219
220  { "i",  NSwitchType::kString, true, kSomeCludePostStringMinSize},
221  { "x",  NSwitchType::kString, true, kSomeCludePostStringMinSize},
222  { "ai", NSwitchType::kString, true, kSomeCludePostStringMinSize},
223  { "ax", NSwitchType::kString, true, kSomeCludePostStringMinSize},
224  { "an" },
225
226  { "u",  NSwitchType::kString, true, 1},
227  { "v",  NSwitchType::kString, true, 1},
228  { "r",  NSwitchType::kChar, false, 0, kRecursedPostCharSet },
229
230  { "stm", NSwitchType::kString },
231  { "sfx", NSwitchType::kString },
232  { "seml", NSwitchType::kString, false, 0},
233  { "scrc", NSwitchType::kString, true, 0 },
234
235  { "si", NSwitchType::kString },
236  { "so" },
237
238  { "slp", NSwitchType::kMinus },
239  { "scs", NSwitchType::kString },
240  { "scc", NSwitchType::kString },
241  { "slt" },
242
243  { "ssw" },
244  { "ssc", NSwitchType::kMinus },
245  { "sa",  NSwitchType::kChar, false, 1, k_ArcNameMode_PostCharSet },
246
247  { "spd" },
248  { "spe", NSwitchType::kMinus },
249  { "spf", NSwitchType::kString, false, 0 },
250
251  { "snh", NSwitchType::kMinus },
252  { "snl", NSwitchType::kMinus },
253  { "sni" },
254  { "sns", NSwitchType::kMinus },
255  { "snr" },
256  { "snc" },
257
258  { "sdel" },
259  { "stl" }
260
261  #ifndef _NO_CRYPTO
262  , { "p",  NSwitchType::kString }
263  #endif
264};
265
266static const wchar_t *kUniversalWildcard = L"*";
267static const unsigned kMinNonSwitchWords = 1;
268static const unsigned kCommandIndex = 0;
269
270// static const char *kUserErrorMessage  = "Incorrect command line";
271static const char *kCannotFindListFile = "Cannot find listfile";
272static const char *kIncorrectListFile = "Incorrect item in listfile.\nCheck charset encoding and -scs switch.";
273static const char *kTerminalOutError = "I won't write compressed data to a terminal";
274static const char *kSameTerminalError = "I won't write data and program's messages to same stream";
275static const char *kEmptyFilePath = "Empty file path";
276static const char *kCannotFindArchive = "Cannot find archive";
277
278bool CArcCommand::IsFromExtractGroup() const
279{
280  switch (CommandType)
281  {
282    case NCommandType::kTest:
283    case NCommandType::kExtract:
284    case NCommandType::kExtractFull:
285      return true;
286  }
287  return false;
288}
289
290NExtract::NPathMode::EEnum CArcCommand::GetPathMode() const
291{
292  switch (CommandType)
293  {
294    case NCommandType::kTest:
295    case NCommandType::kExtractFull:
296      return NExtract::NPathMode::kFullPaths;
297  }
298  return NExtract::NPathMode::kNoPaths;
299}
300
301bool CArcCommand::IsFromUpdateGroup() const
302{
303  switch (CommandType)
304  {
305    case NCommandType::kAdd:
306    case NCommandType::kUpdate:
307    case NCommandType::kDelete:
308    case NCommandType::kRename:
309      return true;
310  }
311  return false;
312}
313
314static NRecursedType::EEnum GetRecursedTypeFromIndex(int index)
315{
316  switch (index)
317  {
318    case NRecursedPostCharIndex::kWildcardRecursionOnly:
319      return NRecursedType::kWildcardOnlyRecursed;
320    case NRecursedPostCharIndex::kNoRecursion:
321      return NRecursedType::kNonRecursed;
322    default:
323      return NRecursedType::kRecursed;
324  }
325}
326
327static const char *g_Commands = "audtexlbih";
328
329static bool ParseArchiveCommand(const UString &commandString, CArcCommand &command)
330{
331  UString s = commandString;
332  s.MakeLower_Ascii();
333  if (s.Len() == 1)
334  {
335    if (s[0] > 0x7F)
336      return false;
337    int index = FindCharPosInString(g_Commands, (char)s[0]);
338    if (index < 0)
339      return false;
340    command.CommandType = (NCommandType::EEnum)index;
341    return true;
342  }
343  if (s.Len() == 2 && s[0] == 'r' && s[1] == 'n')
344  {
345    command.CommandType = (NCommandType::kRename);
346    return true;
347  }
348  return false;
349}
350
351// ------------------------------------------------------------------
352// filenames functions
353
354static void AddNameToCensor(NWildcard::CCensor &censor,
355    const UString &name, bool include, NRecursedType::EEnum type, bool wildcardMatching)
356{
357  bool recursed = false;
358
359  switch (type)
360  {
361    case NRecursedType::kWildcardOnlyRecursed:
362      recursed = DoesNameContainWildcard(name);
363      break;
364    case NRecursedType::kRecursed:
365      recursed = true;
366      break;
367  }
368  censor.AddPreItem(include, name, recursed, wildcardMatching);
369}
370
371static void AddRenamePair(CObjectVector<CRenamePair> *renamePairs,
372    const UString &oldName, const UString &newName, NRecursedType::EEnum type,
373    bool wildcardMatching)
374{
375  CRenamePair &pair = renamePairs->AddNew();
376  pair.OldName = oldName;
377  pair.NewName = newName;
378  pair.RecursedType = type;
379  pair.WildcardParsing = wildcardMatching;
380
381  if (!pair.Prepare())
382  {
383    UString val;
384    val += pair.OldName;
385    val.Add_LF();
386    val += pair.NewName;
387    val.Add_LF();
388    if (type == NRecursedType::kRecursed)
389      val.AddAscii("-r");
390    else if (type == NRecursedType::kWildcardOnlyRecursed)
391      val.AddAscii("-r0");
392    throw CArcCmdLineException("Unsupported rename command:", val);
393  }
394}
395
396static void AddToCensorFromListFile(
397    CObjectVector<CRenamePair> *renamePairs,
398    NWildcard::CCensor &censor,
399    LPCWSTR fileName, bool include, NRecursedType::EEnum type, bool wildcardMatching, Int32 codePage)
400{
401  UStringVector names;
402  if (!NFind::DoesFileExist(us2fs(fileName)))
403    throw CArcCmdLineException(kCannotFindListFile, fileName);
404  if (!ReadNamesFromListFile(us2fs(fileName), names, codePage))
405    throw CArcCmdLineException(kIncorrectListFile, fileName);
406  if (renamePairs)
407  {
408    if ((names.Size() & 1) != 0)
409      throw CArcCmdLineException(kIncorrectListFile, fileName);
410    for (unsigned i = 0; i < names.Size(); i += 2)
411    {
412      // change type !!!!
413      AddRenamePair(renamePairs, names[i], names[i + 1], type, wildcardMatching);
414    }
415  }
416  else
417    FOR_VECTOR (i, names)
418      AddNameToCensor(censor, names[i], include, type, wildcardMatching);
419}
420
421static void AddToCensorFromNonSwitchesStrings(
422    CObjectVector<CRenamePair> *renamePairs,
423    unsigned startIndex,
424    NWildcard::CCensor &censor,
425    const UStringVector &nonSwitchStrings, NRecursedType::EEnum type,
426    bool wildcardMatching,
427    bool thereAreSwitchIncludes, Int32 codePage)
428{
429  if ((renamePairs || nonSwitchStrings.Size() == startIndex) && !thereAreSwitchIncludes)
430    AddNameToCensor(censor, kUniversalWildcard, true, type,
431        true // wildcardMatching
432        );
433
434  int oldIndex = -1;
435
436  for (unsigned i = startIndex; i < nonSwitchStrings.Size(); i++)
437  {
438    const UString &s = nonSwitchStrings[i];
439    if (s.IsEmpty())
440      throw CArcCmdLineException(kEmptyFilePath);
441    if (s[0] == kFileListID)
442      AddToCensorFromListFile(renamePairs, censor, s.Ptr(1), true, type, wildcardMatching, codePage);
443    else if (renamePairs)
444    {
445      if (oldIndex == -1)
446        oldIndex = i;
447      else
448      {
449        // NRecursedType::EEnum type is used for global wildcard (-i! switches)
450        AddRenamePair(renamePairs, nonSwitchStrings[oldIndex], s, NRecursedType::kNonRecursed, wildcardMatching);
451        // AddRenamePair(renamePairs, nonSwitchStrings[oldIndex], s, type);
452        oldIndex = -1;
453      }
454    }
455    else
456      AddNameToCensor(censor, s, true, type, wildcardMatching);
457  }
458
459  if (oldIndex != -1)
460  {
461    throw CArcCmdLineException("There is no second file name for rename pair:", nonSwitchStrings[oldIndex]);
462  }
463}
464
465#ifdef _WIN32
466
467struct CEventSetEnd
468{
469  UString Name;
470
471  CEventSetEnd(const wchar_t *name): Name(name) {}
472  ~CEventSetEnd()
473  {
474    NSynchronization::CManualResetEvent event;
475    if (event.Open(EVENT_MODIFY_STATE, false, GetSystemString(Name)) == 0)
476      event.Set();
477  }
478};
479
480const char *k_IncorrectMapCommand = "Incorrect Map command";
481
482static const char *ParseMapWithPaths(
483    NWildcard::CCensor &censor,
484    const UString &s2, bool include,
485    NRecursedType::EEnum commonRecursedType,
486    bool wildcardMatching)
487{
488  UString s = s2;
489  int pos = s.Find(L':');
490  if (pos < 0)
491    return k_IncorrectMapCommand;
492  int pos2 = s.Find(L':', pos + 1);
493  if (pos2 < 0)
494    return k_IncorrectMapCommand;
495
496  CEventSetEnd eventSetEnd((const wchar_t *)s + ((unsigned)pos2 + 1));
497  s.DeleteFrom(pos2);
498  UInt32 size;
499  if (!StringToUInt32(s.Ptr(pos + 1), size)
500      || size < sizeof(wchar_t)
501      || size > ((UInt32)1 << 31)
502      || size % sizeof(wchar_t) != 0)
503    return "Unsupported Map data size";
504
505  s.DeleteFrom(pos);
506  CFileMapping map;
507  if (map.Open(FILE_MAP_READ, GetSystemString(s)) != 0)
508    return "Can not open mapping";
509  LPVOID data = map.Map(FILE_MAP_READ, 0, size);
510  if (!data)
511    return "MapViewOfFile error";
512  CFileUnmapper unmapper(data);
513
514  UString name;
515  const wchar_t *p = (const wchar_t *)data;
516  if (*p != 0) // data format marker
517    return "Unsupported Map data";
518  UInt32 numChars = size / sizeof(wchar_t);
519  for (UInt32 i = 1; i < numChars; i++)
520  {
521    wchar_t c = p[i];
522    if (c == 0)
523    {
524      // MessageBoxW(0, name, L"7-Zip", 0);
525      AddNameToCensor(censor, name, include, commonRecursedType, wildcardMatching);
526      name.Empty();
527    }
528    else
529      name += c;
530  }
531  if (!name.IsEmpty())
532    return "Map data error";
533
534  return NULL;
535}
536
537#endif
538
539static void AddSwitchWildcardsToCensor(
540    NWildcard::CCensor &censor,
541    const UStringVector &strings, bool include,
542    NRecursedType::EEnum commonRecursedType,
543    bool wildcardMatching,
544    Int32 codePage)
545{
546  const char *errorMessage = NULL;
547  unsigned i;
548  for (i = 0; i < strings.Size(); i++)
549  {
550    const UString &name = strings[i];
551    NRecursedType::EEnum recursedType;
552    unsigned pos = 0;
553
554    if (name.Len() < kSomeCludePostStringMinSize)
555    {
556      errorMessage = "Too short switch";
557      break;
558    }
559
560    if (::MyCharLower_Ascii(name[pos]) == kRecursedIDChar)
561    {
562      pos++;
563      wchar_t c = name[pos];
564      int index = -1;
565      if (c <= 0x7F)
566        index = FindCharPosInString(kRecursedPostCharSet, (char)c);
567      recursedType = GetRecursedTypeFromIndex(index);
568      if (index >= 0)
569        pos++;
570    }
571    else
572      recursedType = commonRecursedType;
573
574    if (name.Len() < pos + kSomeCludeAfterRecursedPostStringMinSize)
575    {
576      errorMessage = "Too short switch";
577      break;
578    }
579
580    UString tail = name.Ptr(pos + 1);
581
582    if (name[pos] == kImmediateNameID)
583      AddNameToCensor(censor, tail, include, recursedType, wildcardMatching);
584    else if (name[pos] == kFileListID)
585      AddToCensorFromListFile(NULL, censor, tail, include, recursedType, wildcardMatching, codePage);
586    #ifdef _WIN32
587    else if (name[pos] == kMapNameID)
588    {
589      errorMessage = ParseMapWithPaths(censor, tail, include, recursedType, wildcardMatching);
590      if (errorMessage)
591        break;
592    }
593    #endif
594    else
595    {
596      errorMessage = "Incorrect wildcard type marker";
597      break;
598    }
599  }
600  if (i != strings.Size())
601    throw CArcCmdLineException(errorMessage, strings[i]);
602}
603
604#ifdef _WIN32
605
606// This code converts all short file names to long file names.
607
608static void ConvertToLongName(const UString &prefix, UString &name)
609{
610  if (name.IsEmpty() || DoesNameContainWildcard(name))
611    return;
612  NFind::CFileInfo fi;
613  const FString path = us2fs(prefix + name);
614  #ifndef UNDER_CE
615  if (NFile::NName::IsDevicePath(path))
616    return;
617  #endif
618  if (fi.Find(path))
619    name = fs2us(fi.Name);
620}
621
622static void ConvertToLongNames(const UString &prefix, CObjectVector<NWildcard::CItem> &items)
623{
624  FOR_VECTOR (i, items)
625  {
626    NWildcard::CItem &item = items[i];
627    if (item.Recursive || item.PathParts.Size() != 1)
628      continue;
629    if (prefix.IsEmpty() && item.IsDriveItem())
630      continue;
631    ConvertToLongName(prefix, item.PathParts.Front());
632  }
633}
634
635static void ConvertToLongNames(const UString &prefix, NWildcard::CCensorNode &node)
636{
637  ConvertToLongNames(prefix, node.IncludeItems);
638  ConvertToLongNames(prefix, node.ExcludeItems);
639  unsigned i;
640  for (i = 0; i < node.SubNodes.Size(); i++)
641  {
642    UString &name = node.SubNodes[i].Name;
643    if (prefix.IsEmpty() && NWildcard::IsDriveColonName(name))
644      continue;
645    ConvertToLongName(prefix, name);
646  }
647  // mix folders with same name
648  for (i = 0; i < node.SubNodes.Size(); i++)
649  {
650    NWildcard::CCensorNode &nextNode1 = node.SubNodes[i];
651    for (unsigned j = i + 1; j < node.SubNodes.Size();)
652    {
653      const NWildcard::CCensorNode &nextNode2 = node.SubNodes[j];
654      if (nextNode1.Name.IsEqualTo_NoCase(nextNode2.Name))
655      {
656        nextNode1.IncludeItems += nextNode2.IncludeItems;
657        nextNode1.ExcludeItems += nextNode2.ExcludeItems;
658        node.SubNodes.Delete(j);
659      }
660      else
661        j++;
662    }
663  }
664  for (i = 0; i < node.SubNodes.Size(); i++)
665  {
666    NWildcard::CCensorNode &nextNode = node.SubNodes[i];
667    ConvertToLongNames(prefix + nextNode.Name + WCHAR_PATH_SEPARATOR, nextNode);
668  }
669}
670
671void ConvertToLongNames(NWildcard::CCensor &censor)
672{
673  FOR_VECTOR (i, censor.Pairs)
674  {
675    NWildcard::CPair &pair = censor.Pairs[i];
676    ConvertToLongNames(pair.Prefix, pair.Head);
677  }
678}
679
680#endif
681
682/*
683static NUpdateArchive::NPairAction::EEnum GetUpdatePairActionType(int i)
684{
685  switch (i)
686  {
687    case NUpdateArchive::NPairAction::kIgnore: return NUpdateArchive::NPairAction::kIgnore;
688    case NUpdateArchive::NPairAction::kCopy: return NUpdateArchive::NPairAction::kCopy;
689    case NUpdateArchive::NPairAction::kCompress: return NUpdateArchive::NPairAction::kCompress;
690    case NUpdateArchive::NPairAction::kCompressAsAnti: return NUpdateArchive::NPairAction::kCompressAsAnti;
691  }
692  throw 98111603;
693}
694*/
695
696static const wchar_t *kUpdatePairStateIDSet = L"pqrxyzw";
697static const int kUpdatePairStateNotSupportedActions[] = {2, 2, 1, -1, -1, -1, -1};
698
699static const unsigned kNumUpdatePairActions = 4;
700static const char *kUpdateIgnoreItselfPostStringID = "-";
701static const wchar_t kUpdateNewArchivePostCharID = '!';
702
703
704static bool ParseUpdateCommandString2(const UString &command,
705    NUpdateArchive::CActionSet &actionSet, UString &postString)
706{
707  for (unsigned i = 0; i < command.Len();)
708  {
709    wchar_t c = MyCharLower_Ascii(command[i]);
710    int statePos = FindCharPosInString(kUpdatePairStateIDSet, c);
711    if (statePos < 0)
712    {
713      postString = command.Ptr(i);
714      return true;
715    }
716    i++;
717    if (i >= command.Len())
718      return false;
719    c = command[i];
720    if (c < '0' || c >= '0' + kNumUpdatePairActions)
721      return false;
722    unsigned actionPos = c - '0';
723    actionSet.StateActions[(unsigned)statePos] = (NUpdateArchive::NPairAction::EEnum)(actionPos);
724    if (kUpdatePairStateNotSupportedActions[(unsigned)statePos] == (int)actionPos)
725      return false;
726    i++;
727  }
728  postString.Empty();
729  return true;
730}
731
732static void ParseUpdateCommandString(CUpdateOptions &options,
733    const UStringVector &updatePostStrings,
734    const NUpdateArchive::CActionSet &defaultActionSet)
735{
736  const char *errorMessage = "incorrect update switch command";
737  unsigned i;
738  for (i = 0; i < updatePostStrings.Size(); i++)
739  {
740    const UString &updateString = updatePostStrings[i];
741    if (updateString.IsEqualTo(kUpdateIgnoreItselfPostStringID))
742    {
743      if (options.UpdateArchiveItself)
744      {
745        options.UpdateArchiveItself = false;
746        options.Commands.Delete(0);
747      }
748    }
749    else
750    {
751      NUpdateArchive::CActionSet actionSet = defaultActionSet;
752
753      UString postString;
754      if (!ParseUpdateCommandString2(updateString, actionSet, postString))
755        break;
756      if (postString.IsEmpty())
757      {
758        if (options.UpdateArchiveItself)
759          options.Commands[0].ActionSet = actionSet;
760      }
761      else
762      {
763        if (postString[0] != kUpdateNewArchivePostCharID)
764          break;
765        CUpdateArchiveCommand uc;
766        UString archivePath = postString.Ptr(1);
767        if (archivePath.IsEmpty())
768          break;
769        uc.UserArchivePath = archivePath;
770        uc.ActionSet = actionSet;
771        options.Commands.Add(uc);
772      }
773    }
774  }
775  if (i != updatePostStrings.Size())
776    throw CArcCmdLineException(errorMessage, updatePostStrings[i]);
777}
778
779bool ParseComplexSize(const wchar_t *s, UInt64 &result);
780
781static void SetAddCommandOptions(
782    NCommandType::EEnum commandType,
783    const CParser &parser,
784    CUpdateOptions &options)
785{
786  NUpdateArchive::CActionSet defaultActionSet;
787  switch (commandType)
788  {
789    case NCommandType::kAdd:
790      defaultActionSet = NUpdateArchive::k_ActionSet_Add;
791      break;
792    case NCommandType::kDelete:
793      defaultActionSet = NUpdateArchive::k_ActionSet_Delete;
794      break;
795    default:
796      defaultActionSet = NUpdateArchive::k_ActionSet_Update;
797  }
798
799  options.UpdateArchiveItself = true;
800
801  options.Commands.Clear();
802  CUpdateArchiveCommand updateMainCommand;
803  updateMainCommand.ActionSet = defaultActionSet;
804  options.Commands.Add(updateMainCommand);
805  if (parser[NKey::kUpdate].ThereIs)
806    ParseUpdateCommandString(options, parser[NKey::kUpdate].PostStrings,
807        defaultActionSet);
808  if (parser[NKey::kWorkingDir].ThereIs)
809  {
810    const UString &postString = parser[NKey::kWorkingDir].PostStrings[0];
811    if (postString.IsEmpty())
812      NDir::MyGetTempPath(options.WorkingDir);
813    else
814      options.WorkingDir = us2fs(postString);
815  }
816  options.SfxMode = parser[NKey::kSfx].ThereIs;
817  if (options.SfxMode)
818    options.SfxModule = us2fs(parser[NKey::kSfx].PostStrings[0]);
819
820  if (parser[NKey::kVolume].ThereIs)
821  {
822    const UStringVector &sv = parser[NKey::kVolume].PostStrings;
823    FOR_VECTOR (i, sv)
824    {
825      UInt64 size;
826      if (!ParseComplexSize(sv[i], size) || size == 0)
827        throw CArcCmdLineException("Incorrect volume size:", sv[i]);
828      options.VolumesSizes.Add(size);
829    }
830  }
831}
832
833static void SetMethodOptions(const CParser &parser, CObjectVector<CProperty> &properties)
834{
835  if (parser[NKey::kProperty].ThereIs)
836  {
837    FOR_VECTOR (i, parser[NKey::kProperty].PostStrings)
838    {
839      CProperty prop;
840      prop.Name = parser[NKey::kProperty].PostStrings[i];
841      int index = prop.Name.Find(L'=');
842      if (index >= 0)
843      {
844        prop.Value = prop.Name.Ptr(index + 1);
845        prop.Name.DeleteFrom(index);
846      }
847      properties.Add(prop);
848    }
849  }
850}
851
852CArcCmdLineParser::CArcCmdLineParser(): parser(ARRAY_SIZE(kSwitchForms)) {}
853
854static inline void SetStreamMode(const CSwitchResult &sw, unsigned &res)
855{
856  if (sw.ThereIs)
857    res = sw.PostCharIndex;
858}
859
860void CArcCmdLineParser::Parse1(const UStringVector &commandStrings,
861    CArcCmdLineOptions &options)
862{
863  if (!parser.ParseStrings(kSwitchForms, commandStrings))
864    throw CArcCmdLineException(parser.ErrorMessage, parser.ErrorLine);
865
866  options.IsInTerminal = MY_IS_TERMINAL(stdin);
867  options.IsStdOutTerminal = MY_IS_TERMINAL(stdout);
868  options.IsStdErrTerminal = MY_IS_TERMINAL(stderr);
869
870  options.HelpMode = parser[NKey::kHelp1].ThereIs || parser[NKey::kHelp2].ThereIs  || parser[NKey::kHelp3].ThereIs;
871
872  options.StdInMode = parser[NKey::kStdIn].ThereIs;
873  options.StdOutMode = parser[NKey::kStdOut].ThereIs;
874  options.EnableHeaders = !parser[NKey::kDisableHeaders].ThereIs;
875  options.TechMode = parser[NKey::kTechMode].ThereIs;
876  options.ShowTime = parser[NKey::kShowTime].ThereIs;
877
878  if (parser[NKey::kDisablePercents].ThereIs
879      || options.StdOutMode
880      || !options.IsStdOutTerminal)
881    options.Number_for_Percents = k_OutStream_disabled;
882
883  if (options.StdOutMode)
884    options.Number_for_Out = k_OutStream_disabled;
885
886  SetStreamMode(parser[NKey::kOutStream], options.Number_for_Out);
887  SetStreamMode(parser[NKey::kErrStream], options.Number_for_Errors);
888  SetStreamMode(parser[NKey::kPercentStream], options.Number_for_Percents);
889
890  if (parser[NKey::kLogLevel].ThereIs)
891  {
892    const UString &s = parser[NKey::kLogLevel].PostStrings[0];
893    if (s.IsEmpty())
894      options.LogLevel = 1;
895    else
896    {
897      UInt32 v;
898      if (!StringToUInt32(s, v))
899        throw CArcCmdLineException("Unsupported switch postfix -bb", s);
900      options.LogLevel = (unsigned)v;
901    }
902  }
903
904  if (parser[NKey::kCaseSensitive].ThereIs)
905  {
906    g_CaseSensitive = !parser[NKey::kCaseSensitive].WithMinus;
907    options.CaseSensitiveChange = true;
908    options.CaseSensitive = g_CaseSensitive;
909  }
910
911  options.LargePages = false;
912  if (parser[NKey::kLargePages].ThereIs)
913    options.LargePages = !parser[NKey::kLargePages].WithMinus;
914
915
916  #ifndef UNDER_CE
917
918  if (parser[NKey::kAffinity].ThereIs)
919  {
920    const UString &s = parser[NKey::kAffinity].PostStrings[0];
921    if (!s.IsEmpty())
922    {
923      UInt32 v = 0;
924      AString a;
925      a.SetFromWStr_if_Ascii(s);
926      if (!a.IsEmpty())
927      {
928        const char *end;
929        v = ConvertHexStringToUInt32(a, &end);
930        if (*end != 0)
931          a.Empty();
932      }
933      if (a.IsEmpty())
934        throw CArcCmdLineException("Unsupported switch postfix -stm", s);
935
936      #ifdef _WIN32
937      SetProcessAffinityMask(GetCurrentProcess(), v);
938      #endif
939    }
940  }
941
942  #endif
943}
944
945struct CCodePagePair
946{
947  const char *Name;
948  Int32 CodePage;
949};
950
951static const unsigned kNumByteOnlyCodePages = 3;
952
953static const CCodePagePair g_CodePagePairs[] =
954{
955  { "utf-8", CP_UTF8 },
956  { "win", CP_ACP },
957  { "dos", CP_OEMCP },
958  { "utf-16le", MY__CP_UTF16 },
959  { "utf-16be", MY__CP_UTF16BE }
960};
961
962static Int32 FindCharset(const NCommandLineParser::CParser &parser, unsigned keyIndex,
963    bool byteOnlyCodePages, Int32 defaultVal)
964{
965  if (!parser[keyIndex].ThereIs)
966    return defaultVal;
967
968  UString name = parser[keyIndex].PostStrings.Back();
969  UInt32 v;
970  if (StringToUInt32(name, v))
971    if (v < ((UInt32)1 << 16))
972      return (Int32)v;
973  name.MakeLower_Ascii();
974  unsigned num = byteOnlyCodePages ? kNumByteOnlyCodePages : ARRAY_SIZE(g_CodePagePairs);
975  for (unsigned i = 0;; i++)
976  {
977    if (i == num) // to disable warnings from different compilers
978      throw CArcCmdLineException("Unsupported charset:", name);
979    const CCodePagePair &pair = g_CodePagePairs[i];
980    if (name.IsEqualTo(pair.Name))
981      return pair.CodePage;
982  }
983}
984
985HRESULT EnumerateDirItemsAndSort(
986    NWildcard::CCensor &censor,
987    NWildcard::ECensorPathMode censorPathMode,
988    const UString &addPathPrefix,
989    UStringVector &sortedPaths,
990    UStringVector &sortedFullPaths,
991    CDirItemsStat &st,
992    IDirItemsCallback *callback)
993{
994  FStringVector paths;
995
996  {
997    CDirItems dirItems;
998    dirItems.Callback = callback;
999    {
1000      HRESULT res = EnumerateItems(censor, censorPathMode, addPathPrefix, dirItems);
1001      st = dirItems.Stat;
1002      RINOK(res);
1003    }
1004
1005    FOR_VECTOR (i, dirItems.Items)
1006    {
1007      const CDirItem &dirItem = dirItems.Items[i];
1008      if (!dirItem.IsDir())
1009        paths.Add(dirItems.GetPhyPath(i));
1010    }
1011  }
1012
1013  if (paths.Size() == 0)
1014    throw CArcCmdLineException(kCannotFindArchive);
1015
1016  UStringVector fullPaths;
1017
1018  unsigned i;
1019
1020  for (i = 0; i < paths.Size(); i++)
1021  {
1022    FString fullPath;
1023    NFile::NDir::MyGetFullPathName(paths[i], fullPath);
1024    fullPaths.Add(fs2us(fullPath));
1025  }
1026
1027  CUIntVector indices;
1028  SortFileNames(fullPaths, indices);
1029  sortedPaths.ClearAndReserve(indices.Size());
1030  sortedFullPaths.ClearAndReserve(indices.Size());
1031
1032  for (i = 0; i < indices.Size(); i++)
1033  {
1034    unsigned index = indices[i];
1035    sortedPaths.AddInReserved(fs2us(paths[index]));
1036    sortedFullPaths.AddInReserved(fullPaths[index]);
1037    if (i > 0 && CompareFileNames(sortedFullPaths[i], sortedFullPaths[i - 1]) == 0)
1038      throw CArcCmdLineException("Duplicate archive path:", sortedFullPaths[i]);
1039  }
1040
1041  return S_OK;
1042}
1043
1044static void SetBoolPair(NCommandLineParser::CParser &parser, unsigned switchID, CBoolPair &bp)
1045{
1046  bp.Def = parser[switchID].ThereIs;
1047  if (bp.Def)
1048    bp.Val = !parser[switchID].WithMinus;
1049}
1050
1051void CArcCmdLineParser::Parse2(CArcCmdLineOptions &options)
1052{
1053  const UStringVector &nonSwitchStrings = parser.NonSwitchStrings;
1054  unsigned numNonSwitchStrings = nonSwitchStrings.Size();
1055  if (numNonSwitchStrings < kMinNonSwitchWords)
1056    throw CArcCmdLineException("The command must be specified");
1057
1058  if (!ParseArchiveCommand(nonSwitchStrings[kCommandIndex], options.Command))
1059    throw CArcCmdLineException("Unsupported command:", nonSwitchStrings[kCommandIndex]);
1060
1061  if (parser[NKey::kHash].ThereIs)
1062    options.HashMethods = parser[NKey::kHash].PostStrings;
1063
1064  if (parser[NKey::kElimDup].ThereIs)
1065  {
1066    options.ExtractOptions.ElimDup.Def = true;
1067    options.ExtractOptions.ElimDup.Val = !parser[NKey::kElimDup].WithMinus;
1068  }
1069
1070  NWildcard::ECensorPathMode censorPathMode = NWildcard::k_RelatPath;
1071  bool fullPathMode = parser[NKey::kFullPathMode].ThereIs;
1072  if (fullPathMode)
1073  {
1074    censorPathMode = NWildcard::k_AbsPath;
1075    const UString &s = parser[NKey::kFullPathMode].PostStrings[0];
1076    if (!s.IsEmpty())
1077    {
1078      if (s == L"2")
1079        censorPathMode = NWildcard::k_FullPath;
1080      else
1081        throw CArcCmdLineException("Unsupported -spf:", s);
1082    }
1083  }
1084
1085  NRecursedType::EEnum recursedType;
1086  if (parser[NKey::kRecursed].ThereIs)
1087    recursedType = GetRecursedTypeFromIndex(parser[NKey::kRecursed].PostCharIndex);
1088  else
1089    recursedType = NRecursedType::kNonRecursed;
1090
1091  bool wildcardMatching = true;
1092  if (parser[NKey::kDisableWildcardParsing].ThereIs)
1093    wildcardMatching = false;
1094
1095  g_CodePage = FindCharset(parser, NKey::kConsoleCharSet, true, -1);
1096  Int32 codePage = FindCharset(parser, NKey::kListfileCharSet, false, CP_UTF8);
1097
1098  bool thereAreSwitchIncludes = false;
1099
1100  if (parser[NKey::kInclude].ThereIs)
1101  {
1102    thereAreSwitchIncludes = true;
1103    AddSwitchWildcardsToCensor(options.Censor,
1104        parser[NKey::kInclude].PostStrings, true, recursedType, wildcardMatching, codePage);
1105  }
1106
1107  if (parser[NKey::kExclude].ThereIs)
1108    AddSwitchWildcardsToCensor(options.Censor,
1109        parser[NKey::kExclude].PostStrings, false, recursedType, wildcardMatching, codePage);
1110
1111  unsigned curCommandIndex = kCommandIndex + 1;
1112  bool thereIsArchiveName = !parser[NKey::kNoArName].ThereIs &&
1113      options.Command.CommandType != NCommandType::kBenchmark &&
1114      options.Command.CommandType != NCommandType::kInfo &&
1115      options.Command.CommandType != NCommandType::kHash;
1116
1117  bool isExtractGroupCommand = options.Command.IsFromExtractGroup();
1118  bool isExtractOrList = isExtractGroupCommand || options.Command.CommandType == NCommandType::kList;
1119  bool isRename = options.Command.CommandType == NCommandType::kRename;
1120
1121  if ((isExtractOrList || isRename) && options.StdInMode)
1122    thereIsArchiveName = false;
1123
1124  if (parser[NKey::kArcNameMode].ThereIs)
1125    options.UpdateOptions.ArcNameMode = ParseArcNameMode(parser[NKey::kArcNameMode].PostCharIndex);
1126
1127  if (thereIsArchiveName)
1128  {
1129    if (curCommandIndex >= numNonSwitchStrings)
1130      throw CArcCmdLineException("Cannot find archive name");
1131    options.ArchiveName = nonSwitchStrings[curCommandIndex++];
1132    if (options.ArchiveName.IsEmpty())
1133      throw CArcCmdLineException("Archive name cannot by empty");
1134    #ifdef _WIN32
1135    // options.ArchiveName.Replace(L'/', WCHAR_PATH_SEPARATOR);
1136    #endif
1137  }
1138
1139  AddToCensorFromNonSwitchesStrings(isRename ? &options.UpdateOptions.RenamePairs : NULL,
1140      curCommandIndex, options.Censor,
1141      nonSwitchStrings, recursedType, wildcardMatching,
1142      thereAreSwitchIncludes, codePage);
1143
1144  options.YesToAll = parser[NKey::kYes].ThereIs;
1145
1146
1147  #ifndef _NO_CRYPTO
1148  options.PasswordEnabled = parser[NKey::kPassword].ThereIs;
1149  if (options.PasswordEnabled)
1150    options.Password = parser[NKey::kPassword].PostStrings[0];
1151  #endif
1152
1153  options.ShowDialog = parser[NKey::kShowDialog].ThereIs;
1154
1155  if (parser[NKey::kArchiveType].ThereIs)
1156    options.ArcType = parser[NKey::kArchiveType].PostStrings[0];
1157
1158  options.ExcludedArcTypes = parser[NKey::kExcludedArcType].PostStrings;
1159
1160  SetMethodOptions(parser, options.Properties);
1161
1162  if (parser[NKey::kNtSecurity].ThereIs) options.NtSecurity.SetTrueTrue();
1163
1164  SetBoolPair(parser, NKey::kAltStreams, options.AltStreams);
1165  SetBoolPair(parser, NKey::kHardLinks, options.HardLinks);
1166  SetBoolPair(parser, NKey::kSymLinks, options.SymLinks);
1167
1168  if (isExtractOrList)
1169  {
1170    CExtractOptionsBase &eo = options.ExtractOptions;
1171
1172    {
1173      CExtractNtOptions &nt = eo.NtOptions;
1174      nt.NtSecurity = options.NtSecurity;
1175
1176      nt.AltStreams = options.AltStreams;
1177      if (!options.AltStreams.Def)
1178        nt.AltStreams.Val = true;
1179
1180      nt.HardLinks = options.HardLinks;
1181      if (!options.HardLinks.Def)
1182        nt.HardLinks.Val = true;
1183
1184      nt.SymLinks = options.SymLinks;
1185      if (!options.SymLinks.Def)
1186        nt.SymLinks.Val = true;
1187
1188      nt.ReplaceColonForAltStream = parser[NKey::kReplaceColonForAltStream].ThereIs;
1189      nt.WriteToAltStreamIfColon = parser[NKey::kWriteToAltStreamIfColon].ThereIs;
1190    }
1191
1192    options.Censor.AddPathsToCensor(NWildcard::k_AbsPath);
1193    options.Censor.ExtendExclude();
1194
1195    // are there paths that look as non-relative (!Prefix.IsEmpty())
1196    if (!options.Censor.AllAreRelative())
1197      throw CArcCmdLineException("Cannot use absolute pathnames for this command");
1198
1199    NWildcard::CCensor &arcCensor = options.arcCensor;
1200
1201    if (parser[NKey::kArInclude].ThereIs)
1202      AddSwitchWildcardsToCensor(arcCensor, parser[NKey::kArInclude].PostStrings, true, NRecursedType::kNonRecursed, wildcardMatching, codePage);
1203    if (parser[NKey::kArExclude].ThereIs)
1204      AddSwitchWildcardsToCensor(arcCensor, parser[NKey::kArExclude].PostStrings, false, NRecursedType::kNonRecursed, wildcardMatching, codePage);
1205
1206    if (thereIsArchiveName)
1207      AddNameToCensor(arcCensor, options.ArchiveName, true, NRecursedType::kNonRecursed, wildcardMatching);
1208
1209    arcCensor.AddPathsToCensor(NWildcard::k_RelatPath);
1210
1211    #ifdef _WIN32
1212    ConvertToLongNames(arcCensor);
1213    #endif
1214
1215    arcCensor.ExtendExclude();
1216
1217    if (options.StdInMode)
1218      options.ArcName_for_StdInMode = parser[NKey::kStdIn].PostStrings.Front();
1219
1220    if (isExtractGroupCommand)
1221    {
1222      if (options.StdOutMode)
1223      {
1224        if (
1225                  options.Number_for_Percents == k_OutStream_stdout
1226            // || options.Number_for_Out      == k_OutStream_stdout
1227            // || options.Number_for_Errors   == k_OutStream_stdout
1228            ||
1229            (
1230              (options.IsStdOutTerminal && options.IsStdErrTerminal)
1231              &&
1232              (
1233                      options.Number_for_Percents != k_OutStream_disabled
1234                // || options.Number_for_Out      != k_OutStream_disabled
1235                // || options.Number_for_Errors   != k_OutStream_disabled
1236              )
1237            )
1238           )
1239          throw CArcCmdLineException(kSameTerminalError);
1240      }
1241
1242      if (parser[NKey::kOutputDir].ThereIs)
1243      {
1244        eo.OutputDir = us2fs(parser[NKey::kOutputDir].PostStrings[0]);
1245        NFile::NName::NormalizeDirPathPrefix(eo.OutputDir);
1246      }
1247
1248      eo.OverwriteMode = NExtract::NOverwriteMode::kAsk;
1249      if (parser[NKey::kOverwrite].ThereIs)
1250      {
1251        eo.OverwriteMode = k_OverwriteModes[(unsigned)parser[NKey::kOverwrite].PostCharIndex];
1252        eo.OverwriteMode_Force = true;
1253      }
1254      else if (options.YesToAll)
1255      {
1256        eo.OverwriteMode = NExtract::NOverwriteMode::kOverwrite;
1257        eo.OverwriteMode_Force = true;
1258      }
1259    }
1260
1261    eo.PathMode = options.Command.GetPathMode();
1262    if (censorPathMode == NWildcard::k_AbsPath)
1263    {
1264      eo.PathMode = NExtract::NPathMode::kAbsPaths;
1265      eo.PathMode_Force = true;
1266    }
1267    else if (censorPathMode == NWildcard::k_FullPath)
1268    {
1269      eo.PathMode = NExtract::NPathMode::kFullPaths;
1270      eo.PathMode_Force = true;
1271    }
1272  }
1273  else if (options.Command.IsFromUpdateGroup())
1274  {
1275    if (parser[NKey::kArInclude].ThereIs)
1276      throw CArcCmdLineException("-ai switch is not supported for this command");
1277
1278    CUpdateOptions &updateOptions = options.UpdateOptions;
1279
1280    SetAddCommandOptions(options.Command.CommandType, parser, updateOptions);
1281
1282    updateOptions.MethodMode.Properties = options.Properties;
1283
1284    if (parser[NKey::kShareForWrite].ThereIs)
1285      updateOptions.OpenShareForWrite = true;
1286
1287    updateOptions.PathMode = censorPathMode;
1288
1289    updateOptions.AltStreams = options.AltStreams;
1290    updateOptions.NtSecurity = options.NtSecurity;
1291    updateOptions.HardLinks = options.HardLinks;
1292    updateOptions.SymLinks = options.SymLinks;
1293
1294    updateOptions.EMailMode = parser[NKey::kEmail].ThereIs;
1295    if (updateOptions.EMailMode)
1296    {
1297      updateOptions.EMailAddress = parser[NKey::kEmail].PostStrings.Front();
1298      if (updateOptions.EMailAddress.Len() > 0)
1299        if (updateOptions.EMailAddress[0] == L'.')
1300        {
1301          updateOptions.EMailRemoveAfter = true;
1302          updateOptions.EMailAddress.Delete(0);
1303        }
1304    }
1305
1306    updateOptions.StdOutMode = options.StdOutMode;
1307    updateOptions.StdInMode = options.StdInMode;
1308
1309    updateOptions.DeleteAfterCompressing = parser[NKey::kDeleteAfterCompressing].ThereIs;
1310    updateOptions.SetArcMTime = parser[NKey::kSetArcMTime].ThereIs;
1311
1312    if (updateOptions.StdOutMode && updateOptions.EMailMode)
1313      throw CArcCmdLineException("stdout mode and email mode cannot be combined");
1314
1315    if (updateOptions.StdOutMode)
1316    {
1317      if (options.IsStdOutTerminal)
1318        throw CArcCmdLineException(kTerminalOutError);
1319
1320      if (options.Number_for_Percents == k_OutStream_stdout
1321          || options.Number_for_Out == k_OutStream_stdout
1322          || options.Number_for_Errors == k_OutStream_stdout)
1323        throw CArcCmdLineException(kSameTerminalError);
1324    }
1325
1326    if (updateOptions.StdInMode)
1327      updateOptions.StdInFileName = parser[NKey::kStdIn].PostStrings.Front();
1328
1329    if (options.Command.CommandType == NCommandType::kRename)
1330      if (updateOptions.Commands.Size() != 1)
1331        throw CArcCmdLineException("Only one archive can be created with rename command");
1332  }
1333  else if (options.Command.CommandType == NCommandType::kBenchmark)
1334  {
1335    options.NumIterations = 1;
1336    if (curCommandIndex < numNonSwitchStrings)
1337    {
1338      if (!StringToUInt32(nonSwitchStrings[curCommandIndex], options.NumIterations))
1339        throw CArcCmdLineException("Incorrect Number of benmchmark iterations", nonSwitchStrings[curCommandIndex]);
1340      curCommandIndex++;
1341    }
1342  }
1343  else if (options.Command.CommandType == NCommandType::kHash)
1344  {
1345    options.Censor.AddPathsToCensor(censorPathMode);
1346    options.Censor.ExtendExclude();
1347
1348    CHashOptions &hashOptions = options.HashOptions;
1349    hashOptions.PathMode = censorPathMode;
1350    hashOptions.Methods = options.HashMethods;
1351    if (parser[NKey::kShareForWrite].ThereIs)
1352      hashOptions.OpenShareForWrite = true;
1353    hashOptions.StdInMode = options.StdInMode;
1354    hashOptions.AltStreamsMode = options.AltStreams.Val;
1355  }
1356  else if (options.Command.CommandType == NCommandType::kInfo)
1357  {
1358  }
1359  else
1360    throw 20150919;
1361}
1362