1 // UdfHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "Common/ComTry.h"
6 #include "Common/NewHandler.h"
7 
8 #include "Windows/PropVariant.h"
9 #include "Windows/Time.h"
10 
11 #include "../../Common/LimitedStreams.h"
12 #include "../../Common/ProgressUtils.h"
13 
14 #include "../../Compress/CopyCoder.h"
15 
16 #include "UdfHandler.h"
17 
18 namespace NArchive {
19 namespace NUdf {
20 
UdfTimeToFileTime(const CTime & t,NWindows::NCOM::CPropVariant & prop)21 void UdfTimeToFileTime(const CTime &t, NWindows::NCOM::CPropVariant &prop)
22 {
23   UInt64 numSecs;
24   const Byte *d = t.Data;
25   if (!NWindows::NTime::GetSecondsSince1601(t.GetYear(), d[4], d[5], d[6], d[7], d[8], numSecs))
26     return;
27   if (t.IsLocal())
28     numSecs -= t.GetMinutesOffset() * 60;
29   FILETIME ft;
30   UInt64 v = (((numSecs * 100 + d[9]) * 100 + d[10]) * 100 + d[11]) * 10;
31   ft.dwLowDateTime = (UInt32)v;
32   ft.dwHighDateTime = (UInt32)(v >> 32);
33   prop = ft;
34 }
35 
36 STATPROPSTG kProps[] =
37 {
38   { NULL, kpidPath, VT_BSTR},
39   { NULL, kpidIsDir, VT_BOOL},
40   { NULL, kpidSize, VT_UI8},
41   { NULL, kpidPackSize, VT_UI8},
42   { NULL, kpidMTime, VT_FILETIME},
43   { NULL, kpidATime, VT_FILETIME}
44 };
45 
46 STATPROPSTG kArcProps[] =
47 {
48   { NULL, kpidComment, VT_BSTR},
49   { NULL, kpidClusterSize, VT_UI4},
50   { NULL, kpidCTime, VT_FILETIME}
51 };
52 
53 IMP_IInArchive_Props
54 IMP_IInArchive_ArcProps
55 
GetArchiveProperty(PROPID propID,PROPVARIANT * value)56 STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
57 {
58   COM_TRY_BEGIN
59   NWindows::NCOM::CPropVariant prop;
60   switch(propID)
61   {
62     case kpidComment:
63     {
64       UString comment = _archive.GetComment();
65       if (!comment.IsEmpty())
66         prop = comment;
67       break;
68     }
69 
70     case kpidClusterSize:
71       if (_archive.LogVols.Size() > 0)
72       {
73         UInt32 blockSize = _archive.LogVols[0].BlockSize;
74         int i;
75         for (i = 1; i < _archive.LogVols.Size(); i++)
76           if (_archive.LogVols[i].BlockSize != blockSize)
77             break;
78         if (i == _archive.LogVols.Size())
79           prop = blockSize;
80       }
81       break;
82 
83     case kpidCTime:
84       if (_archive.LogVols.Size() == 1)
85       {
86         const CLogVol &vol = _archive.LogVols[0];
87         if (vol.FileSets.Size() >= 1)
88           UdfTimeToFileTime(vol.FileSets[0].RecodringTime, prop);
89       }
90       break;
91   }
92   prop.Detach(value);
93   return S_OK;
94   COM_TRY_END
95 }
96 
97 class CProgressImp: public CProgressVirt
98 {
99   CMyComPtr<IArchiveOpenCallback> _callback;
100   UInt64 _numFiles;
101   UInt64 _numBytes;
102 public:
103   HRESULT SetTotal(UInt64 numBytes);
104   HRESULT SetCompleted(UInt64 numFiles, UInt64 numBytes);
105   HRESULT SetCompleted();
CProgressImp(IArchiveOpenCallback * callback)106   CProgressImp(IArchiveOpenCallback *callback): _callback(callback), _numFiles(0), _numBytes(0) {}
107 };
108 
SetTotal(UInt64 numBytes)109 HRESULT CProgressImp::SetTotal(UInt64 numBytes)
110 {
111   if (_callback)
112     return _callback->SetTotal(NULL, &numBytes);
113   return S_OK;
114 }
115 
SetCompleted(UInt64 numFiles,UInt64 numBytes)116 HRESULT CProgressImp::SetCompleted(UInt64 numFiles, UInt64 numBytes)
117 {
118   _numFiles = numFiles;
119   _numBytes = numBytes;
120   return SetCompleted();
121 }
122 
SetCompleted()123 HRESULT CProgressImp::SetCompleted()
124 {
125   if (_callback)
126     return _callback->SetCompleted(&_numFiles, &_numBytes);
127   return S_OK;
128 }
129 
Open(IInStream * stream,const UInt64 *,IArchiveOpenCallback * callback)130 STDMETHODIMP CHandler::Open(IInStream *stream,
131     const UInt64 * /* maxCheckStartPosition */,
132     IArchiveOpenCallback *callback)
133 {
134   COM_TRY_BEGIN
135   {
136     Close();
137     CProgressImp progressImp(callback);
138     RINOK(_archive.Open(stream, &progressImp));
139     bool showVolName = (_archive.LogVols.Size() > 1);
140     for (int volIndex = 0; volIndex < _archive.LogVols.Size(); volIndex++)
141     {
142       const CLogVol &vol = _archive.LogVols[volIndex];
143       bool showFileSetName = (vol.FileSets.Size() > 1);
144       for (int fsIndex = 0; fsIndex < vol.FileSets.Size(); fsIndex++)
145       {
146         const CFileSet &fs = vol.FileSets[fsIndex];
147         for (int i = ((showVolName || showFileSetName) ? 0 : 1); i < fs.Refs.Size(); i++)
148         {
149           CRef2 ref2;
150           ref2.Vol = volIndex;
151           ref2.Fs = fsIndex;
152           ref2.Ref = i;
153           _refs2.Add(ref2);
154         }
155       }
156     }
157     _inStream = stream;
158   }
159   return S_OK;
160   COM_TRY_END
161 }
162 
Close()163 STDMETHODIMP CHandler::Close()
164 {
165   _inStream.Release();
166   _archive.Clear();
167   _refs2.Clear();
168   return S_OK;
169 }
170 
GetNumberOfItems(UInt32 * numItems)171 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
172 {
173   *numItems = _refs2.Size();
174   return S_OK;
175 }
176 
GetProperty(UInt32 index,PROPID propID,PROPVARIANT * value)177 STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
178 {
179   COM_TRY_BEGIN
180   NWindows::NCOM::CPropVariant prop;
181   {
182     const CRef2 &ref2 = _refs2[index];
183     const CLogVol &vol = _archive.LogVols[ref2.Vol];
184     const CRef &ref = vol.FileSets[ref2.Fs].Refs[ref2.Ref];
185     const CFile &file = _archive.Files[ref.FileIndex];
186     const CItem &item = _archive.Items[file.ItemIndex];
187     switch(propID)
188     {
189       case kpidPath:  prop = _archive.GetItemPath(ref2.Vol, ref2.Fs, ref2.Ref,
190             _archive.LogVols.Size() > 1, vol.FileSets.Size() > 1); break;
191       case kpidIsDir:  prop = item.IsDir(); break;
192       case kpidSize:      if (!item.IsDir()) prop = (UInt64)item.Size; break;
193       case kpidPackSize:  if (!item.IsDir()) prop = (UInt64)item.NumLogBlockRecorded * vol.BlockSize; break;
194       case kpidMTime:  UdfTimeToFileTime(item.MTime, prop); break;
195       case kpidATime:  UdfTimeToFileTime(item.ATime, prop); break;
196     }
197   }
198   prop.Detach(value);
199   return S_OK;
200   COM_TRY_END
201 }
202 
203 class CBufInStream:
204   public IInStream,
205   public CMyUnknownImp
206 {
207   CByteBuffer _data;
208   UInt64 _pos;
209 
210 public:
Init(const CByteBuffer & data)211   void Init(const CByteBuffer &data)
212   {
213     _data = data;
214     _pos = 0;
215   }
216 
217   MY_UNKNOWN_IMP
218 
219   STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
220   STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition);
221 };
222 
223 
Read(void * data,UInt32 size,UInt32 * processedSize)224 STDMETHODIMP CBufInStream::Read(void *data, UInt32 size, UInt32 *processedSize)
225 {
226   if (processedSize != NULL)
227     *processedSize = 0;
228   if (_pos > _data.GetCapacity())
229     return E_FAIL;
230   size_t rem = _data.GetCapacity() - (size_t)_pos;
231   if (size < rem)
232     rem = (size_t)size;
233   memcpy(data, (const Byte *)_data + _pos, rem);
234   _pos += rem;
235   if (processedSize != NULL)
236     *processedSize = (UInt32)rem;
237   return S_OK;
238 }
239 
Seek(Int64 offset,UInt32 seekOrigin,UInt64 * newPosition)240 STDMETHODIMP CBufInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)
241 {
242   switch(seekOrigin)
243   {
244     case STREAM_SEEK_SET: _pos = offset; break;
245     case STREAM_SEEK_CUR: _pos += offset; break;
246     case STREAM_SEEK_END: _pos = _data.GetCapacity() + offset; break;
247     default: return STG_E_INVALIDFUNCTION;
248   }
249   if (newPosition)
250     *newPosition = _pos;
251   return S_OK;
252 }
253 
254 struct CSeekExtent
255 {
256   UInt64 Phy;
257   UInt64 Virt;
258 };
259 
260 class CExtentsStream:
261   public IInStream,
262   public CMyUnknownImp
263 {
264   UInt64 _phyPos;
265   UInt64 _virtPos;
266   bool _needStartSeek;
267 
SeekToPhys()268   HRESULT SeekToPhys() { return Stream->Seek(_phyPos, STREAM_SEEK_SET, NULL); }
269 
270 public:
271   CMyComPtr<IInStream> Stream;
272   CRecordVector<CSeekExtent> Extents;
273 
274   MY_UNKNOWN_IMP1(IInStream)
275   STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
276   STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition);
ReleaseStream()277   void ReleaseStream() { Stream.Release(); }
278 
Init()279   void Init()
280   {
281     _virtPos = 0;
282     _phyPos = 0;
283     _needStartSeek = true;
284   }
285 
286 };
287 
288 
Read(void * data,UInt32 size,UInt32 * processedSize)289 STDMETHODIMP CExtentsStream::Read(void *data, UInt32 size, UInt32 *processedSize)
290 {
291   if (processedSize)
292     *processedSize = 0;
293   if (size > 0)
294   {
295     UInt64 totalSize = Extents.Back().Virt;
296     if (_virtPos >= totalSize)
297       return (_virtPos == totalSize) ? S_OK : E_FAIL;
298     int left = 0, right = Extents.Size() - 1;
299     for (;;)
300     {
301       int mid = (left + right) / 2;
302       if (mid == left)
303         break;
304       if (_virtPos < Extents[mid].Virt)
305         right = mid;
306       else
307         left = mid;
308     }
309 
310     const CSeekExtent &extent = Extents[left];
311     UInt64 phyPos = extent.Phy + (_virtPos - extent.Virt);
312     if (_needStartSeek || _phyPos != phyPos)
313     {
314       _needStartSeek = false;
315       _phyPos = phyPos;
316       RINOK(SeekToPhys());
317     }
318 
319     UInt64 rem = Extents[left + 1].Virt - _virtPos;
320     if (size > rem)
321       size = (UInt32)rem;
322 
323     HRESULT res = Stream->Read(data, size, &size);
324     _phyPos += size;
325     _virtPos += size;
326     if (processedSize)
327       *processedSize = size;
328     return res;
329   }
330   return S_OK;
331 }
332 
Seek(Int64 offset,UInt32 seekOrigin,UInt64 * newPosition)333 STDMETHODIMP CExtentsStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)
334 {
335   switch(seekOrigin)
336   {
337     case STREAM_SEEK_SET: _virtPos = offset; break;
338     case STREAM_SEEK_CUR: _virtPos += offset; break;
339     case STREAM_SEEK_END: _virtPos = Extents.Back().Virt + offset; break;
340     default: return STG_E_INVALIDFUNCTION;
341   }
342   if (newPosition)
343     *newPosition = _virtPos;
344   return S_OK;
345 }
346 
GetStream(UInt32 index,ISequentialInStream ** stream)347 STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream)
348 {
349   *stream = 0;
350 
351   const CRef2 &ref2 = _refs2[index];
352   const CLogVol &vol = _archive.LogVols[ref2.Vol];
353   const CRef &ref = vol.FileSets[ref2.Fs].Refs[ref2.Ref];
354   const CFile &file = _archive.Files[ref.FileIndex];
355   const CItem &item = _archive.Items[file.ItemIndex];
356   UInt64 size = item.Size;
357 
358   if (!item.IsRecAndAlloc() || !item.CheckChunkSizes() || ! _archive.CheckItemExtents(ref2.Vol, item))
359     return E_NOTIMPL;
360 
361   if (item.IsInline)
362   {
363     CBufInStream *inStreamSpec = new CBufInStream;
364     CMyComPtr<ISequentialInStream> inStream = inStreamSpec;
365     inStreamSpec->Init(item.InlineData);
366     *stream = inStream .Detach();
367     return S_OK;
368   }
369 
370   CExtentsStream *extentStreamSpec = new CExtentsStream();
371   CMyComPtr<ISequentialInStream> extentStream = extentStreamSpec;
372 
373   extentStreamSpec->Stream = _inStream;
374 
375   UInt64 virtOffset = 0;
376   for (int extentIndex = 0; extentIndex < item.Extents.Size(); extentIndex++)
377   {
378     const CMyExtent &extent = item.Extents[extentIndex];
379     UInt32 len = extent.GetLen();
380     if (len == 0)
381       continue;
382     if (size < len)
383       return S_FALSE;
384 
385     int partitionIndex = vol.PartitionMaps[extent.PartitionRef].PartitionIndex;
386     UInt32 logBlockNumber = extent.Pos;
387     const CPartition &partition = _archive.Partitions[partitionIndex];
388     UInt64 offset = ((UInt64)partition.Pos << _archive.SecLogSize) +
389       (UInt64)logBlockNumber * vol.BlockSize;
390 
391     CSeekExtent se;
392     se.Phy = offset;
393     se.Virt = virtOffset;
394     virtOffset += len;
395     extentStreamSpec->Extents.Add(se);
396 
397     size -= len;
398   }
399   if (size != 0)
400     return S_FALSE;
401   CSeekExtent se;
402   se.Phy = 0;
403   se.Virt = virtOffset;
404   extentStreamSpec->Extents.Add(se);
405   extentStreamSpec->Init();
406   *stream = extentStream.Detach();
407   return S_OK;
408 }
409 
Extract(const UInt32 * indices,UInt32 numItems,Int32 _aTestMode,IArchiveExtractCallback * extractCallback)410 STDMETHODIMP CHandler::Extract(const UInt32* indices, UInt32 numItems,
411     Int32 _aTestMode, IArchiveExtractCallback *extractCallback)
412 {
413   COM_TRY_BEGIN
414   bool testMode = (_aTestMode != 0);
415   bool allFilesMode = (numItems == UInt32(-1));
416   if (allFilesMode)
417     numItems = _refs2.Size();
418   if (numItems == 0)
419     return S_OK;
420   UInt64 totalSize = 0;
421   UInt32 i;
422 
423   for (i = 0; i < numItems; i++)
424   {
425     UInt32 index = (allFilesMode ? i : indices[i]);
426     const CRef2 &ref2 = _refs2[index];
427     const CRef &ref = _archive.LogVols[ref2.Vol].FileSets[ref2.Fs].Refs[ref2.Ref];
428     const CFile &file = _archive.Files[ref.FileIndex];
429     const CItem &item = _archive.Items[file.ItemIndex];
430     if (!item.IsDir())
431       totalSize += item.Size;
432   }
433   extractCallback->SetTotal(totalSize);
434 
435   UInt64 currentTotalSize = 0;
436 
437   NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
438   CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
439 
440   CLocalProgress *lps = new CLocalProgress;
441   CMyComPtr<ICompressProgressInfo> progress = lps;
442   lps->Init(extractCallback, false);
443 
444   CLimitedSequentialOutStream *outStreamSpec = new CLimitedSequentialOutStream;
445   CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);
446 
447   for (i = 0; i < numItems; i++)
448   {
449     lps->InSize = lps->OutSize = currentTotalSize;
450     RINOK(lps->SetCur());
451     CMyComPtr<ISequentialOutStream> realOutStream;
452     Int32 askMode = testMode ?
453         NArchive::NExtract::NAskMode::kTest :
454         NArchive::NExtract::NAskMode::kExtract;
455     UInt32 index = allFilesMode ? i : indices[i];
456 
457     RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
458 
459     const CRef2 &ref2 = _refs2[index];
460     const CRef &ref = _archive.LogVols[ref2.Vol].FileSets[ref2.Fs].Refs[ref2.Ref];
461     const CFile &file = _archive.Files[ref.FileIndex];
462     const CItem &item = _archive.Items[file.ItemIndex];
463 
464     if (item.IsDir())
465     {
466       RINOK(extractCallback->PrepareOperation(askMode));
467       RINOK(extractCallback->SetOperationResult(NArchive::NExtract::NOperationResult::kOK));
468       continue;
469     }
470     currentTotalSize += item.Size;
471 
472     if (!testMode && !realOutStream)
473       continue;
474 
475     RINOK(extractCallback->PrepareOperation(askMode));
476     outStreamSpec->SetStream(realOutStream);
477     realOutStream.Release();
478     outStreamSpec->Init(item.Size);
479     Int32 opRes;
480     CMyComPtr<ISequentialInStream> udfInStream;
481     HRESULT res = GetStream(index, &udfInStream);
482     if (res == E_NOTIMPL)
483       opRes = NArchive::NExtract::NOperationResult::kUnSupportedMethod;
484     else if (res != S_OK)
485       opRes = NArchive::NExtract::NOperationResult::kDataError;
486     else
487     {
488       RINOK(copyCoder->Code(udfInStream, outStream, NULL, NULL, progress));
489       opRes = outStreamSpec->IsFinishedOK() ?
490         NArchive::NExtract::NOperationResult::kOK:
491         NArchive::NExtract::NOperationResult::kDataError;
492     }
493     outStreamSpec->ReleaseStream();
494     RINOK(extractCallback->SetOperationResult(opRes));
495   }
496   return S_OK;
497   COM_TRY_END
498 }
499 
500 }}
501