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