]> git.saurik.com Git - apt.git/blame - apt-inst/contrib/extracttar.cc
reorder includes: add <config.h> if needed and include it at first
[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
ea542140 21#include <apt-pkg/extracttar.h>
b2e465d6
AL
22#include <apt-pkg/error.h>
23#include <apt-pkg/strutl.h>
24#include <apt-pkg/configuration.h>
aea7f4c8 25#include <apt-pkg/macros.h>
b2e465d6
AL
26
27#include <stdlib.h>
28#include <unistd.h>
29#include <signal.h>
30#include <fcntl.h>
90f057fd 31#include <iostream>
ea542140 32
d77559ac 33#include <apti18n.h>
b2e465d6 34 /*}}}*/
d77559ac 35
584e4558 36using namespace std;
4520bfdf 37
b2e465d6
AL
38// The on disk header for a tar file.
39struct ExtractTar::TarHeader
40{
41 char Name[100];
42 char Mode[8];
43 char UserID[8];
44 char GroupID[8];
45 char Size[12];
46 char MTime[12];
47 char Checksum[8];
48 char LinkFlag;
49 char LinkName[100];
50 char MagicNumber[8];
51 char UserName[32];
52 char GroupName[32];
53 char Major[8];
54 char Minor[8];
55};
56
57// ExtractTar::ExtractTar - Constructor /*{{{*/
58// ---------------------------------------------------------------------
59/* */
b21c0438
MZ
60ExtractTar::ExtractTar(FileFd &Fd,unsigned long Max,string DecompressionProgram) : File(Fd),
61 MaxInSize(Max), DecompressProg(DecompressionProgram)
b2e465d6
AL
62
63{
64 GZPid = -1;
65 InFd = -1;
66 Eof = false;
67}
68 /*}}}*/
69// ExtractTar::ExtractTar - Destructor /*{{{*/
70// ---------------------------------------------------------------------
71/* */
72ExtractTar::~ExtractTar()
73{
4520bfdf
AL
74 // Error close
75 Done(true);
b2e465d6
AL
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. */
82bool 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);
b21c0438
MZ
95 string confvar = string("dir::bin::") + DecompressProg;
96 if (ExecWait(GZPid,_config->Find(confvar.c_str(),DecompressProg.c_str()).c_str(),
b2e465d6
AL
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. */
112bool ExtractTar::StartGzip()
113{
114 int Pipes[2];
115 if (pipe(Pipes) != 0)
05eb7df0 116 return _error->Errno("pipe",_("Failed to create pipes"));
b2e465d6
AL
117
118 // Fork off the process
119 GZPid = ExecFork();
120
121 // Spawn the subprocess
122 if (GZPid == 0)
123 {
124 // Setup the FDs
125 dup2(Pipes[1],STDOUT_FILENO);
126 dup2(File.Fd(),STDIN_FILENO);
127 int Fd = open("/dev/null",O_RDWR);
128 if (Fd == -1)
129 _exit(101);
130 dup2(Fd,STDERR_FILENO);
131 close(Fd);
132 SetCloseExec(STDOUT_FILENO,false);
133 SetCloseExec(STDIN_FILENO,false);
134 SetCloseExec(STDERR_FILENO,false);
135
136 const char *Args[3];
b21c0438 137 string confvar = string("dir::bin::") + DecompressProg;
a9be43ff
MV
138 string argv0 = _config->Find(confvar.c_str(),DecompressProg.c_str());
139 Args[0] = argv0.c_str();
b2e465d6
AL
140 Args[1] = "-d";
141 Args[2] = 0;
b21c0438 142 execvp(Args[0],(char **)Args);
05eb7df0 143 cerr << _("Failed to exec gzip ") << Args[0] << endl;
b2e465d6
AL
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. */
158bool 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)
05eb7df0 180 return _error->Error(_("Corrupted archive"));
b2e465d6
AL
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)
db0db9fe 195 return _error->Error(_("Tar checksum failed, archive corrupted"));
b2e465d6
AL
196
197 // Decode all of the fields
198 pkgDirStream::Item Itm;
b2e465d6 199 if (StrToNum(Tar->Mode,Itm.Mode,sizeof(Tar->Mode),8) == false ||
54f2f0a3
NH
200 (Base256ToNum(Tar->UserID,Itm.UID,8) == false &&
201 StrToNum(Tar->UserID,Itm.UID,sizeof(Tar->UserID),8) == false) ||
202 (Base256ToNum(Tar->GroupID,Itm.GID,8) == false &&
203 StrToNum(Tar->GroupID,Itm.GID,sizeof(Tar->GroupID),8) == false) ||
204 (Base256ToNum(Tar->Size,Itm.Size,12) == false &&
205 StrToNum(Tar->Size,Itm.Size,sizeof(Tar->Size),8) == false) ||
206 (Base256ToNum(Tar->MTime,Itm.MTime,12) == false &&
207 StrToNum(Tar->MTime,Itm.MTime,sizeof(Tar->MTime),8) == false) ||
b2e465d6
AL
208 StrToNum(Tar->Major,Itm.Major,sizeof(Tar->Major),8) == false ||
209 StrToNum(Tar->Minor,Itm.Minor,sizeof(Tar->Minor),8) == false)
05eb7df0 210 return _error->Error(_("Corrupted archive"));
b2e465d6
AL
211
212 // Grab the filename
213 if (LastLongName.empty() == false)
214 Itm.Name = (char *)LastLongName.c_str();
215 else
216 {
3c8cda8b 217 Tar->Name[sizeof(Tar->Name)-1] = 0;
b2e465d6
AL
218 Itm.Name = Tar->Name;
219 }
220 if (Itm.Name[0] == '.' && Itm.Name[1] == '/' && Itm.Name[2] != 0)
221 Itm.Name += 2;
222
223 // Grab the link target
3c8cda8b 224 Tar->Name[sizeof(Tar->LinkName)-1] = 0;
b2e465d6
AL
225 Itm.LinkTarget = Tar->LinkName;
226
227 if (LastLongLink.empty() == false)
228 Itm.LinkTarget = (char *)LastLongLink.c_str();
229
230 // Convert the type over
231 switch (Tar->LinkFlag)
232 {
233 case NormalFile0:
234 case NormalFile:
235 Itm.Type = pkgDirStream::Item::File;
236 break;
237
238 case HardLink:
239 Itm.Type = pkgDirStream::Item::HardLink;
240 break;
241
242 case SymbolicLink:
243 Itm.Type = pkgDirStream::Item::SymbolicLink;
244 break;
245
246 case CharacterDevice:
247 Itm.Type = pkgDirStream::Item::CharDevice;
248 break;
249
250 case BlockDevice:
251 Itm.Type = pkgDirStream::Item::BlockDevice;
252 break;
253
254 case Directory:
255 Itm.Type = pkgDirStream::Item::Directory;
256 break;
257
258 case FIFO:
259 Itm.Type = pkgDirStream::Item::FIFO;
260 break;
261
262 case GNU_LongLink:
263 {
264 unsigned long Length = Itm.Size;
265 unsigned char Block[512];
266 while (Length > 0)
267 {
268 if (InFd.Read(Block,sizeof(Block),true) == false)
269 return false;
270 if (Length <= sizeof(Block))
271 {
272 LastLongLink.append(Block,Block+sizeof(Block));
273 break;
274 }
275 LastLongLink.append(Block,Block+sizeof(Block));
276 Length -= sizeof(Block);
277 }
278 continue;
279 }
280
281 case GNU_LongName:
282 {
283 unsigned long Length = Itm.Size;
284 unsigned char Block[512];
285 while (Length > 0)
286 {
287 if (InFd.Read(Block,sizeof(Block),true) == false)
288 return false;
289 if (Length < sizeof(Block))
290 {
291 LastLongName.append(Block,Block+sizeof(Block));
292 break;
293 }
294 LastLongName.append(Block,Block+sizeof(Block));
295 Length -= sizeof(Block);
296 }
297 continue;
298 }
299
300 default:
301 BadRecord = true;
4c6a9fad 302 _error->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar->LinkFlag,Tar->Name);
b2e465d6
AL
303 break;
304 }
305
306 int Fd = -1;
307 if (BadRecord == false)
308 if (Stream.DoItem(Itm,Fd) == false)
309 return false;
310
311 // Copy the file over the FD
312 unsigned long Size = Itm.Size;
313 while (Size != 0)
314 {
315 unsigned char Junk[32*1024];
42ab8223 316 unsigned long Read = min(Size,(unsigned long)sizeof(Junk));
b2e465d6
AL
317 if (InFd.Read(Junk,((Read+511)/512)*512) == false)
318 return false;
319
320 if (BadRecord == false)
321 {
322 if (Fd > 0)
323 {
324 if (write(Fd,Junk,Read) != (signed)Read)
325 return Stream.Fail(Itm,Fd);
326 }
327 else
328 {
329 /* An Fd of -2 means to send to a special processing
330 function */
331 if (Fd == -2)
332 if (Stream.Process(Itm,Junk,Read,Itm.Size - Size) == false)
333 return Stream.Fail(Itm,Fd);
334 }
335 }
336
337 Size -= Read;
338 }
339
340 // And finish up
59152cdb 341 if (BadRecord == false)
b2e465d6
AL
342 if (Stream.FinishedFile(Itm,Fd) == false)
343 return false;
344
345 LastLongName.erase();
346 LastLongLink.erase();
347 }
348
349 return Done(false);
350}
351 /*}}}*/