]> git.saurik.com Git - apt.git/blob - apt-inst/contrib/extracttar.cc
add hashsum support in apt-file download and add more tests
[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/macros.h>
27
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <signal.h>
31 #include <fcntl.h>
32 #include <iostream>
33
34 #include <apti18n.h>
35 /*}}}*/
36
37 using namespace std;
38
39 // The on disk header for a tar file.
40 struct ExtractTar::TarHeader
41 {
42 char Name[100];
43 char Mode[8];
44 char UserID[8];
45 char GroupID[8];
46 char Size[12];
47 char MTime[12];
48 char Checksum[8];
49 char LinkFlag;
50 char LinkName[100];
51 char MagicNumber[8];
52 char UserName[32];
53 char GroupName[32];
54 char Major[8];
55 char Minor[8];
56 };
57
58 // ExtractTar::ExtractTar - Constructor /*{{{*/
59 // ---------------------------------------------------------------------
60 /* */
61 ExtractTar::ExtractTar(FileFd &Fd,unsigned long Max,string DecompressionProgram) : File(Fd),
62 MaxInSize(Max), DecompressProg(DecompressionProgram)
63
64 {
65 GZPid = -1;
66 Eof = false;
67 }
68 /*}}}*/
69 // ExtractTar::ExtractTar - Destructor /*{{{*/
70 // ---------------------------------------------------------------------
71 /* */
72 ExtractTar::~ExtractTar()
73 {
74 // Error close
75 Done(true);
76 }
77 /*}}}*/
78 // ExtractTar::Done - Reap the gzip sub process /*{{{*/
79 // ---------------------------------------------------------------------
80 /* If the force flag is given then error messages are suppressed - this
81 means we hit the end of the tar file but there was still gzip data. */
82 bool ExtractTar::Done(bool Force)
83 {
84 InFd.Close();
85 if (GZPid <= 0)
86 return true;
87
88 /* If there is a pending error then we are cleaning up gzip and are
89 not interested in it's failures */
90 if (_error->PendingError() == true)
91 Force = true;
92
93 // Make sure we clean it up!
94 kill(GZPid,SIGINT);
95 string confvar = string("dir::bin::") + DecompressProg;
96 if (ExecWait(GZPid,_config->Find(confvar.c_str(),DecompressProg.c_str()).c_str(),
97 Force) == false)
98 {
99 GZPid = -1;
100 return Force;
101 }
102
103 GZPid = -1;
104 return true;
105 }
106 /*}}}*/
107 // ExtractTar::StartGzip - Startup gzip /*{{{*/
108 // ---------------------------------------------------------------------
109 /* This creates a gzip sub process that has its input as the file itself.
110 If this tar file is embedded into something like an ar file then
111 gzip will efficiently ignore the extra bits. */
112 bool ExtractTar::StartGzip()
113 {
114 if (DecompressProg.empty())
115 {
116 InFd.OpenDescriptor(File.Fd(), FileFd::ReadOnly, FileFd::None, false);
117 return true;
118 }
119
120 int Pipes[2];
121 if (pipe(Pipes) != 0)
122 return _error->Errno("pipe",_("Failed to create pipes"));
123
124 // Fork off the process
125 GZPid = ExecFork();
126
127 // Spawn the subprocess
128 if (GZPid == 0)
129 {
130 // Setup the FDs
131 dup2(Pipes[1],STDOUT_FILENO);
132 dup2(File.Fd(),STDIN_FILENO);
133 int Fd = open("/dev/null",O_RDWR);
134 if (Fd == -1)
135 _exit(101);
136 dup2(Fd,STDERR_FILENO);
137 close(Fd);
138 SetCloseExec(STDOUT_FILENO,false);
139 SetCloseExec(STDIN_FILENO,false);
140 SetCloseExec(STDERR_FILENO,false);
141
142 const char *Args[3];
143 string confvar = string("dir::bin::") + DecompressProg;
144 string argv0 = _config->Find(confvar.c_str(),DecompressProg.c_str());
145 Args[0] = argv0.c_str();
146 Args[1] = "-d";
147 Args[2] = 0;
148 execvp(Args[0],(char **)Args);
149 cerr << _("Failed to exec gzip ") << Args[0] << endl;
150 _exit(100);
151 }
152
153 // Fix up our FDs
154 InFd.OpenDescriptor(Pipes[0], FileFd::ReadOnly, FileFd::None, true);
155 close(Pipes[1]);
156 return true;
157 }
158 /*}}}*/
159 // ExtractTar::Go - Perform extraction /*{{{*/
160 // ---------------------------------------------------------------------
161 /* This reads each 512 byte block from the archive and extracts the header
162 information into the Item structure. Then it resolves the UID/GID and
163 invokes the correct processing function. */
164 bool ExtractTar::Go(pkgDirStream &Stream)
165 {
166 if (StartGzip() == false)
167 return false;
168
169 // Loop over all blocks
170 string LastLongLink, ItemLink;
171 string LastLongName, ItemName;
172 while (1)
173 {
174 bool BadRecord = false;
175 unsigned char Block[512];
176 if (InFd.Read(Block,sizeof(Block),true) == false)
177 return false;
178
179 if (InFd.Eof() == true)
180 break;
181
182 // Get the checksum
183 TarHeader *Tar = (TarHeader *)Block;
184 unsigned long CheckSum;
185 if (StrToNum(Tar->Checksum,CheckSum,sizeof(Tar->Checksum),8) == false)
186 return _error->Error(_("Corrupted archive"));
187
188 /* Compute the checksum field. The actual checksum is blanked out
189 with spaces so it is not included in the computation */
190 unsigned long NewSum = 0;
191 memset(Tar->Checksum,' ',sizeof(Tar->Checksum));
192 for (int I = 0; I != sizeof(Block); I++)
193 NewSum += Block[I];
194
195 /* Check for a block of nulls - in this case we kill gzip, GNU tar
196 does this.. */
197 if (NewSum == ' '*sizeof(Tar->Checksum))
198 return Done(true);
199
200 if (NewSum != CheckSum)
201 return _error->Error(_("Tar checksum failed, archive corrupted"));
202
203 // Decode all of the fields
204 pkgDirStream::Item Itm;
205 if (StrToNum(Tar->Mode,Itm.Mode,sizeof(Tar->Mode),8) == false ||
206 (Base256ToNum(Tar->UserID,Itm.UID,8) == false &&
207 StrToNum(Tar->UserID,Itm.UID,sizeof(Tar->UserID),8) == false) ||
208 (Base256ToNum(Tar->GroupID,Itm.GID,8) == false &&
209 StrToNum(Tar->GroupID,Itm.GID,sizeof(Tar->GroupID),8) == false) ||
210 (Base256ToNum(Tar->Size,Itm.Size,12) == false &&
211 StrToNum(Tar->Size,Itm.Size,sizeof(Tar->Size),8) == false) ||
212 (Base256ToNum(Tar->MTime,Itm.MTime,12) == false &&
213 StrToNum(Tar->MTime,Itm.MTime,sizeof(Tar->MTime),8) == false) ||
214 StrToNum(Tar->Major,Itm.Major,sizeof(Tar->Major),8) == false ||
215 StrToNum(Tar->Minor,Itm.Minor,sizeof(Tar->Minor),8) == false)
216 return _error->Error(_("Corrupted archive"));
217
218 // Grab the filename and link target: use last long name if one was
219 // set, otherwise use the header value as-is, but remember that it may
220 // fill the entire 100-byte block and needs to be zero-terminated.
221 // See Debian Bug #689582.
222 if (LastLongName.empty() == false)
223 Itm.Name = (char *)LastLongName.c_str();
224 else
225 Itm.Name = (char *)ItemName.assign(Tar->Name, sizeof(Tar->Name)).c_str();
226 if (Itm.Name[0] == '.' && Itm.Name[1] == '/' && Itm.Name[2] != 0)
227 Itm.Name += 2;
228
229 if (LastLongLink.empty() == false)
230 Itm.LinkTarget = (char *)LastLongLink.c_str();
231 else
232 Itm.LinkTarget = (char *)ItemLink.assign(Tar->LinkName, sizeof(Tar->LinkName)).c_str();
233
234 // Convert the type over
235 switch (Tar->LinkFlag)
236 {
237 case NormalFile0:
238 case NormalFile:
239 Itm.Type = pkgDirStream::Item::File;
240 break;
241
242 case HardLink:
243 Itm.Type = pkgDirStream::Item::HardLink;
244 break;
245
246 case SymbolicLink:
247 Itm.Type = pkgDirStream::Item::SymbolicLink;
248 break;
249
250 case CharacterDevice:
251 Itm.Type = pkgDirStream::Item::CharDevice;
252 break;
253
254 case BlockDevice:
255 Itm.Type = pkgDirStream::Item::BlockDevice;
256 break;
257
258 case Directory:
259 Itm.Type = pkgDirStream::Item::Directory;
260 break;
261
262 case FIFO:
263 Itm.Type = pkgDirStream::Item::FIFO;
264 break;
265
266 case GNU_LongLink:
267 {
268 unsigned long Length = Itm.Size;
269 unsigned char Block[512];
270 while (Length > 0)
271 {
272 if (InFd.Read(Block,sizeof(Block),true) == false)
273 return false;
274 if (Length <= sizeof(Block))
275 {
276 LastLongLink.append(Block,Block+sizeof(Block));
277 break;
278 }
279 LastLongLink.append(Block,Block+sizeof(Block));
280 Length -= sizeof(Block);
281 }
282 continue;
283 }
284
285 case GNU_LongName:
286 {
287 unsigned long Length = Itm.Size;
288 unsigned char Block[512];
289 while (Length > 0)
290 {
291 if (InFd.Read(Block,sizeof(Block),true) == false)
292 return false;
293 if (Length < sizeof(Block))
294 {
295 LastLongName.append(Block,Block+sizeof(Block));
296 break;
297 }
298 LastLongName.append(Block,Block+sizeof(Block));
299 Length -= sizeof(Block);
300 }
301 continue;
302 }
303
304 default:
305 BadRecord = true;
306 _error->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar->LinkFlag,Tar->Name);
307 break;
308 }
309
310 int Fd = -1;
311 if (BadRecord == false)
312 if (Stream.DoItem(Itm,Fd) == false)
313 return false;
314
315 // Copy the file over the FD
316 unsigned long Size = Itm.Size;
317 while (Size != 0)
318 {
319 unsigned char Junk[32*1024];
320 unsigned long Read = min(Size,(unsigned long)sizeof(Junk));
321 if (InFd.Read(Junk,((Read+511)/512)*512) == false)
322 return false;
323
324 if (BadRecord == false)
325 {
326 if (Fd > 0)
327 {
328 if (write(Fd,Junk,Read) != (signed)Read)
329 return Stream.Fail(Itm,Fd);
330 }
331 else
332 {
333 /* An Fd of -2 means to send to a special processing
334 function */
335 if (Fd == -2)
336 if (Stream.Process(Itm,Junk,Read,Itm.Size - Size) == false)
337 return Stream.Fail(Itm,Fd);
338 }
339 }
340
341 Size -= Read;
342 }
343
344 // And finish up
345 if (BadRecord == false)
346 if (Stream.FinishedFile(Itm,Fd) == false)
347 return false;
348
349 LastLongName.erase();
350 LastLongLink.erase();
351 }
352
353 return Done(false);
354 }
355 /*}}}*/