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