]> git.saurik.com Git - apt.git/blob - apt-inst/contrib/extracttar.cc
* merged changes from the conference
[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 Args[0] = _config->Find(confvar.c_str(),DecompressProg.c_str()).c_str();
140 Args[1] = "-d";
141 Args[2] = 0;
142 execvp(Args[0],(char **)Args);
143 cerr << _("Failed to exec gzip ") << Args[0] << endl;
144 _exit(100);
145 }
146
147 // Fix up our FDs
148 InFd.Fd(Pipes[0]);
149 close(Pipes[1]);
150 return true;
151 }
152 /*}}}*/
153 // ExtractTar::Go - Perform extraction /*{{{*/
154 // ---------------------------------------------------------------------
155 /* This reads each 512 byte block from the archive and extracts the header
156 information into the Item structure. Then it resolves the UID/GID and
157 invokes the correct processing function. */
158 bool ExtractTar::Go(pkgDirStream &Stream)
159 {
160 if (StartGzip() == false)
161 return false;
162
163 // Loop over all blocks
164 string LastLongLink;
165 string LastLongName;
166 while (1)
167 {
168 bool BadRecord = false;
169 unsigned char Block[512];
170 if (InFd.Read(Block,sizeof(Block),true) == false)
171 return false;
172
173 if (InFd.Eof() == true)
174 break;
175
176 // Get the checksum
177 TarHeader *Tar = (TarHeader *)Block;
178 unsigned long CheckSum;
179 if (StrToNum(Tar->Checksum,CheckSum,sizeof(Tar->Checksum),8) == false)
180 return _error->Error(_("Corrupted archive"));
181
182 /* Compute the checksum field. The actual checksum is blanked out
183 with spaces so it is not included in the computation */
184 unsigned long NewSum = 0;
185 memset(Tar->Checksum,' ',sizeof(Tar->Checksum));
186 for (int I = 0; I != sizeof(Block); I++)
187 NewSum += Block[I];
188
189 /* Check for a block of nulls - in this case we kill gzip, GNU tar
190 does this.. */
191 if (NewSum == ' '*sizeof(Tar->Checksum))
192 return Done(true);
193
194 if (NewSum != CheckSum)
195 return _error->Error(_("Tar Checksum failed, archive corrupted"));
196
197 // Decode all of the fields
198 pkgDirStream::Item Itm;
199 if (StrToNum(Tar->Mode,Itm.Mode,sizeof(Tar->Mode),8) == false ||
200 StrToNum(Tar->UserID,Itm.UID,sizeof(Tar->UserID),8) == false ||
201 StrToNum(Tar->GroupID,Itm.GID,sizeof(Tar->GroupID),8) == false ||
202 StrToNum(Tar->Size,Itm.Size,sizeof(Tar->Size),8) == false ||
203 StrToNum(Tar->MTime,Itm.MTime,sizeof(Tar->MTime),8) == false ||
204 StrToNum(Tar->Major,Itm.Major,sizeof(Tar->Major),8) == false ||
205 StrToNum(Tar->Minor,Itm.Minor,sizeof(Tar->Minor),8) == false)
206 return _error->Error(_("Corrupted archive"));
207
208 // Grab the filename
209 if (LastLongName.empty() == false)
210 Itm.Name = (char *)LastLongName.c_str();
211 else
212 {
213 Tar->Name[sizeof(Tar->Name)] = 0;
214 Itm.Name = Tar->Name;
215 }
216 if (Itm.Name[0] == '.' && Itm.Name[1] == '/' && Itm.Name[2] != 0)
217 Itm.Name += 2;
218
219 // Grab the link target
220 Tar->Name[sizeof(Tar->LinkName)] = 0;
221 Itm.LinkTarget = Tar->LinkName;
222
223 if (LastLongLink.empty() == false)
224 Itm.LinkTarget = (char *)LastLongLink.c_str();
225
226 // Convert the type over
227 switch (Tar->LinkFlag)
228 {
229 case NormalFile0:
230 case NormalFile:
231 Itm.Type = pkgDirStream::Item::File;
232 break;
233
234 case HardLink:
235 Itm.Type = pkgDirStream::Item::HardLink;
236 break;
237
238 case SymbolicLink:
239 Itm.Type = pkgDirStream::Item::SymbolicLink;
240 break;
241
242 case CharacterDevice:
243 Itm.Type = pkgDirStream::Item::CharDevice;
244 break;
245
246 case BlockDevice:
247 Itm.Type = pkgDirStream::Item::BlockDevice;
248 break;
249
250 case Directory:
251 Itm.Type = pkgDirStream::Item::Directory;
252 break;
253
254 case FIFO:
255 Itm.Type = pkgDirStream::Item::FIFO;
256 break;
257
258 case GNU_LongLink:
259 {
260 unsigned long Length = Itm.Size;
261 unsigned char Block[512];
262 while (Length > 0)
263 {
264 if (InFd.Read(Block,sizeof(Block),true) == false)
265 return false;
266 if (Length <= sizeof(Block))
267 {
268 LastLongLink.append(Block,Block+sizeof(Block));
269 break;
270 }
271 LastLongLink.append(Block,Block+sizeof(Block));
272 Length -= sizeof(Block);
273 }
274 continue;
275 }
276
277 case GNU_LongName:
278 {
279 unsigned long Length = Itm.Size;
280 unsigned char Block[512];
281 while (Length > 0)
282 {
283 if (InFd.Read(Block,sizeof(Block),true) == false)
284 return false;
285 if (Length < sizeof(Block))
286 {
287 LastLongName.append(Block,Block+sizeof(Block));
288 break;
289 }
290 LastLongName.append(Block,Block+sizeof(Block));
291 Length -= sizeof(Block);
292 }
293 continue;
294 }
295
296 default:
297 BadRecord = true;
298 _error->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar->LinkFlag,Tar->Name);
299 break;
300 }
301
302 int Fd = -1;
303 if (BadRecord == false)
304 if (Stream.DoItem(Itm,Fd) == false)
305 return false;
306
307 // Copy the file over the FD
308 unsigned long Size = Itm.Size;
309 while (Size != 0)
310 {
311 unsigned char Junk[32*1024];
312 unsigned long Read = MIN(Size,sizeof(Junk));
313 if (InFd.Read(Junk,((Read+511)/512)*512) == false)
314 return false;
315
316 if (BadRecord == false)
317 {
318 if (Fd > 0)
319 {
320 if (write(Fd,Junk,Read) != (signed)Read)
321 return Stream.Fail(Itm,Fd);
322 }
323 else
324 {
325 /* An Fd of -2 means to send to a special processing
326 function */
327 if (Fd == -2)
328 if (Stream.Process(Itm,Junk,Read,Itm.Size - Size) == false)
329 return Stream.Fail(Itm,Fd);
330 }
331 }
332
333 Size -= Read;
334 }
335
336 // And finish up
337 if (Itm.Size != 0 && BadRecord == false)
338 if (Stream.FinishedFile(Itm,Fd) == false)
339 return false;
340
341 LastLongName.erase();
342 LastLongLink.erase();
343 }
344
345 return Done(false);
346 }
347 /*}}}*/