]> git.saurik.com Git - apt.git/blame - apt-inst/contrib/extracttar.cc
* apt-inst/contrib/extracttar.cc:
[apt.git] / apt-inst / contrib / extracttar.cc
CommitLineData
b2e465d6
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
7db98ffc 3// $Id: extracttar.cc,v 1.8.2.1 2004/01/16 18:58:50 mdz Exp $
b2e465d6
AL
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 /*{{{*/
ea542140 19#include<config.h>
b2e465d6 20
472ff00e 21#include <apt-pkg/dirstream.h>
ea542140 22#include <apt-pkg/extracttar.h>
b2e465d6
AL
23#include <apt-pkg/error.h>
24#include <apt-pkg/strutl.h>
25#include <apt-pkg/configuration.h>
aea7f4c8 26#include <apt-pkg/macros.h>
b2e465d6
AL
27
28#include <stdlib.h>
29#include <unistd.h>
30#include <signal.h>
31#include <fcntl.h>
90f057fd 32#include <iostream>
ea542140 33
d77559ac 34#include <apti18n.h>
b2e465d6 35 /*}}}*/
d77559ac 36
584e4558 37using namespace std;
4520bfdf 38
b2e465d6
AL
39// The on disk header for a tar file.
40struct 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/* */
b21c0438
MZ
61ExtractTar::ExtractTar(FileFd &Fd,unsigned long Max,string DecompressionProgram) : File(Fd),
62 MaxInSize(Max), DecompressProg(DecompressionProgram)
b2e465d6
AL
63
64{
65 GZPid = -1;
66 InFd = -1;
67 Eof = false;
68}
69 /*}}}*/
70// ExtractTar::ExtractTar - Destructor /*{{{*/
71// ---------------------------------------------------------------------
72/* */
73ExtractTar::~ExtractTar()
74{
4520bfdf
AL
75 // Error close
76 Done(true);
b2e465d6
AL
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. */
83bool 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);
b21c0438
MZ
96 string confvar = string("dir::bin::") + DecompressProg;
97 if (ExecWait(GZPid,_config->Find(confvar.c_str(),DecompressProg.c_str()).c_str(),
b2e465d6
AL
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. */
113bool ExtractTar::StartGzip()
114{
115 int Pipes[2];
116 if (pipe(Pipes) != 0)
05eb7df0 117 return _error->Errno("pipe",_("Failed to create pipes"));
b2e465d6
AL
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];
b21c0438 138 string confvar = string("dir::bin::") + DecompressProg;
a9be43ff
MV
139 string argv0 = _config->Find(confvar.c_str(),DecompressProg.c_str());
140 Args[0] = argv0.c_str();
b2e465d6
AL
141 Args[1] = "-d";
142 Args[2] = 0;
b21c0438 143 execvp(Args[0],(char **)Args);
05eb7df0 144 cerr << _("Failed to exec gzip ") << Args[0] << endl;
b2e465d6
AL
145 _exit(100);
146 }
147
148 // Fix up our FDs
2448a064 149 InFd.OpenDescriptor(Pipes[0], FileFd::ReadOnly, FileFd::None, true);
b2e465d6
AL
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. */
159bool 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)
05eb7df0 181 return _error->Error(_("Corrupted archive"));
b2e465d6
AL
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)
db0db9fe 196 return _error->Error(_("Tar checksum failed, archive corrupted"));
b2e465d6
AL
197
198 // Decode all of the fields
199 pkgDirStream::Item Itm;
b2e465d6 200 if (StrToNum(Tar->Mode,Itm.Mode,sizeof(Tar->Mode),8) == false ||
54f2f0a3
NH
201 (Base256ToNum(Tar->UserID,Itm.UID,8) == false &&
202 StrToNum(Tar->UserID,Itm.UID,sizeof(Tar->UserID),8) == false) ||
203 (Base256ToNum(Tar->GroupID,Itm.GID,8) == false &&
204 StrToNum(Tar->GroupID,Itm.GID,sizeof(Tar->GroupID),8) == false) ||
205 (Base256ToNum(Tar->Size,Itm.Size,12) == false &&
206 StrToNum(Tar->Size,Itm.Size,sizeof(Tar->Size),8) == false) ||
207 (Base256ToNum(Tar->MTime,Itm.MTime,12) == false &&
208 StrToNum(Tar->MTime,Itm.MTime,sizeof(Tar->MTime),8) == false) ||
b2e465d6
AL
209 StrToNum(Tar->Major,Itm.Major,sizeof(Tar->Major),8) == false ||
210 StrToNum(Tar->Minor,Itm.Minor,sizeof(Tar->Minor),8) == false)
05eb7df0 211 return _error->Error(_("Corrupted archive"));
b2e465d6
AL
212
213 // Grab the filename
214 if (LastLongName.empty() == false)
215 Itm.Name = (char *)LastLongName.c_str();
216 else
217 {
3c8cda8b 218 Tar->Name[sizeof(Tar->Name)-1] = 0;
b2e465d6
AL
219 Itm.Name = Tar->Name;
220 }
221 if (Itm.Name[0] == '.' && Itm.Name[1] == '/' && Itm.Name[2] != 0)
222 Itm.Name += 2;
223
224 // Grab the link target
3c8cda8b 225 Tar->Name[sizeof(Tar->LinkName)-1] = 0;
b2e465d6
AL
226 Itm.LinkTarget = Tar->LinkName;
227
228 if (LastLongLink.empty() == false)
229 Itm.LinkTarget = (char *)LastLongLink.c_str();
230
231 // Convert the type over
232 switch (Tar->LinkFlag)
233 {
234 case NormalFile0:
235 case NormalFile:
236 Itm.Type = pkgDirStream::Item::File;
237 break;
238
239 case HardLink:
240 Itm.Type = pkgDirStream::Item::HardLink;
241 break;
242
243 case SymbolicLink:
244 Itm.Type = pkgDirStream::Item::SymbolicLink;
245 break;
246
247 case CharacterDevice:
248 Itm.Type = pkgDirStream::Item::CharDevice;
249 break;
250
251 case BlockDevice:
252 Itm.Type = pkgDirStream::Item::BlockDevice;
253 break;
254
255 case Directory:
256 Itm.Type = pkgDirStream::Item::Directory;
257 break;
258
259 case FIFO:
260 Itm.Type = pkgDirStream::Item::FIFO;
261 break;
262
263 case GNU_LongLink:
264 {
265 unsigned long Length = Itm.Size;
266 unsigned char Block[512];
267 while (Length > 0)
268 {
269 if (InFd.Read(Block,sizeof(Block),true) == false)
270 return false;
271 if (Length <= sizeof(Block))
272 {
273 LastLongLink.append(Block,Block+sizeof(Block));
274 break;
275 }
276 LastLongLink.append(Block,Block+sizeof(Block));
277 Length -= sizeof(Block);
278 }
279 continue;
280 }
281
282 case GNU_LongName:
283 {
284 unsigned long Length = Itm.Size;
285 unsigned char Block[512];
286 while (Length > 0)
287 {
288 if (InFd.Read(Block,sizeof(Block),true) == false)
289 return false;
290 if (Length < sizeof(Block))
291 {
292 LastLongName.append(Block,Block+sizeof(Block));
293 break;
294 }
295 LastLongName.append(Block,Block+sizeof(Block));
296 Length -= sizeof(Block);
297 }
298 continue;
299 }
300
301 default:
302 BadRecord = true;
4c6a9fad 303 _error->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar->LinkFlag,Tar->Name);
b2e465d6
AL
304 break;
305 }
306
307 int Fd = -1;
308 if (BadRecord == false)
309 if (Stream.DoItem(Itm,Fd) == false)
310 return false;
311
312 // Copy the file over the FD
313 unsigned long Size = Itm.Size;
314 while (Size != 0)
315 {
316 unsigned char Junk[32*1024];
42ab8223 317 unsigned long Read = min(Size,(unsigned long)sizeof(Junk));
b2e465d6
AL
318 if (InFd.Read(Junk,((Read+511)/512)*512) == false)
319 return false;
320
321 if (BadRecord == false)
322 {
323 if (Fd > 0)
324 {
325 if (write(Fd,Junk,Read) != (signed)Read)
326 return Stream.Fail(Itm,Fd);
327 }
328 else
329 {
330 /* An Fd of -2 means to send to a special processing
331 function */
332 if (Fd == -2)
333 if (Stream.Process(Itm,Junk,Read,Itm.Size - Size) == false)
334 return Stream.Fail(Itm,Fd);
335 }
336 }
337
338 Size -= Read;
339 }
340
341 // And finish up
59152cdb 342 if (BadRecord == false)
b2e465d6
AL
343 if (Stream.FinishedFile(Itm,Fd) == false)
344 return false;
345
346 LastLongName.erase();
347 LastLongLink.erase();
348 }
349
350 return Done(false);
351}
352 /*}}}*/