Change verbose logging output of apt-ftparchive to go t...
[ntk/apt.git] / apt-inst / contrib / extracttar.cc
CommitLineData
b2e465d6
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
90f057fd 3// $Id: extracttar.cc,v 1.5 2002/03/26 07:38:58 jgg 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 /*{{{*/
19#ifdef __GNUG__
20#pragma implementation "apt-pkg/extracttar.h"
21#endif
22#include <apt-pkg/extracttar.h>
23
24#include <apt-pkg/error.h>
25#include <apt-pkg/strutl.h>
26#include <apt-pkg/configuration.h>
27#include <system.h>
28
29#include <stdlib.h>
30#include <unistd.h>
31#include <signal.h>
32#include <fcntl.h>
90f057fd 33#include <iostream>
b2e465d6 34 /*}}}*/
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/* */
58ExtractTar::ExtractTar(FileFd &Fd,unsigned long Max) : File(Fd),
59 MaxInSize(Max)
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);
93 if (ExecWait(GZPid,_config->Find("dir::bin::gzip","/bin/gzip").c_str(),
94 Force) == false)
95 {
96 GZPid = -1;
97 return Force;
98 }
99
100 GZPid = -1;
101 return true;
102}
103 /*}}}*/
104// ExtractTar::StartGzip - Startup gzip /*{{{*/
105// ---------------------------------------------------------------------
106/* This creates a gzip sub process that has its input as the file itself.
107 If this tar file is embedded into something like an ar file then
108 gzip will efficiently ignore the extra bits. */
109bool ExtractTar::StartGzip()
110{
111 int Pipes[2];
112 if (pipe(Pipes) != 0)
113 return _error->Errno("pipe","Failed to create pipes");
114
115 // Fork off the process
116 GZPid = ExecFork();
117
118 // Spawn the subprocess
119 if (GZPid == 0)
120 {
121 // Setup the FDs
122 dup2(Pipes[1],STDOUT_FILENO);
123 dup2(File.Fd(),STDIN_FILENO);
124 int Fd = open("/dev/null",O_RDWR);
125 if (Fd == -1)
126 _exit(101);
127 dup2(Fd,STDERR_FILENO);
128 close(Fd);
129 SetCloseExec(STDOUT_FILENO,false);
130 SetCloseExec(STDIN_FILENO,false);
131 SetCloseExec(STDERR_FILENO,false);
132
133 const char *Args[3];
134 Args[0] = _config->Find("dir::bin::gzip","/bin/gzip").c_str();
135 Args[1] = "-d";
136 Args[2] = 0;
137 execv(Args[0],(char **)Args);
138 cerr << "Failed to exec gzip " << Args[0] << endl;
139 _exit(100);
140 }
141
142 // Fix up our FDs
143 InFd.Fd(Pipes[0]);
144 close(Pipes[1]);
145 return true;
146}
147 /*}}}*/
148// ExtractTar::Go - Perform extraction /*{{{*/
149// ---------------------------------------------------------------------
150/* This reads each 512 byte block from the archive and extracts the header
151 information into the Item structure. Then it resolves the UID/GID and
152 invokes the correct processing function. */
153bool ExtractTar::Go(pkgDirStream &Stream)
154{
155 if (StartGzip() == false)
156 return false;
157
158 // Loop over all blocks
159 string LastLongLink;
160 string LastLongName;
161 while (1)
162 {
163 bool BadRecord = false;
164 unsigned char Block[512];
165 if (InFd.Read(Block,sizeof(Block),true) == false)
166 return false;
167
168 if (InFd.Eof() == true)
169 break;
170
171 // Get the checksum
172 TarHeader *Tar = (TarHeader *)Block;
173 unsigned long CheckSum;
174 if (StrToNum(Tar->Checksum,CheckSum,sizeof(Tar->Checksum),8) == false)
175 return _error->Error("Corrupted archive");
176
177 /* Compute the checksum field. The actual checksum is blanked out
178 with spaces so it is not included in the computation */
179 unsigned long NewSum = 0;
180 memset(Tar->Checksum,' ',sizeof(Tar->Checksum));
181 for (int I = 0; I != sizeof(Block); I++)
182 NewSum += Block[I];
183
184 /* Check for a block of nulls - in this case we kill gzip, GNU tar
185 does this.. */
186 if (NewSum == ' '*sizeof(Tar->Checksum))
187 return Done(true);
188
189 if (NewSum != CheckSum)
190 return _error->Error("Tar Checksum failed, archive corrupted");
191
192 // Decode all of the fields
193 pkgDirStream::Item Itm;
b2e465d6 194 if (StrToNum(Tar->Mode,Itm.Mode,sizeof(Tar->Mode),8) == false ||
4520bfdf
AL
195 StrToNum(Tar->UserID,Itm.UID,sizeof(Tar->UserID),8) == false ||
196 StrToNum(Tar->GroupID,Itm.GID,sizeof(Tar->GroupID),8) == false ||
b2e465d6
AL
197 StrToNum(Tar->Size,Itm.Size,sizeof(Tar->Size),8) == false ||
198 StrToNum(Tar->MTime,Itm.MTime,sizeof(Tar->MTime),8) == false ||
199 StrToNum(Tar->Major,Itm.Major,sizeof(Tar->Major),8) == false ||
200 StrToNum(Tar->Minor,Itm.Minor,sizeof(Tar->Minor),8) == false)
201 return _error->Error("Corrupted archive");
202
203 // Grab the filename
204 if (LastLongName.empty() == false)
205 Itm.Name = (char *)LastLongName.c_str();
206 else
207 {
208 Tar->Name[sizeof(Tar->Name)] = 0;
209 Itm.Name = Tar->Name;
210 }
211 if (Itm.Name[0] == '.' && Itm.Name[1] == '/' && Itm.Name[2] != 0)
212 Itm.Name += 2;
213
214 // Grab the link target
215 Tar->Name[sizeof(Tar->LinkName)] = 0;
216 Itm.LinkTarget = Tar->LinkName;
217
218 if (LastLongLink.empty() == false)
219 Itm.LinkTarget = (char *)LastLongLink.c_str();
220
221 // Convert the type over
222 switch (Tar->LinkFlag)
223 {
224 case NormalFile0:
225 case NormalFile:
226 Itm.Type = pkgDirStream::Item::File;
227 break;
228
229 case HardLink:
230 Itm.Type = pkgDirStream::Item::HardLink;
231 break;
232
233 case SymbolicLink:
234 Itm.Type = pkgDirStream::Item::SymbolicLink;
235 break;
236
237 case CharacterDevice:
238 Itm.Type = pkgDirStream::Item::CharDevice;
239 break;
240
241 case BlockDevice:
242 Itm.Type = pkgDirStream::Item::BlockDevice;
243 break;
244
245 case Directory:
246 Itm.Type = pkgDirStream::Item::Directory;
247 break;
248
249 case FIFO:
250 Itm.Type = pkgDirStream::Item::FIFO;
251 break;
252
253 case GNU_LongLink:
254 {
255 unsigned long Length = Itm.Size;
256 unsigned char Block[512];
257 while (Length > 0)
258 {
259 if (InFd.Read(Block,sizeof(Block),true) == false)
260 return false;
261 if (Length <= sizeof(Block))
262 {
263 LastLongLink.append(Block,Block+sizeof(Block));
264 break;
265 }
266 LastLongLink.append(Block,Block+sizeof(Block));
267 Length -= sizeof(Block);
268 }
269 continue;
270 }
271
272 case GNU_LongName:
273 {
274 unsigned long Length = Itm.Size;
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 LastLongName.append(Block,Block+sizeof(Block));
283 break;
284 }
285 LastLongName.append(Block,Block+sizeof(Block));
286 Length -= sizeof(Block);
287 }
288 continue;
289 }
290
291 default:
292 BadRecord = true;
293 _error->Warning("Unkown TAR header type %u, member %s",(unsigned)Tar->LinkFlag,Tar->Name);
294 break;
295 }
296
297 int Fd = -1;
298 if (BadRecord == false)
299 if (Stream.DoItem(Itm,Fd) == false)
300 return false;
301
302 // Copy the file over the FD
303 unsigned long Size = Itm.Size;
304 while (Size != 0)
305 {
306 unsigned char Junk[32*1024];
307 unsigned long Read = MIN(Size,sizeof(Junk));
308 if (InFd.Read(Junk,((Read+511)/512)*512) == false)
309 return false;
310
311 if (BadRecord == false)
312 {
313 if (Fd > 0)
314 {
315 if (write(Fd,Junk,Read) != (signed)Read)
316 return Stream.Fail(Itm,Fd);
317 }
318 else
319 {
320 /* An Fd of -2 means to send to a special processing
321 function */
322 if (Fd == -2)
323 if (Stream.Process(Itm,Junk,Read,Itm.Size - Size) == false)
324 return Stream.Fail(Itm,Fd);
325 }
326 }
327
328 Size -= Read;
329 }
330
331 // And finish up
332 if (Itm.Size != 0 && BadRecord == false)
333 if (Stream.FinishedFile(Itm,Fd) == false)
334 return false;
335
336 LastLongName.erase();
337 LastLongLink.erase();
338 }
339
340 return Done(false);
341}
342 /*}}}*/