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