Imported Upstream version 9.20
[platform/upstream/7zip.git] / CPP / 7zip / Archive / Tar / TarHandler.cpp
1 // TarHandler.cpp\r
2 \r
3 #include "StdAfx.h"\r
4 \r
5 #include "Common/ComTry.h"\r
6 #include "Common/StringConvert.h"\r
7 \r
8 #include "Windows/PropVariant.h"\r
9 #include "Windows/Time.h"\r
10 \r
11 #include "../../Common/LimitedStreams.h"\r
12 #include "../../Common/ProgressUtils.h"\r
13 #include "../../Common/StreamObjects.h"\r
14 #include "../../Common/StreamUtils.h"\r
15 \r
16 #include "../Common/ItemNameUtils.h"\r
17 \r
18 #include "TarHandler.h"\r
19 #include "TarIn.h"\r
20 \r
21 using namespace NWindows;\r
22 \r
23 namespace NArchive {\r
24 namespace NTar {\r
25 \r
26 static const char *kUnexpectedEnd = "Unexpected end of archive";\r
27 \r
28 static const STATPROPSTG kProps[] =\r
29 {\r
30   { NULL, kpidPath, VT_BSTR},\r
31   { NULL, kpidIsDir, VT_BOOL},\r
32   { NULL, kpidSize, VT_UI8},\r
33   { NULL, kpidPackSize, VT_UI8},\r
34   { NULL, kpidMTime, VT_FILETIME},\r
35   { NULL, kpidPosixAttrib, VT_UI4},\r
36   { NULL, kpidUser, VT_BSTR},\r
37   { NULL, kpidGroup, VT_BSTR},\r
38   { NULL, kpidLink, VT_BSTR}\r
39 };\r
40 \r
41 static const STATPROPSTG kArcProps[] =\r
42 {\r
43   { NULL, kpidPhySize, VT_UI8},\r
44   { NULL, kpidHeadersSize, VT_UI8}\r
45 };\r
46 \r
47 IMP_IInArchive_Props\r
48 IMP_IInArchive_ArcProps\r
49 \r
50 STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)\r
51 {\r
52   NCOM::CPropVariant prop;\r
53   switch(propID)\r
54   {\r
55     case kpidPhySize: if (_phySizeDefined) prop = _phySize; break;\r
56     case kpidHeadersSize: if (_phySizeDefined) prop = _headersSize; break;\r
57     case kpidError: if (!_errorMessage.IsEmpty()) prop = _errorMessage; break;\r
58   }\r
59   prop.Detach(value);\r
60   return S_OK;\r
61 }\r
62 \r
63 HRESULT CHandler::ReadItem2(ISequentialInStream *stream, bool &filled, CItemEx &item)\r
64 {\r
65   item.HeaderPos = _phySize;\r
66   RINOK(ReadItem(stream, filled, item, _errorMessage));\r
67   _phySize += item.HeaderSize;\r
68   _headersSize += item.HeaderSize;\r
69   return S_OK;\r
70 }\r
71 \r
72 HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback)\r
73 {\r
74   UInt64 endPos = 0;\r
75   {\r
76     RINOK(stream->Seek(0, STREAM_SEEK_END, &endPos));\r
77     RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL));\r
78   }\r
79   \r
80   _phySizeDefined = true;\r
81   for (;;)\r
82   {\r
83     CItemEx item;\r
84     bool filled;\r
85     RINOK(ReadItem2(stream, filled, item));\r
86     if (!filled)\r
87       break;\r
88     _items.Add(item);\r
89     \r
90     RINOK(stream->Seek(item.GetPackSize(), STREAM_SEEK_CUR, &_phySize));\r
91     if (_phySize > endPos)\r
92     {\r
93       _errorMessage = kUnexpectedEnd;\r
94       break;\r
95     }\r
96     /*\r
97     if (_phySize == endPos)\r
98     {\r
99       _errorMessage = "There are no trailing zero-filled records";\r
100       break;\r
101     }\r
102     */\r
103     if (callback != NULL)\r
104     {\r
105       if (_items.Size() == 1)\r
106       {\r
107         RINOK(callback->SetTotal(NULL, &endPos));\r
108       }\r
109       if (_items.Size() % 100 == 0)\r
110       {\r
111         UInt64 numFiles = _items.Size();\r
112         RINOK(callback->SetCompleted(&numFiles, &_phySize));\r
113       }\r
114     }\r
115   }\r
116 \r
117   if (_items.Size() == 0)\r
118   {\r
119     CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;\r
120     if (!callback)\r
121       return S_FALSE;\r
122     callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback);\r
123     if (!openVolumeCallback)\r
124       return S_FALSE;\r
125     NCOM::CPropVariant prop;\r
126     if (openVolumeCallback->GetProperty(kpidName, &prop) != S_OK)\r
127       return S_FALSE;\r
128     if (prop.vt != VT_BSTR)\r
129       return S_FALSE;\r
130     UString baseName = prop.bstrVal;\r
131     baseName = baseName.Right(4);\r
132     if (baseName.CompareNoCase(L".tar") != 0)\r
133       return S_FALSE;\r
134   }\r
135   return S_OK;\r
136 }\r
137 \r
138 STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *openArchiveCallback)\r
139 {\r
140   COM_TRY_BEGIN\r
141   {\r
142     Close();\r
143     RINOK(Open2(stream, openArchiveCallback));\r
144     _stream = stream;\r
145   }\r
146   return S_OK;\r
147   COM_TRY_END\r
148 }\r
149 \r
150 STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream)\r
151 {\r
152   Close();\r
153   _seqStream = stream;\r
154   return S_OK;\r
155 }\r
156 \r
157 STDMETHODIMP CHandler::Close()\r
158 {\r
159   _errorMessage.Empty();\r
160   _phySizeDefined = false;\r
161   _phySize = 0;\r
162   _headersSize = 0;\r
163   _curIndex = 0;\r
164   _latestIsRead = false;\r
165   _items.Clear();\r
166   _seqStream.Release();\r
167   _stream.Release();\r
168   return S_OK;\r
169 }\r
170 \r
171 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)\r
172 {\r
173   *numItems = (_stream ? _items.Size() : (UInt32)(Int32)-1);\r
174   return S_OK;\r
175 }\r
176 \r
177 CHandler::CHandler()\r
178 {\r
179   copyCoderSpec = new NCompress::CCopyCoder();\r
180   copyCoder = copyCoderSpec;\r
181 }\r
182 \r
183 HRESULT CHandler::SkipTo(UInt32 index)\r
184 {\r
185   while (_curIndex < index || !_latestIsRead)\r
186   {\r
187     if (_latestIsRead)\r
188     {\r
189       UInt64 packSize = _latestItem.GetPackSize();\r
190       RINOK(copyCoderSpec->Code(_seqStream, NULL, &packSize, &packSize, NULL));\r
191       _phySize += copyCoderSpec->TotalSize;\r
192       if (copyCoderSpec->TotalSize != packSize)\r
193       {\r
194         _errorMessage = kUnexpectedEnd;\r
195         return S_FALSE;\r
196       }\r
197       _latestIsRead = false;\r
198       _curIndex++;\r
199     }\r
200     else\r
201     {\r
202       bool filled;\r
203       RINOK(ReadItem2(_seqStream, filled, _latestItem));\r
204       if (!filled)\r
205       {\r
206         _phySizeDefined = true;\r
207         return E_INVALIDARG;\r
208       }\r
209       _latestIsRead = true;\r
210     }\r
211   }\r
212   return S_OK;\r
213 }\r
214 \r
215 static UString TarStringToUnicode(const AString &s)\r
216 {\r
217   return MultiByteToUnicodeString(s, CP_OEMCP);\r
218 }\r
219 \r
220 STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)\r
221 {\r
222   COM_TRY_BEGIN\r
223   NWindows::NCOM::CPropVariant prop;\r
224 \r
225   const CItemEx *item;\r
226   if (_stream)\r
227     item = &_items[index];\r
228   else\r
229   {\r
230     if (index < _curIndex)\r
231       return E_INVALIDARG;\r
232     else\r
233     {\r
234       RINOK(SkipTo(index));\r
235       item = &_latestItem;\r
236     }\r
237   }\r
238 \r
239   switch(propID)\r
240   {\r
241     case kpidPath: prop = NItemName::GetOSName2(TarStringToUnicode(item->Name)); break;\r
242     case kpidIsDir: prop = item->IsDir(); break;\r
243     case kpidSize: prop = item->GetUnpackSize(); break;\r
244     case kpidPackSize: prop = item->GetPackSize(); break;\r
245     case kpidMTime:\r
246       if (item->MTime != 0)\r
247       {\r
248         FILETIME ft;\r
249         NTime::UnixTimeToFileTime(item->MTime, ft);\r
250         prop = ft;\r
251       }\r
252       break;\r
253     case kpidPosixAttrib: prop = item->Mode; break;\r
254     case kpidUser: prop = TarStringToUnicode(item->User); break;\r
255     case kpidGroup: prop = TarStringToUnicode(item->Group); break;\r
256     case kpidLink: prop = TarStringToUnicode(item->LinkName); break;\r
257   }\r
258   prop.Detach(value);\r
259   return S_OK;\r
260   COM_TRY_END\r
261 }\r
262 \r
263 HRESULT CHandler::Extract(const UInt32 *indices, UInt32 numItems,\r
264     Int32 testMode, IArchiveExtractCallback *extractCallback)\r
265 {\r
266   COM_TRY_BEGIN\r
267   ISequentialInStream *stream = _seqStream;\r
268   bool seqMode = (_stream == NULL);\r
269   if (!seqMode)\r
270     stream = _stream;\r
271 \r
272   bool allFilesMode = (numItems == (UInt32)-1);\r
273   if (allFilesMode)\r
274     numItems = _items.Size();\r
275   if (_stream && numItems == 0)\r
276     return S_OK;\r
277   UInt64 totalSize = 0;\r
278   UInt32 i;\r
279   for (i = 0; i < numItems; i++)\r
280     totalSize += _items[allFilesMode ? i : indices[i]].GetUnpackSize();\r
281   extractCallback->SetTotal(totalSize);\r
282 \r
283   UInt64 totalPackSize;\r
284   totalSize = totalPackSize = 0;\r
285   \r
286   CLocalProgress *lps = new CLocalProgress;\r
287   CMyComPtr<ICompressProgressInfo> progress = lps;\r
288   lps->Init(extractCallback, false);\r
289 \r
290   CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;\r
291   CMyComPtr<ISequentialInStream> inStream(streamSpec);\r
292   streamSpec->SetStream(stream);\r
293 \r
294   CLimitedSequentialOutStream *outStreamSpec = new CLimitedSequentialOutStream;\r
295   CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);\r
296 \r
297   for (i = 0; i < numItems || seqMode; i++)\r
298   {\r
299     lps->InSize = totalPackSize;\r
300     lps->OutSize = totalSize;\r
301     RINOK(lps->SetCur());\r
302     CMyComPtr<ISequentialOutStream> realOutStream;\r
303     Int32 askMode = testMode ?\r
304         NExtract::NAskMode::kTest :\r
305         NExtract::NAskMode::kExtract;\r
306     Int32 index = allFilesMode ? i : indices[i];\r
307     const CItemEx *item;\r
308     if (seqMode)\r
309     {\r
310       HRESULT res = SkipTo(index);\r
311       if (res == E_INVALIDARG)\r
312         break;\r
313       RINOK(res);\r
314       item = &_latestItem;\r
315     }\r
316     else\r
317       item = &_items[index];\r
318 \r
319     RINOK(extractCallback->GetStream(index, &realOutStream, askMode));\r
320     UInt64 unpackSize = item->GetUnpackSize();\r
321     totalSize += unpackSize;\r
322     totalPackSize += item->GetPackSize();\r
323     if (item->IsDir())\r
324     {\r
325       RINOK(extractCallback->PrepareOperation(askMode));\r
326       RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));\r
327       continue;\r
328     }\r
329     bool skipMode = false;\r
330     if (!testMode && !realOutStream)\r
331     {\r
332       if (!seqMode)\r
333         continue;\r
334       skipMode = true;\r
335       askMode = NExtract::NAskMode::kSkip;\r
336     }\r
337     RINOK(extractCallback->PrepareOperation(askMode));\r
338 \r
339     outStreamSpec->SetStream(realOutStream);\r
340     realOutStream.Release();\r
341     outStreamSpec->Init(skipMode ? 0 : unpackSize, true);\r
342 \r
343     if (item->IsLink())\r
344     {\r
345       RINOK(WriteStream(outStreamSpec, (const char *)item->LinkName, item->LinkName.Length()));\r
346     }\r
347     else\r
348     {\r
349       if (!seqMode)\r
350       {\r
351         RINOK(_stream->Seek(item->GetDataPosition(), STREAM_SEEK_SET, NULL));\r
352       }\r
353       streamSpec->Init(item->GetPackSize());\r
354       RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress));\r
355     }\r
356     if (seqMode)\r
357     {\r
358       _latestIsRead = false;\r
359       _curIndex++;\r
360     }\r
361     outStreamSpec->ReleaseStream();\r
362     RINOK(extractCallback->SetOperationResult(outStreamSpec->GetRem() == 0 ?\r
363         NExtract::NOperationResult::kOK:\r
364         NExtract::NOperationResult::kDataError));\r
365   }\r
366   return S_OK;\r
367   COM_TRY_END\r
368 }\r
369 \r
370 STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream)\r
371 {\r
372   COM_TRY_BEGIN\r
373   const CItemEx &item = _items[index];\r
374   if (item.IsLink())\r
375   {\r
376     CBufInStream *streamSpec = new CBufInStream;\r
377     CMyComPtr<IInStream> streamTemp = streamSpec;\r
378     streamSpec->Init((const Byte *)(const char *)item.LinkName, item.LinkName.Length(), (IInArchive *)this);\r
379     *stream = streamTemp.Detach();\r
380     return S_OK;\r
381   }\r
382   return CreateLimitedInStream(_stream, item.GetDataPosition(), item.Size, stream);\r
383   COM_TRY_END\r
384 }\r
385 \r
386 }}\r