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