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