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