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