]> git.saurik.com Git - apt.git/blob - apt-inst/contrib/extracttar.cc
apt-helper: Add a cat-file command for concatening files
[apt.git] / apt-inst / contrib / extracttar.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: extracttar.cc,v 1.8.2.1 2004/01/16 18:58:50 mdz Exp $
4 /* ######################################################################
5
6 Extract a Tar - Tar Extractor
7
8 Some performance measurements showed that zlib performed quite poorly
9 in comparison to a forked gzip process. This tar extractor makes use
10 of the fact that dup'd file descriptors have the same seek pointer
11 and that gzip will not read past the end of a compressed stream,
12 even if there is more data. We use the dup property to track extraction
13 progress and the gzip feature to just feed gzip a fd in the middle
14 of an AR file.
15
16 ##################################################################### */
17 /*}}}*/
18 // Include Files /*{{{*/
19 #include<config.h>
20
21 #include <apt-pkg/dirstream.h>
22 #include <apt-pkg/extracttar.h>
23 #include <apt-pkg/error.h>
24 #include <apt-pkg/strutl.h>
25 #include <apt-pkg/configuration.h>
26 #include <apt-pkg/fileutl.h>
27
28 #include <string.h>
29 #include <algorithm>
30 #include <string>
31 #include <unistd.h>
32 #include <signal.h>
33 #include <fcntl.h>
34 #include <iostream>
35
36 #include <apti18n.h>
37 /*}}}*/
38
39 using namespace std;
40
41 // The on disk header for a tar file.
42 struct ExtractTar::TarHeader
43 {
44 char Name[100];
45 char Mode[8];
46 char UserID[8];
47 char GroupID[8];
48 char Size[12];
49 char MTime[12];
50 char Checksum[8];
51 char LinkFlag;
52 char LinkName[100];
53 char MagicNumber[8];
54 char UserName[32];
55 char GroupName[32];
56 char Major[8];
57 char Minor[8];
58 };
59
60 // ExtractTar::ExtractTar - Constructor /*{{{*/
61 // ---------------------------------------------------------------------
62 /* */
63 ExtractTar::ExtractTar(FileFd &Fd,unsigned long long Max,string DecompressionProgram)
64 : File(Fd), MaxInSize(Max), DecompressProg(DecompressionProgram)
65 {
66 GZPid = -1;
67 Eof = false;
68 }
69 /*}}}*/
70 // ExtractTar::ExtractTar - Destructor /*{{{*/
71 // ---------------------------------------------------------------------
72 /* */
73 ExtractTar::~ExtractTar()
74 {
75 // Error close
76 Done();
77 }
78 /*}}}*/
79 // ExtractTar::Done - Reap the gzip sub process /*{{{*/
80 bool ExtractTar::Done(bool)
81 {
82 return Done();
83 }
84 bool ExtractTar::Done()
85 {
86 return InFd.Close();
87 }
88 /*}}}*/
89 // ExtractTar::StartGzip - Startup gzip /*{{{*/
90 // ---------------------------------------------------------------------
91 /* This creates a gzip sub process that has its input as the file itself.
92 If this tar file is embedded into something like an ar file then
93 gzip will efficiently ignore the extra bits. */
94 bool ExtractTar::StartGzip()
95 {
96 if (DecompressProg.empty())
97 {
98 InFd.OpenDescriptor(File.Fd(), FileFd::ReadOnly, FileFd::None, false);
99 return true;
100 }
101
102 std::vector<APT::Configuration::Compressor> const compressors = APT::Configuration::getCompressors();
103 std::vector<APT::Configuration::Compressor>::const_iterator compressor = compressors.begin();
104 for (; compressor != compressors.end(); ++compressor) {
105 if (compressor->Name == DecompressProg) {
106 return InFd.OpenDescriptor(File.Fd(), FileFd::ReadOnly, *compressor, false);
107 }
108 }
109
110 return _error->Error(_("Cannot find a configured compressor for '%s'"),
111 DecompressProg.c_str());
112
113 }
114 /*}}}*/
115 // ExtractTar::Go - Perform extraction /*{{{*/
116 // ---------------------------------------------------------------------
117 /* This reads each 512 byte block from the archive and extracts the header
118 information into the Item structure. Then it resolves the UID/GID and
119 invokes the correct processing function. */
120 bool ExtractTar::Go(pkgDirStream &Stream)
121 {
122 if (StartGzip() == false)
123 return false;
124
125 // Loop over all blocks
126 string LastLongLink, ItemLink;
127 string LastLongName, ItemName;
128 while (1)
129 {
130 bool BadRecord = false;
131 unsigned char Block[512];
132 if (InFd.Read(Block,sizeof(Block),true) == false)
133 return false;
134
135 if (InFd.Eof() == true)
136 break;
137
138 // Get the checksum
139 TarHeader *Tar = (TarHeader *)Block;
140 unsigned long CheckSum;
141 if (StrToNum(Tar->Checksum,CheckSum,sizeof(Tar->Checksum),8) == false)
142 return _error->Error(_("Corrupted archive"));
143
144 /* Compute the checksum field. The actual checksum is blanked out
145 with spaces so it is not included in the computation */
146 unsigned long NewSum = 0;
147 memset(Tar->Checksum,' ',sizeof(Tar->Checksum));
148 for (int I = 0; I != sizeof(Block); I++)
149 NewSum += Block[I];
150
151 /* Check for a block of nulls - in this case we kill gzip, GNU tar
152 does this.. */
153 if (NewSum == ' '*sizeof(Tar->Checksum))
154 return Done();
155
156 if (NewSum != CheckSum)
157 return _error->Error(_("Tar checksum failed, archive corrupted"));
158
159 // Decode all of the fields
160 pkgDirStream::Item Itm;
161 if (StrToNum(Tar->Mode,Itm.Mode,sizeof(Tar->Mode),8) == false ||
162 (Base256ToNum(Tar->UserID,Itm.UID,8) == false &&
163 StrToNum(Tar->UserID,Itm.UID,sizeof(Tar->UserID),8) == false) ||
164 (Base256ToNum(Tar->GroupID,Itm.GID,8) == false &&
165 StrToNum(Tar->GroupID,Itm.GID,sizeof(Tar->GroupID),8) == false) ||
166 (Base256ToNum(Tar->Size,Itm.Size,12) == false &&
167 StrToNum(Tar->Size,Itm.Size,sizeof(Tar->Size),8) == false) ||
168 (Base256ToNum(Tar->MTime,Itm.MTime,12) == false &&
169 StrToNum(Tar->MTime,Itm.MTime,sizeof(Tar->MTime),8) == false) ||
170 StrToNum(Tar->Major,Itm.Major,sizeof(Tar->Major),8) == false ||
171 StrToNum(Tar->Minor,Itm.Minor,sizeof(Tar->Minor),8) == false)
172 return _error->Error(_("Corrupted archive"));
173
174 // Grab the filename and link target: use last long name if one was
175 // set, otherwise use the header value as-is, but remember that it may
176 // fill the entire 100-byte block and needs to be zero-terminated.
177 // See Debian Bug #689582.
178 if (LastLongName.empty() == false)
179 Itm.Name = (char *)LastLongName.c_str();
180 else
181 Itm.Name = (char *)ItemName.assign(Tar->Name, sizeof(Tar->Name)).c_str();
182 if (Itm.Name[0] == '.' && Itm.Name[1] == '/' && Itm.Name[2] != 0)
183 Itm.Name += 2;
184
185 if (LastLongLink.empty() == false)
186 Itm.LinkTarget = (char *)LastLongLink.c_str();
187 else
188 Itm.LinkTarget = (char *)ItemLink.assign(Tar->LinkName, sizeof(Tar->LinkName)).c_str();
189
190 // Convert the type over
191 switch (Tar->LinkFlag)
192 {
193 case NormalFile0:
194 case NormalFile:
195 Itm.Type = pkgDirStream::Item::File;
196 break;
197
198 case HardLink:
199 Itm.Type = pkgDirStream::Item::HardLink;
200 break;
201
202 case SymbolicLink:
203 Itm.Type = pkgDirStream::Item::SymbolicLink;
204 break;
205
206 case CharacterDevice:
207 Itm.Type = pkgDirStream::Item::CharDevice;
208 break;
209
210 case BlockDevice:
211 Itm.Type = pkgDirStream::Item::BlockDevice;
212 break;
213
214 case Directory:
215 Itm.Type = pkgDirStream::Item::Directory;
216 break;
217
218 case FIFO:
219 Itm.Type = pkgDirStream::Item::FIFO;
220 break;
221
222 case GNU_LongLink:
223 {
224 unsigned long long Length = Itm.Size;
225 unsigned char Block[512];
226 while (Length > 0)
227 {
228 if (InFd.Read(Block,sizeof(Block),true) == false)
229 return false;
230 if (Length <= sizeof(Block))
231 {
232 LastLongLink.append(Block,Block+sizeof(Block));
233 break;
234 }
235 LastLongLink.append(Block,Block+sizeof(Block));
236 Length -= sizeof(Block);
237 }
238 continue;
239 }
240
241 case GNU_LongName:
242 {
243 unsigned long long Length = Itm.Size;
244 unsigned char Block[512];
245 while (Length > 0)
246 {
247 if (InFd.Read(Block,sizeof(Block),true) == false)
248 return false;
249 if (Length < sizeof(Block))
250 {
251 LastLongName.append(Block,Block+sizeof(Block));
252 break;
253 }
254 LastLongName.append(Block,Block+sizeof(Block));
255 Length -= sizeof(Block);
256 }
257 continue;
258 }
259
260 default:
261 BadRecord = true;
262 _error->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar->LinkFlag,Tar->Name);
263 break;
264 }
265
266 int Fd = -1;
267 if (BadRecord == false)
268 if (Stream.DoItem(Itm,Fd) == false)
269 return false;
270
271 // Copy the file over the FD
272 unsigned long long Size = Itm.Size;
273 while (Size != 0)
274 {
275 unsigned char Junk[32*1024];
276 unsigned long Read = min(Size, (unsigned long long)sizeof(Junk));
277 if (InFd.Read(Junk,((Read+511)/512)*512) == false)
278 return false;
279
280 if (BadRecord == false)
281 {
282 if (Fd > 0)
283 {
284 if (write(Fd,Junk,Read) != (signed)Read)
285 return Stream.Fail(Itm,Fd);
286 }
287 else
288 {
289 /* An Fd of -2 means to send to a special processing
290 function */
291 if (Fd == -2)
292 if (Stream.Process(Itm,Junk,Read,Itm.Size - Size) == false)
293 return Stream.Fail(Itm,Fd);
294 }
295 }
296
297 Size -= Read;
298 }
299
300 // And finish up
301 if (BadRecord == false)
302 if (Stream.FinishedFile(Itm,Fd) == false)
303 return false;
304
305 LastLongName.erase();
306 LastLongLink.erase();
307 }
308
309 return Done();
310 }
311 /*}}}*/