text: // Update.cpp
  
  #include "StdAfx.h"
  
  #include "Update.h"
  
  #include "../../../Common/IntToString.h"
  #include "../../../Common/StringConvert.h"
  
  #include "../../../Windows/DLL.h"
  #include "../../../Windows/FileDir.h"
  #include "../../../Windows/FileFind.h"
  #include "../../../Windows/FileName.h"
  #include "../../../Windows/PropVariant.h"
  #include "../../../Windows/PropVariantConv.h"
  #include "../../../Windows/TimeUtils.h"
  
  #include "../../Common/FileStreams.h"
  #include "../../Common/LimitedStreams.h"
  
  #include "../../Compress/CopyCoder.h"
  
  #include "../Common/DirItem.h"
  #include "../Common/EnumDirItems.h"
  #include "../Common/OpenArchive.h"
  #include "../Common/UpdateProduce.h"
  
  #include "EnumDirItems.h"
  #include "SetProperties.h"
  #include "TempFiles.h"
  #include "UpdateCallback.h"
  
  static const char *kUpdateIsNotSupoorted =
    "update operations are not supported for this archive";
  
  using namespace NWindows;
  using namespace NCOM;
  using namespace NFile;
  using namespace NDir;
  using namespace NName;
  
  static CFSTR kTempFolderPrefix = FTEXT("7zE");
  
  
  void CUpdateErrorInfo::SetFromLastError(const char *message)
  {
    SystemError = ::GetLastError();
    Message = message;
  }
  
  HRESULT CUpdateErrorInfo::SetFromLastError(const char *message, const FString &fileName)
  {
    SetFromLastError(message);
    FileNames.Add(fileName);
    return Get_HRESULT_Error();
  }
  
  static bool DeleteEmptyFolderAndEmptySubFolders(const FString &path)
  {
    NFind::CFileInfo fileInfo;
    FString pathPrefix = path + FCHAR_PATH_SEPARATOR;
    {
      NFind::CEnumerator enumerator(pathPrefix + FCHAR_ANY_MASK);
      while (enumerator.Next(fileInfo))
      {
        if (fileInfo.IsDir())
          if (!DeleteEmptyFolderAndEmptySubFolders(pathPrefix + fileInfo.Name))
            return false;
      }
    }
    /*
    // we don't need clear read-only for folders
    if (!MySetFileAttributes(path, 0))
      return false;
    */
    return RemoveDir(path);
  }
  
  
  using namespace NUpdateArchive;
  
  class COutMultiVolStream:
    public IOutStream,
    public CMyUnknownImp
  {
    unsigned _streamIndex; // required stream
    UInt64 _offsetPos; // offset from start of _streamIndex index
    UInt64 _absPos;
    UInt64 _length;
  
    struct CAltStreamInfo
    {
      COutFileStream *StreamSpec;
      CMyComPtr<IOutStream> Stream;
      FString Name;
      UInt64 Pos;
      UInt64 RealSize;
    };
    CObjectVector<CAltStreamInfo> Streams;
  public:
    // CMyComPtr<IArchiveUpdateCallback2> VolumeCallback;
    CRecordVector<UInt64> Sizes;
    FString Prefix;
    CTempFiles *TempFiles;
  
    void Init()
    {
      _streamIndex = 0;
      _offsetPos = 0;
      _absPos = 0;
      _length = 0;
    }
  
    bool SetMTime(const FILETIME *mTime);
    HRESULT Close();
  
    UInt64 GetSize() const { return _length; }
  
    MY_UNKNOWN_IMP1(IOutStream)
  
    STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
    STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition);
    STDMETHOD(SetSize)(UInt64 newSize);
  };
  
  // static NSynchronization::CCriticalSection g_TempPathsCS;
  
  HRESULT COutMultiVolStream::Close()
  {
    HRESULT res = S_OK;
    FOR_VECTOR (i, Streams)
    {
      COutFileStream *s = Streams[i].StreamSpec;
      if (s)
      {
        HRESULT res2 = s->Close();
        if (res2 != S_OK)
          res = res2;
      }
    }
    return res;
  }
  
  bool COutMultiVolStream::SetMTime(const FILETIME *mTime)
  {
    bool res = true;
    FOR_VECTOR (i, Streams)
    {
      COutFileStream *s = Streams[i].StreamSpec;
      if (s)
        if (!s->SetMTime(mTime))
          res = false;
    }
    return res;
  }
  
  STDMETHODIMP COutMultiVolStream::Write(const void *data, UInt32 size, UInt32 *processedSize)
  {
    if (processedSize != NULL)
      *processedSize = 0;
    while (size > 0)
    {
      if (_streamIndex >= Streams.Size())
      {
        CAltStreamInfo altStream;
  
        FChar temp[16];
        ConvertUInt32ToString(_streamIndex + 1, temp);
        FString name = temp;
        while (name.Len() < 3)
          name.InsertAtFront(FTEXT('0'));
        name.Insert(0, Prefix);
        altStream.StreamSpec = new COutFileStream;
        altStream.Stream = altStream.StreamSpec;
        if (!altStream.StreamSpec->Create(name, false))
          return ::GetLastError();
        {
          // NSynchronization::CCriticalSectionLock lock(g_TempPathsCS);
          TempFiles->Paths.Add(name);
        }
  
        altStream.Pos = 0;
        altStream.RealSize = 0;
        altStream.Name = name;
        Streams.Add(altStream);
        continue;
      }
      CAltStreamInfo &altStream = Streams[_streamIndex];
  
      unsigned index = _streamIndex;
      if (index >= Sizes.Size())
        index = Sizes.Size() - 1;
      UInt64 volSize = Sizes[index];
  
      if (_offsetPos >= volSize)
      {
        _offsetPos -= volSize;
        _streamIndex++;
        continue;
      }
      if (_offsetPos != altStream.Pos)
      {
        // CMyComPtr<IOutStream> outStream;
        // RINOK(altStream.Stream.QueryInterface(IID_IOutStream, &outStream));
        RINOK(altStream.Stream->Seek(_offsetPos, STREAM_SEEK_SET, NULL));
        altStream.Pos = _offsetPos;
      }
  
      UInt32 curSize = (UInt32)MyMin((UInt64)size, volSize - altStream.Pos);
      UInt32 realProcessed;
      RINOK(altStream.Stream->Write(data, curSize, &realProcessed));
      data = (void *)((Byte *)data + realProcessed);
      size -= realProcessed;
      altStream.Pos += realProcessed;
      _offsetPos += realProcessed;
      _absPos += realProcessed;
      if (_absPos > _length)
        _length = _absPos;
      if (_offsetPos > altStream.RealSize)
        altStream.RealSize = _offsetPos;
      if (processedSize != NULL)
        *processedSize += realProcessed;
      if (altStream.Pos == volSize)
      {
        _streamIndex++;
        _offsetPos = 0;
      }
      if (realProcessed == 0 && curSize != 0)
        return E_FAIL;
      break;
    }
    return S_OK;
  }
  
  STDMETHODIMP COutMultiVolStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)
  {
    if (seekOrigin >= 3)
      return STG_E_INVALIDFUNCTION;
    switch (seekOrigin)
    {
      case STREAM_SEEK_SET: _absPos = offset; break;
      case STREAM_SEEK_CUR: _absPos += offset; break;
      case STREAM_SEEK_END: _absPos = _length + offset; break;
    }
    _offsetPos = _absPos;
    if (newPosition != NULL)
      *newPosition = _absPos;
    _streamIndex = 0;
    return S_OK;
  }
  
  STDMETHODIMP COutMultiVolStream::SetSize(UInt64 newSize)
  {
    if (newSize < 0)
      return E_INVALIDARG;
    unsigned i = 0;
    while (i < Streams.Size())
    {
      CAltStreamInfo &altStream = Streams[i++];
      if ((UInt64)newSize < altStream.RealSize)
      {
        RINOK(altStream.Stream->SetSize(newSize));
        altStream.RealSize = newSize;
        break;
      }
      newSize -= altStream.RealSize;
    }
    while (i < Streams.Size())
    {
      {
        CAltStreamInfo &altStream = Streams.Back();
        altStream.Stream.Release();
        DeleteFileAlways(altStream.Name);
      }
      Streams.DeleteBack();
    }
    _offsetPos = _absPos;
    _streamIndex = 0;
    _length = newSize;
    return S_OK;
  }
  
  void CArchivePath::ParseFromPath(const UString &path, EArcNameMode mode)
  {
    OriginalPath = path;
    
    SplitPathToParts_2(path, Prefix, Name);
    
    if (mode == k_ArcNameMode_Add)
      return;
    if (mode == k_ArcNameMode_Exact)
    {
      BaseExtension.Empty();
      return;
    }
    
    int dotPos = Name.ReverseFind_Dot();
    if (dotPos < 0)
      return;
    if ((unsigned)dotPos == Name.Len() - 1)
    {
      Name.DeleteBack();
      BaseExtension.Empty();
      return;
    }
    const UString ext = Name.Ptr(dotPos + 1);
    if (BaseExtension.IsEqualTo_NoCase(ext))
    {
      BaseExtension = ext;
      Name.DeleteFrom(dotPos);
    }
    else
      BaseExtension.Empty();
  }
  
  UString CArchivePath::GetFinalPath() const
  {
    UString path = GetPathWithoutExt();
    if (!BaseExtension.IsEmpty())
    {
      path += L'.';
      path += BaseExtension;
    }
    return path;
  }
  
  UString CArchivePath::GetFinalVolPath() const
  {
    UString path = GetPathWithoutExt();
    if (!BaseExtension.IsEmpty())
    {
      path += L'.';
      path += VolExtension;
    }
    return path;
  }
  
  FString CArchivePath::GetTempPath() const
  {
    FString path = TempPrefix;
    path += us2fs(Name);
    if (!BaseExtension.IsEmpty())
    {
      path += FTEXT('.');
      path += us2fs(BaseExtension);
    }
    path.AddAscii(".tmp");
    path += TempPostfix;
    return path;
  }
  
  static const wchar_t *kDefaultArcType = L"7z";
  static const wchar_t *kDefaultArcExt = L"7z";
  static const char *kSFXExtension =
    #ifdef _WIN32
      "exe";
    #else
      "";
    #endif
  
  bool CUpdateOptions::InitFormatIndex(const CCodecs *codecs,
      const CObjectVector<COpenType> &types, const UString &arcPath)
  {
    if (types.Size() > 1)
      return false;
    // int arcTypeIndex = -1;
    if (types.Size() != 0)
    {
      MethodMode.Type = types[0];
      MethodMode.Type_Defined = true;
    }
    if (MethodMode.Type.FormatIndex < 0)
    {
      // MethodMode.Type = -1;
      MethodMode.Type = COpenType();
      if (ArcNameMode != k_ArcNameMode_Add)
      {
        MethodMode.Type.FormatIndex = codecs->FindFormatForArchiveName(arcPath);
        if (MethodMode.Type.FormatIndex >= 0)
          MethodMode.Type_Defined = true;
      }
    }
    return true;
  }
  
  bool CUpdateOptions::SetArcPath(const CCodecs *codecs, const UString &arcPath)
  {
    UString typeExt;
    int formatIndex = MethodMode.Type.FormatIndex;
    if (formatIndex < 0)
    {
      typeExt = kDefaultArcExt;
    }
    else
    {
      const CArcInfoEx &arcInfo = codecs->Formats[formatIndex];
      if (!arcInfo.UpdateEnabled)
        return false;
      typeExt = arcInfo.GetMainExt();
    }
    UString ext = typeExt;
    if (SfxMode)
      ext.SetFromAscii(kSFXExtension);
    ArchivePath.BaseExtension = ext;
    ArchivePath.VolExtension = typeExt;
    ArchivePath.ParseFromPath(arcPath, ArcNameMode);
    FOR_VECTOR (i, Commands)
    {
      CUpdateArchiveCommand &uc = Commands[i];
      uc.ArchivePath.BaseExtension = ext;
      uc.ArchivePath.VolExtension = typeExt;
      uc.ArchivePath.ParseFromPath(uc.UserArchivePath, ArcNameMode);
    }
    return true;
  }
  
  struct CUpdateProduceCallbackImp: public IUpdateProduceCallback
  {
    const CObjectVector<CArcItem> *_arcItems;
    IUpdateCallbackUI *_callback;
    
    CUpdateProduceCallbackImp(const CObjectVector<CArcItem> *a,
        IUpdateCallbackUI *callback): _arcItems(a), _callback(callback) {}
    virtual HRESULT ShowDeleteFile(unsigned arcIndex);
  };
  
  HRESULT CUpdateProduceCallbackImp::ShowDeleteFile(unsigned arcIndex)
  {
    const CArcItem &ai = (*_arcItems)[arcIndex];
    return _callback->ShowDeleteFile(ai.Name, ai.IsDir);
  }
  
  bool CRenamePair::Prepare()
  {
    if (RecursedType != NRecursedType::kNonRecursed)
      return false;
    if (!WildcardParsing)
      return true;
    return !DoesNameContainWildcard(OldName);
  }
  
  extern bool g_CaseSensitive;
  
  static unsigned CompareTwoNames(const wchar_t *s1, const wchar_t *s2)
  {
    for (unsigned i = 0;; i++)
    {
      wchar_t c1 = s1[i];
      wchar_t c2 = s2[i];
      if (c1 == 0 || c2 == 0)
        return i;
      if (c1 == c2)
        continue;
      if (!g_CaseSensitive && (MyCharUpper(c1) == MyCharUpper(c2)))
        continue;
      if (IsPathSepar(c1) && IsPathSepar(c2))
        continue;
      return i;
    }
  }
  
  bool CRenamePair::GetNewPath(bool isFolder, const UString &src, UString &dest) const
  {
    unsigned num = CompareTwoNames(OldName, src);
    if (OldName[num] == 0)
    {
      if (src[num] != 0 && !IsPathSepar(src[num]) && num != 0 && !IsPathSepar(src[num - 1]))
        return false;
    }
    else
    {
      // OldName[num] != 0
      // OldName = "1\1a.txt"
      // src = "1"
  
      if (!isFolder
          || src[num] != 0
          || !IsPathSepar(OldName[num])
          || OldName[num + 1] != 0)
        return false;
    }
    dest = NewName + src.Ptr(num);
    return true;
  }
  
  #ifdef SUPPORT_ALT_STREAMS
  int FindAltStreamColon_in_Path(const wchar_t *path);
  #endif
  
  static HRESULT Compress(
      const CUpdateOptions &options,
      bool isUpdatingItself,
      CCodecs *codecs,
      const CActionSet &actionSet,
      const CArc *arc,
      CArchivePath &archivePath,
      const CObjectVector<CArcItem> &arcItems,
      Byte *processedItemsStatuses,
      const CDirItems &dirItems,
      const CDirItem *parentDirItem,
      CTempFiles &tempFiles,
      CUpdateErrorInfo &errorInfo,
      IUpdateCallbackUI *callback,
      CFinishArchiveStat &st)
  {
    CMyComPtr<IOutArchive> outArchive;
    int formatIndex = options.MethodMode.Type.FormatIndex;
    
    if (arc)
    {
      formatIndex = arc->FormatIndex;
      if (formatIndex < 0)
        return E_NOTIMPL;
      CMyComPtr<IInArchive> archive2 = arc->Archive;
      HRESULT result = archive2.QueryInterface(IID_IOutArchive, &outArchive);
      if (result != S_OK)
        throw kUpdateIsNotSupoorted;
    }
    else
    {
      RINOK(codecs->CreateOutArchive(formatIndex, outArchive));
  
      #ifdef EXTERNAL_CODECS
      {
        CMyComPtr<ISetCompressCodecsInfo> setCompressCodecsInfo;
        outArchive.QueryInterface(IID_ISetCompressCodecsInfo, (void **)&setCompressCodecsInfo);
        if (setCompressCodecsInfo)
        {
          RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(codecs));
        }
      }
      #endif
    }
    
    if (outArchive == 0)
      throw kUpdateIsNotSupoorted;
    
    NFileTimeType::EEnum fileTimeType;
    {
      UInt32 value;
      RINOK(outArchive->GetFileTimeType(&value));
      
      switch (value)
      {
        case NFileTimeType::kWindows:
        case NFileTimeType::kUnix:
        case NFileTimeType::kDOS:
          fileTimeType = (NFileTimeType::EEnum)value;
          break;
        default:
          return E_FAIL;
      }
    }
  
    {
      const CArcInfoEx &arcInfo = codecs->Formats[formatIndex];
      if (options.AltStreams.Val && !arcInfo.Flags_AltStreams())
        return E_NOTIMPL;
      if (options.NtSecurity.Val && !arcInfo.Flags_NtSecure())
        return E_NOTIMPL;
    }
  
    CRecordVector<CUpdatePair2> updatePairs2;
  
    UStringVector newNames;
  
    if (options.RenamePairs.Size() != 0)
    {
      FOR_VECTOR (i, arcItems)
      {
        const CArcItem &ai = arcItems[i];
        bool needRename = false;
        UString dest;
        
        if (ai.Censored)
        {
          FOR_VECTOR (j, options.RenamePairs)
          {
            const CRenamePair &rp = options.RenamePairs[j];
            if (rp.GetNewPath(ai.IsDir, ai.Name, dest))
            {
              needRename = true;
              break;
            }
            
            #ifdef SUPPORT_ALT_STREAMS
            if (ai.IsAltStream)
            {
              int colonPos = FindAltStreamColon_in_Path(ai.Name);
              if (colonPos >= 0)
              {
                UString mainName = ai.Name.Left(colonPos);
                /*
                actually we must improve that code to support cases
                with folder renaming like: rn arc dir1\ dir2\
                */
                if (rp.GetNewPath(false, mainName, dest))
                {
                  needRename = true;
                  dest += L':';
                  dest += ai.Name.Ptr(colonPos + 1);
                  break;
                }
              }
            }
            #endif
          }
        }
        
        CUpdatePair2 up2;
        up2.SetAs_NoChangeArcItem(ai.IndexInServer);
        if (needRename)
        {
          up2.NewProps = true;
          RINOK(arc->IsItemAnti(i, up2.IsAnti));
          up2.NewNameIndex = newNames.Add(dest);
        }
        updatePairs2.Add(up2);
      }
    }
    else
    {
      CRecordVector<CUpdatePair> updatePairs;
      GetUpdatePairInfoList(dirItems, arcItems, fileTimeType, updatePairs); // must be done only once!!!
      CUpdateProduceCallbackImp upCallback(&arcItems, callback);
      
      UpdateProduce(updatePairs, actionSet, updatePairs2, isUpdatingItself ? &upCallback : NULL);
    }
  
    {
      UInt32 numItems = 0;
      FOR_VECTOR (i, updatePairs2)
        if (updatePairs2[i].NewData)
          numItems++;
      RINOK(callback->SetNumItems(numItems));
    }
    
    CArchiveUpdateCallback *updateCallbackSpec = new CArchiveUpdateCallback;
    CMyComPtr<IArchiveUpdateCallback> updateCallback(updateCallbackSpec);
    
    updateCallbackSpec->ShareForWrite = options.OpenShareForWrite;
    updateCallbackSpec->StdInMode = options.StdInMode;
    updateCallbackSpec->Callback = callback;
  
    if (arc)
    {
      // we set Archive to allow to transfer GetProperty requests back to DLL.
      updateCallbackSpec->Archive = arc->Archive;
    }
    
    updateCallbackSpec->DirItems = &dirItems;
    updateCallbackSpec->ParentDirItem = parentDirItem;
  
    updateCallbackSpec->StoreNtSecurity = options.NtSecurity.Val;
    updateCallbackSpec->StoreHardLinks = options.HardLinks.Val;
    updateCallbackSpec->StoreSymLinks = options.SymLinks.Val;
  
    updateCallbackSpec->Arc = arc;
    updateCallbackSpec->ArcItems = &arcItems;
    updateCallbackSpec->UpdatePairs = &updatePairs2;
  
    updateCallbackSpec->ProcessedItemsStatuses = processedItemsStatuses;
  
    if (options.RenamePairs.Size() != 0)
      updateCallbackSpec->NewNames = &newNames;
  
    CMyComPtr<IOutStream> outSeekStream;
    CMyComPtr<ISequentialOutStream> outStream;
  
    if (!options.StdOutMode)
    {
      FString dirPrefix;
      if (!GetOnlyDirPrefix(us2fs(archivePath.GetFinalPath()), dirPrefix))
        throw 1417161;
      CreateComplexDir(dirPrefix);
    }
  
    COutFileStream *outStreamSpec = NULL;
    CStdOutFileStream *stdOutFileStreamSpec = NULL;
    COutMultiVolStream *volStreamSpec = NULL;
  
    if (options.VolumesSizes.Size() == 0)
    {
      if (options.StdOutMode)
      {
        stdOutFileStreamSpec = new CStdOutFileStream;
        outStream = stdOutFileStreamSpec;
      }
      else
      {
        outStreamSpec = new COutFileStream;
        outSeekStream = outStreamSpec;
        outStream = outSeekStream;
        bool isOK = false;
        FString realPath;
        
        for (unsigned i = 0; i < (1 << 16); i++)
        {
          if (archivePath.Temp)
          {
            if (i > 0)
            {
              FChar s[16];
              ConvertUInt32ToString(i, s);
              archivePath.TempPostfix = s;
            }
            realPath = archivePath.GetTempPath();
          }
          else
            realPath = us2fs(archivePath.GetFinalPath());
          if (outStreamSpec->Create(realPath, false))
          {
            tempFiles.Paths.Add(realPath);
            isOK = true;
            break;
          }
          if (::GetLastError() != ERROR_FILE_EXISTS)
            break;
          if (!archivePath.Temp)
            break;
        }
        
        if (!isOK)
          return errorInfo.SetFromLastError("cannot open file", realPath);
      }
    }
    else
    {
      if (options.StdOutMode)
        return E_FAIL;
      if (arc && arc->GetGlobalOffset() > 0)
        return E_NOTIMPL;
        
      volStreamSpec = new COutMultiVolStream;
      outSeekStream = volStreamSpec;
      outStream = outSeekStream;
      volStreamSpec->Sizes = options.VolumesSizes;
      volStreamSpec->Prefix = us2fs(archivePath.GetFinalVolPath());
      volStreamSpec->Prefix += FTEXT('.');
      volStreamSpec->TempFiles = &tempFiles;
      volStreamSpec->Init();
  
      /*
      updateCallbackSpec->VolumesSizes = volumesSizes;
      updateCallbackSpec->VolName = archivePath.Prefix + archivePath.Name;
      if (!archivePath.VolExtension.IsEmpty())
        updateCallbackSpec->VolExt = UString(L'.') + archivePath.VolExtension;
      */
    }
  
    RINOK(SetProperties(outArchive, options.MethodMode.Properties));
  
    if (options.SfxMode)
    {
      CInFileStream *sfxStreamSpec = new CInFileStream;
      CMyComPtr<IInStream> sfxStream(sfxStreamSpec);
      if (!sfxStreamSpec->Open(options.SfxModule))
        return errorInfo.SetFromLastError("cannot open SFX module", options.SfxModule);
  
      CMyComPtr<ISequentialOutStream> sfxOutStream;
      COutFileStream *outStreamSpec = NULL;
      if (options.VolumesSizes.Size() == 0)
        sfxOutStream = outStream;
      else
      {
        outStreamSpec = new COutFileStream;
        sfxOutStream = outStreamSpec;
        FString realPath = us2fs(archivePath.GetFinalPath());
        if (!outStreamSpec->Create(realPath, false))
          return errorInfo.SetFromLastError("cannot open file", realPath);
      }
  
      {
        UInt64 sfxSize;
        RINOK(sfxStreamSpec->GetSize(&sfxSize));
        RINOK(callback->WriteSfx(fs2us(options.SfxModule), sfxSize));
      }
  
      RINOK(NCompress::CopyStream(sfxStream, sfxOutStream, NULL));
      
      if (outStreamSpec)
      {
        RINOK(outStreamSpec->Close());
      }
    }
  
    CMyComPtr<ISequentialOutStream> tailStream;
  
    if (options.SfxMode || !arc || arc->ArcStreamOffset == 0)
      tailStream = outStream;
    else
    {
      // Int64 globalOffset = arc->GetGlobalOffset();
      RINOK(arc->InStream->Seek(0, STREAM_SEEK_SET, NULL));
      RINOK(NCompress::CopyStream_ExactSize(arc->InStream, outStream, arc->ArcStreamOffset, NULL));
      if (options.StdOutMode)
        tailStream = outStream;
      else
      {
        CTailOutStream *tailStreamSpec = new CTailOutStream;
        tailStream = tailStreamSpec;
        tailStreamSpec->Stream = outSeekStream;
        tailStreamSpec->Offset = arc->ArcStreamOffset;
        tailStreamSpec->Init();
      }
    }
  
  
    HRESULT result = outArchive->UpdateItems(tailStream, updatePairs2.Size(), updateCallback);
    // callback->Finalize();
    RINOK(result);
  
    if (!updateCallbackSpec->AreAllFilesClosed())
    {
      errorInfo.Message = "There are unclosed input file:";
      errorInfo.FileNames = updateCallbackSpec->_openFiles_Paths;
      return E_FAIL;
    }
  
    if (options.SetArcMTime)
    {
      FILETIME ft;
      ft.dwLowDateTime = 0;
      ft.dwHighDateTime = 0;
      FOR_VECTOR (i, updatePairs2)
      {
        CUpdatePair2 &pair2 = updatePairs2[i];
        const FILETIME *ft2 = NULL;
        if (pair2.NewProps && pair2.DirIndex >= 0)
          ft2 = &dirItems.Items[pair2.DirIndex].MTime;
        else if (pair2.UseArcProps && pair2.ArcIndex >= 0)
          ft2 = &arcItems[pair2.ArcIndex].MTime;
        if (ft2)
        {
          if (::CompareFileTime(&ft, ft2) < 0)
            ft = *ft2;
        }
      }
      if (ft.dwLowDateTime != 0 || ft.dwHighDateTime != 0)
      {
        if (outStreamSpec)
          outStreamSpec->SetMTime(&ft);
        else if (volStreamSpec)
          volStreamSpec->SetMTime(&ft);;
      }
    }
  
    if (callback)
    {
      UInt64 size = 0;
      if (outStreamSpec)
        outStreamSpec->GetSize(&size);
      else if (stdOutFileStreamSpec)
        size = stdOutFileStreamSpec->GetSize();
      else
        size = volStreamSpec->GetSize();
  
      st.OutArcFileSize = size;
    }
  
    if (outStreamSpec)
      result = outStreamSpec->Close();
    else if (volStreamSpec)
      result = volStreamSpec->Close();
    return result;
  }
  
  bool CensorNode_CheckPath2(const NWildcard::CCensorNode &node, const CReadArcItem &item, bool &include);
  
  static bool Censor_CheckPath(const NWildcard::CCensor &censor, const CReadArcItem &item)
  {
    bool finded = false;
    FOR_VECTOR (i, censor.Pairs)
    {
      bool include;
      if (CensorNode_CheckPath2(censor.Pairs[i].Head, item, include))
      {
        if (!include)
          return false;
        finded = true;
      }
    }
    return finded;
  }
  
  static HRESULT EnumerateInArchiveItems(
      // bool storeStreamsMode,
      const NWildcard::CCensor &censor,
      const CArc &arc,
      CObjectVector<CArcItem> &arcItems)
  {
    arcItems.Clear();
    UInt32 numItems;
    IInArchive *archive = arc.Archive;
    RINOK(archive->GetNumberOfItems(&numItems));
    arcItems.ClearAndReserve(numItems);
  
    CReadArcItem item;
  
    for (UInt32 i = 0; i < numItems; i++)
    {
      CArcItem ai;
  
      RINOK(arc.GetItem(i, item));
      ai.Name = item.Path;
      ai.IsDir = item.IsDir;
      ai.IsAltStream =
          #ifdef SUPPORT_ALT_STREAMS
            item.IsAltStream;
          #else
            false;
          #endif
  
      /*
      if (!storeStreamsMode && ai.IsAltStream)
        continue;
      */
      ai.Censored = Censor_CheckPath(censor, item);
  
      RINOK(arc.GetItemMTime(i, ai.MTime, ai.MTimeDefined));
      RINOK(arc.GetItemSize(i, ai.Size, ai.SizeDefined));
  
      {
        CPropVariant prop;
        RINOK(archive->GetProperty(i, kpidTimeType, &prop));
        if (prop.vt == VT_UI4)
        {
          ai.TimeType = (int)(NFileTimeType::EEnum)prop.ulVal;
          switch (ai.TimeType)
          {
            case NFileTimeType::kWindows:
            case NFileTimeType::kUnix:
            case NFileTimeType::kDOS:
              break;
            default:
              return E_FAIL;
          }
        }
      }
  
      ai.IndexInServer = i;
      arcItems.AddInReserved(ai);
    }
    return S_OK;
  }
  
  #if defined(_WIN32) && !defined(UNDER_CE)
  
  #include <mapi.h>
  
  #endif
  
  struct CRefSortPair
  {
    unsigned Len;
    unsigned Index;
  };
  
  #define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; }
  
  static int CompareRefSortPair(const CRefSortPair *a1, const CRefSortPair *a2, void *)
  {
    RINOZ(-MyCompare(a1->Len, a2->Len));
    return MyCompare(a1->Index, a2->Index);
  }
  
  static unsigned GetNumSlashes(const FChar *s)
  {
    for (unsigned numSlashes = 0;;)
    {
      FChar c = *s++;
      if (c == 0)
        return numSlashes;
      if (IS_PATH_SEPAR(c))
        numSlashes++;
    }
  }
  
  #ifdef _WIN32
  void ConvertToLongNames(NWildcard::CCensor &censor);
  #endif
  
  HRESULT UpdateArchive(
      CCodecs *codecs,
      const CObjectVector<COpenType> &types,
      const UString &cmdArcPath2,
      NWildcard::CCensor &censor,
      CUpdateOptions &options,
      CUpdateErrorInfo &errorInfo,
      IOpenCallbackUI *openCallback,
      IUpdateCallbackUI2 *callback,
      bool needSetPath)
  {
    if (options.StdOutMode && options.EMailMode)
      return E_FAIL;
  
    if (types.Size() > 1)
      return E_NOTIMPL;
  
    bool renameMode = !options.RenamePairs.IsEmpty();
    if (renameMode)
    {
      if (options.Commands.Size() != 1)
        return E_FAIL;
    }
  
    if (options.DeleteAfterCompressing)
    {
      if (options.Commands.Size() != 1)
        return E_NOTIMPL;
      const CActionSet &as = options.Commands[0].ActionSet;
      for (int i = 2; i < NPairState::kNumValues; i++)
        if (as.StateActions[i] != NPairAction::kCompress)
          return E_NOTIMPL;
    }
  
    censor.AddPathsToCensor(options.PathMode);
    #ifdef _WIN32
    ConvertToLongNames(censor);
    #endif
    censor.ExtendExclude();
  
    
    if (options.VolumesSizes.Size() > 0 && (options.EMailMode /* || options.SfxMode */))
      return E_NOTIMPL;
  
    if (options.SfxMode)
    {
      CProperty property;
      property.Name.SetFromAscii("rsfx");
      options.MethodMode.Properties.Add(property);
      if (options.SfxModule.IsEmpty())
      {
        errorInfo.Message = "SFX file is not specified";
        return E_FAIL;
      }
      bool found = false;
      if (options.SfxModule.Find(FCHAR_PATH_SEPARATOR) < 0)
      {
        const FString fullName = NDLL::GetModuleDirPrefix() + options.SfxModule;
        if (NFind::DoesFileExist(fullName))
        {
          options.SfxModule = fullName;
          found = true;
        }
      }
      if (!found)
      {
        if (!NFind::DoesFileExist(options.SfxModule))
          return errorInfo.SetFromLastError("cannot find specified SFX module", options.SfxModule);
      }
    }
  
    CArchiveLink arcLink;
  
    
    if (needSetPath)
    {
      if (!options.InitFormatIndex(codecs, types, cmdArcPath2) ||
          !options.SetArcPath(codecs, cmdArcPath2))
        return E_NOTIMPL;
    }
    UString arcPath = options.ArchivePath.GetFinalPath();
  
    if (cmdArcPath2.IsEmpty())
    {
      if (options.MethodMode.Type.FormatIndex < 0)
        throw "type of archive is not specified";
    }
    else
    {
      NFind::CFileInfo fi;
      if (!fi.Find(us2fs(arcPath)))
      {
        if (renameMode)
          throw "can't find archive";;
        if (options.MethodMode.Type.FormatIndex < 0)
        {
          if (!options.SetArcPath(codecs, cmdArcPath2))
            return E_NOTIMPL;
        }
      }
      else
      {
        if (fi.IsDir())
          throw "there is no such archive";
        if (fi.IsDevice)
          return E_NOTIMPL;
        if (options.VolumesSizes.Size() > 0)
          return E_NOTIMPL;
        CObjectVector<COpenType> types;
        // change it.
        if (options.MethodMode.Type_Defined)
          types.Add(options.MethodMode.Type);
        // We need to set Properties to open archive only in some cases (WIM archives).
  
        CIntVector excl;
        COpenOptions op;
        #ifndef _SFX
        op.props = &options.MethodMode.Properties;
        #endif
        op.codecs = codecs;
        op.types = &types;
        op.excludedFormats = &excl;
        op.stdInMode = false;
        op.stream = NULL;
        op.filePath = arcPath;
  
        RINOK(callback->StartOpenArchive(arcPath));
  
        HRESULT result = arcLink.Open3(op, openCallback);
  
        if (result == E_ABORT)
          return result;
        
        if (result == S_OK && arcLink.NonOpen_ErrorInfo.ErrorFormatIndex >= 0)
          result = S_FALSE;
  
        HRESULT res2 = callback->OpenResult(codecs, arcLink, arcPath, result);
        /*
        if (result == S_FALSE)
          return E_FAIL;
        */
        RINOK(res2);
        RINOK(result);
  
        if (arcLink.VolumePaths.Size() > 1)
        {
          errorInfo.SystemError = (DWORD)E_NOTIMPL;
          errorInfo.Message = "Updating for multivolume archives is not implemented";
          return E_NOTIMPL;
        }
        
        CArc &arc = arcLink.Arcs.Back();
        arc.MTimeDefined = !fi.IsDevice;
        arc.MTime = fi.MTime;
  
        if (arc.ErrorInfo.ThereIsTail)
        {
          errorInfo.SystemError = (DWORD)E_NOTIMPL;
          errorInfo.Message = "There is some data block after the end of the archive";
          return E_NOTIMPL;
        }
        if (options.MethodMode.Type.FormatIndex < 0)
        {
          options.MethodMode.Type.FormatIndex = arcLink.GetArc()->FormatIndex;
          if (!options.SetArcPath(codecs, cmdArcPath2))
            return E_NOTIMPL;
        }
      }
    }
  
    if (options.MethodMode.Type.FormatIndex < 0)
    {
      options.MethodMode.Type.FormatIndex = codecs->FindFormatForArchiveType(kDefaultArcType);
      if (options.MethodMode.Type.FormatIndex < 0)
        return E_NOTIMPL;
    }
  
    bool thereIsInArchive = arcLink.IsOpen;
    if (!thereIsInArchive && renameMode)
      return E_FAIL;
    
    CDirItems dirItems;
    dirItems.Callback = callback;
  
    CDirItem parentDirItem;
    CDirItem *parentDirItem_Ptr = NULL;
    
    /*
    FStringVector requestedPaths;
    FStringVector *requestedPaths_Ptr = NULL;
    if (options.DeleteAfterCompressing)
      requestedPaths_Ptr = &requestedPaths;
    */
  
    if (options.StdInMode)
    {
      CDirItem di;
      di.Name = options.StdInFileName;
      di.Size = (UInt64)(Int64)-1;
      di.Attrib = 0;
      NTime::GetCurUtcFileTime(di.MTime);
      di.CTime = di.ATime = di.MTime;
      dirItems.Items.Add(di);
    }
    else
    {
      bool needScanning = false;
      
      if (!renameMode)
      FOR_VECTOR (i, options.Commands)
        if (options.Commands[i].ActionSet.NeedScanning())
          needScanning = true;
  
      if (needScanning)
      {
        RINOK(callback->StartScanning());
  
        dirItems.SymLinks = options.SymLinks.Val;
  
        #if defined(_WIN32) && !defined(UNDER_CE)
        dirItems.ReadSecure = options.NtSecurity.Val;
        #endif
  
        dirItems.ScanAltStreams = options.AltStreams.Val;
  
        HRESULT res = EnumerateItems(censor,
            options.PathMode,
            options.AddPathPrefix,
            dirItems);
  
        if (res != S_OK)
        {
          if (res != E_ABORT)
            errorInfo.Message = "Scanning error";
          return res;
        }
        
        RINOK(callback->FinishScanning(dirItems.Stat));
  
        if (censor.Pairs.Size() == 1)
        {
          NFind::CFileInfo fi;
          FString prefix = us2fs(censor.Pairs[0].Prefix);
          prefix += FTEXT('.');
          // UString prefix = censor.Pairs[0].Prefix;
          /*
          if (prefix.Back() == WCHAR_PATH_SEPARATOR)
          {
            prefix.DeleteBack();
          }
          */
          if (fi.Find(prefix))
            if (fi.IsDir())
            {
              parentDirItem.Size = fi.Size;
              parentDirItem.CTime = fi.CTime;
              parentDirItem.ATime = fi.ATime;
              parentDirItem.MTime = fi.MTime;
              parentDirItem.Attrib = fi.Attrib;
              parentDirItem_Ptr = &parentDirItem;
  
              int secureIndex = -1;
              #if defined(_WIN32) && !defined(UNDER_CE)
              if (options.NtSecurity.Val)
                dirItems.AddSecurityItem(prefix, secureIndex);
              #endif
              parentDirItem.SecureIndex = secureIndex;
  
              parentDirItem_Ptr = &parentDirItem;
            }
        }
      }
    }
  
    FString tempDirPrefix;
    bool usesTempDir = false;
    
    #ifdef _WIN32
    CTempDir tempDirectory;
    if (options.EMailMode && options.EMailRemoveAfter)
    {
      tempDirectory.Create(kTempFolderPrefix);
      tempDirPrefix = tempDirectory.GetPath();
      NormalizeDirPathPrefix(tempDirPrefix);
      usesTempDir = true;
    }
    #endif
  
    CTempFiles tempFiles;
  
    bool createTempFile = false;
  
    if (!options.StdOutMode && options.UpdateArchiveItself)
    {
      CArchivePath &ap = options.Commands[0].ArchivePath;
      ap = options.ArchivePath;
      // if ((archive != 0 && !usesTempDir) || !options.WorkingDir.IsEmpty())
      if ((thereIsInArchive || !options.WorkingDir.IsEmpty()) && !usesTempDir && options.VolumesSizes.Size() == 0)
      {
        createTempFile = true;
        ap.Temp = true;
        if (!options.WorkingDir.IsEmpty())
          ap.TempPrefix = options.WorkingDir;
        else
          ap.TempPrefix = us2fs(ap.Prefix);
        NormalizeDirPathPrefix(ap.TempPrefix);
      }
    }
  
    unsigned i;
    for (i = 0; i < options.Commands.Size(); i++)
    {
      CArchivePath &ap = options.Commands[i].ArchivePath;
      if (usesTempDir)
      {
        // Check it
        ap.Prefix = fs2us(tempDirPrefix);
        // ap.Temp = true;
        // ap.TempPrefix = tempDirPrefix;
      }
      if (!options.StdOutMode &&
          (i > 0 || !createTempFile))
      {
        const FString path = us2fs(ap.GetFinalPath());
        if (NFind::DoesFileOrDirExist(path))
        {
          errorInfo.SystemError = ERROR_FILE_EXISTS;
          errorInfo.Message = "The file already exists";
          errorInfo.FileNames.Add(path);
          return errorInfo.Get_HRESULT_Error();
        }
      }
    }
  
    CObjectVector<CArcItem> arcItems;
    if (thereIsInArchive)
    {
      RINOK(EnumerateInArchiveItems(
        // options.StoreAltStreams,
        censor, arcLink.Arcs.Back(), arcItems));
    }
  
    /*
    FStringVector processedFilePaths;
    FStringVector *processedFilePaths_Ptr = NULL;
    if (options.DeleteAfterCompressing)
      processedFilePaths_Ptr = &processedFilePaths;
    */
  
    CByteBuffer processedItems;
    if (options.DeleteAfterCompressing)
    {
      unsigned num = dirItems.Items.Size();
      processedItems.Alloc(num);
      for (i = 0; i < num; i++)
        processedItems[i] = 0;
    }
  
    /*
    #ifndef _NO_CRYPTO
    if (arcLink.PasswordWasAsked)
    {
      // We set password, if open have requested password
      RINOK(callback->SetPassword(arcLink.Password));
    }
    #endif
    */
  
    for (i = 0; i < options.Commands.Size(); i++)
    {
      const CArc *arc = thereIsInArchive ? arcLink.GetArc() : NULL;
      CUpdateArchiveCommand &command = options.Commands[i];
      UString name;
      bool isUpdating;
      
      if (options.StdOutMode)
      {
        name.SetFromAscii("stdout");
        isUpdating = thereIsInArchive;
      }
      else
      {
        name = command.ArchivePath.GetFinalPath();
        isUpdating = (i == 0 && options.UpdateArchiveItself && thereIsInArchive);
      }
      
      RINOK(callback->StartArchive(name, isUpdating))
  
      CFinishArchiveStat st;
  
      RINOK(Compress(options,
          isUpdating,
          codecs,
          command.ActionSet,
          arc,
          command.ArchivePath,
          arcItems,
          options.DeleteAfterCompressing ? (Byte *)processedItems : NULL,
  
          dirItems,
          parentDirItem_Ptr,
  
          tempFiles,
          errorInfo, callback, st));
  
      RINOK(callback->FinishArchive(st));
    }
  
  
    if (thereIsInArchive)
    {
      RINOK(arcLink.Close());
      arcLink.Release();
    }
  
    tempFiles.Paths.Clear();
    if (createTempFile)
    {
      try
      {
        CArchivePath &ap = options.Commands[0].ArchivePath;
        const FString &tempPath = ap.GetTempPath();
        
        if (thereIsInArchive)
          if (!DeleteFileAlways(us2fs(arcPath)))
            return errorInfo.SetFromLastError("cannot delete the file", us2fs(arcPath));
        
        if (!MyMoveFile(tempPath, us2fs(arcPath)))
        {
          errorInfo.SetFromLastError("cannot move the file", tempPath);
          errorInfo.FileNames.Add(us2fs(arcPath));
          return errorInfo.Get_HRESULT_Error();
        }
      }
      catch(...)
      {
        throw;
      }
    }
  
  
    #if defined(_WIN32) && !defined(UNDER_CE)
    
    if (options.EMailMode)
    {
      NDLL::CLibrary mapiLib;
      if (!mapiLib.Load(FTEXT("Mapi32.dll")))
      {
        errorInfo.SetFromLastError("cannot load Mapi32.dll");
        return errorInfo.Get_HRESULT_Error();
      }
  
      /*
      LPMAPISENDDOCUMENTS fnSend = (LPMAPISENDDOCUMENTS)mapiLib.GetProc("MAPISendDocuments");
      if (fnSend == 0)
      {
        errorInfo.SetFromLastError)("7-Zip cannot find MAPISendDocuments function");
        return errorInfo.Get_HRESULT_Error();
      }
      */
      
      LPMAPISENDMAIL sendMail = (LPMAPISENDMAIL)mapiLib.GetProc("MAPISendMail");
      if (sendMail == 0)
      {
        errorInfo.SetFromLastError("7-Zip cannot find MAPISendMail function");
        return errorInfo.Get_HRESULT_Error();;
      }
  
      FStringVector fullPaths;
      unsigned i;
      
      for (i = 0; i < options.Commands.Size(); i++)
      {
        CArchivePath &ap = options.Commands[i].ArchivePath;
        FString finalPath = us2fs(ap.GetFinalPath());
        FString arcPath;
        if (!MyGetFullPathName(finalPath, arcPath))
          return errorInfo.SetFromLastError("GetFullPathName error", finalPath);
        fullPaths.Add(arcPath);
      }
  
      CCurrentDirRestorer curDirRestorer;
      
      for (i = 0; i < fullPaths.Size(); i++)
      {
        UString arcPath = fs2us(fullPaths[i]);
        UString fileName = ExtractFileNameFromPath(arcPath);
        AString path = GetAnsiString(arcPath);
        AString name = GetAnsiString(fileName);
        // Warning!!! MAPISendDocuments function changes Current directory
        // fnSend(0, ";", (LPSTR)(LPCSTR)path, (LPSTR)(LPCSTR)name, 0);
  
        MapiFileDesc f;
        memset(&f, 0, sizeof(f));
        f.nPosition = 0xFFFFFFFF;
        f.lpszPathName = (char *)(const char *)path;
        f.lpszFileName = (char *)(const char *)name;
        
        MapiMessage m;
        memset(&m, 0, sizeof(m));
        m.nFileCount = 1;
        m.lpFiles = &f;
        
        const AString addr = GetAnsiString(options.EMailAddress);
        MapiRecipDesc rec;
        if (!addr.IsEmpty())
        {
          memset(&rec, 0, sizeof(rec));
          rec.ulRecipClass = MAPI_TO;
          rec.lpszAddress = (char *)(const char *)addr;
          m.nRecipCount = 1;
          m.lpRecips = &rec;
        }
        
        sendMail((LHANDLE)0, 0, &m, MAPI_DIALOG, 0);
      }
    }
    
    #endif
  
    if (options.DeleteAfterCompressing)
    {
      CRecordVector<CRefSortPair> pairs;
      FStringVector foldersNames;
  
      for (i = 0; i < dirItems.Items.Size(); i++)
      {
        const CDirItem &dirItem = dirItems.Items[i];
        FString phyPath = dirItems.GetPhyPath(i);
        if (dirItem.IsDir())
        {
          CRefSortPair pair;
          pair.Index = i;
          pair.Len = GetNumSlashes(phyPath);
          pairs.Add(pair);
        }
        else
        {
          if (processedItems[i] != 0 || dirItem.Size == 0)
          {
            RINOK(callback->DeletingAfterArchiving(phyPath, false));
            DeleteFileAlways(phyPath);
          }
          else
          {
            // file was skipped
            /*
            errorInfo.SystemError = 0;
            errorInfo.Message = "file was not processed";
            errorInfo.FileName = phyPath;
            return E_FAIL;
            */
          }
        }
      }
  
      pairs.Sort(CompareRefSortPair, NULL);
      
      for (i = 0; i < pairs.Size(); i++)
      {
        FString phyPath = dirItems.GetPhyPath(pairs[i].Index);
        if (NFind::DoesDirExist(phyPath))
        {
          RINOK(callback->DeletingAfterArchiving(phyPath, true));
          RemoveDir(phyPath);
        }
      }
  
      RINOK(callback->FinishDeletingAfterArchiving());
    }
  
    return S_OK;
  }
  