Use baz instead of tla
[ntk/apt.git] / apt-inst / contrib / extracttar.cc
CommitLineData
b2e465d6
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
d77559ac 3// $Id: extracttar.cc,v 1.9 2004/01/07 20:39:37 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 /*{{{*/
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>
d77559ac 34#include <apti18n.h>
b2e465d6 35 /*}}}*/
d77559ac 36
584e4558 37using namespace std;
4520bfdf 38
b2e465d6
AL
39// The on disk header for a tar file.
40struct ExtractTar::TarHeader
41{
42 char Name[100];
43 char Mode[8];
44 char UserID[8];
45 char GroupID[8];
46 char Size[12];
47 char MTime[12];
48 char Checksum[8];
49 char LinkFlag;
50 char LinkName[100];
51 char MagicNumber[8];
52 char UserName[32];
53 char GroupName[32];
54 char Major[8];
55 char Minor[8];
56};
57
58// ExtractTar::ExtractTar - Constructor /*{{{*/
59// ---------------------------------------------------------------------
60/* */
61ExtractTar::ExtractTar(FileFd &Fd,unsigned long Max) : File(Fd),
62 MaxInSize(Max)
63
64{
65 GZPid = -1;
66 InFd = -1;
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);
96 if (ExecWait(GZPid,_config->Find("dir::bin::gzip","/bin/gzip").c_str(),
97 Force) == false)
98 {
99 GZPid = -1;
100 return Force;
101 }
102
103 GZPid = -1;
104 return true;
105}
106 /*}}}*/
107// ExtractTar::StartGzip - Startup gzip /*{{{*/
108// ---------------------------------------------------------------------
109/* This creates a gzip sub process that has its input as the file itself.
110 If this tar file is embedded into something like an ar file then
111 gzip will efficiently ignore the extra bits. */
112bool ExtractTar::StartGzip()
113{
114 int Pipes[2];
115 if (pipe(Pipes) != 0)
05eb7df0 116 return _error->Errno("pipe",_("Failed to create pipes"));
b2e465d6
AL
117
118 // Fork off the process
119 GZPid = ExecFork();
120
121 // Spawn the subprocess
122 if (GZPid == 0)
123 {
124 // Setup the FDs
125 dup2(Pipes[1],STDOUT_FILENO);
126 dup2(File.Fd(),STDIN_FILENO);
127 int Fd = open("/dev/null",O_RDWR);
128 if (Fd == -1)
129 _exit(101);
130 dup2(Fd,STDERR_FILENO);
131 close(Fd);
132 SetCloseExec(STDOUT_FILENO,false);
133 SetCloseExec(STDIN_FILENO,false);
134 SetCloseExec(STDERR_FILENO,false);
135
136 const char *Args[3];
137 Args[0] = _config->Find("dir::bin::gzip","/bin/gzip").c_str();
138 Args[1] = "-d";
139 Args[2] = 0;
140 execv(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)
05eb7df0 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 ||
4520bfdf
AL
198 StrToNum(Tar->UserID,Itm.UID,sizeof(Tar->UserID),8) == false ||
199 StrToNum(Tar->GroupID,Itm.GID,sizeof(Tar->GroupID),8) == false ||
b2e465d6
AL
200 StrToNum(Tar->Size,Itm.Size,sizeof(Tar->Size),8) == false ||
201 StrToNum(Tar->MTime,Itm.MTime,sizeof(Tar->MTime),8) == false ||
202 StrToNum(Tar->Major,Itm.Major,sizeof(Tar->Major),8) == false ||
203 StrToNum(Tar->Minor,Itm.Minor,sizeof(Tar->Minor),8) == false)
05eb7df0 204 return _error->Error(_("Corrupted archive"));
b2e465d6
AL
205
206 // Grab the filename
207 if (LastLongName.empty() == false)
208 Itm.Name = (char *)LastLongName.c_str();
209 else
210 {
211 Tar->Name[sizeof(Tar->Name)] = 0;
212 Itm.Name = Tar->Name;
213 }
214 if (Itm.Name[0] == '.' && Itm.Name[1] == '/' && Itm.Name[2] != 0)
215 Itm.Name += 2;
216
217 // Grab the link target
218 Tar->Name[sizeof(Tar->LinkName)] = 0;
219 Itm.LinkTarget = Tar->LinkName;
220
221 if (LastLongLink.empty() == false)
222 Itm.LinkTarget = (char *)LastLongLink.c_str();
223
224 // Convert the type over
225 switch (Tar->LinkFlag)
226 {
227 case NormalFile0:
228 case NormalFile:
229 Itm.Type = pkgDirStream::Item::File;
230 break;
231
232 case HardLink:
233 Itm.Type = pkgDirStream::Item::HardLink;
234 break;
235
236 case SymbolicLink:
237 Itm.Type = pkgDirStream::Item::SymbolicLink;
238 break;
239
240 case CharacterDevice:
241 Itm.Type = pkgDirStream::Item::CharDevice;
242 break;
243
244 case BlockDevice:
245 Itm.Type = pkgDirStream::Item::BlockDevice;
246 break;
247
248 case Directory:
249 Itm.Type = pkgDirStream::Item::Directory;
250 break;
251
252 case FIFO:
253 Itm.Type = pkgDirStream::Item::FIFO;
254 break;
255
256 case GNU_LongLink:
257 {
258 unsigned long Length = Itm.Size;
259 unsigned char Block[512];
260 while (Length > 0)
261 {
262 if (InFd.Read(Block,sizeof(Block),true) == false)
263 return false;
264 if (Length <= sizeof(Block))
265 {
266 LastLongLink.append(Block,Block+sizeof(Block));
267 break;
268 }
269 LastLongLink.append(Block,Block+sizeof(Block));
270 Length -= sizeof(Block);
271 }
272 continue;
273 }
274
275 case GNU_LongName:
276 {
277 unsigned long Length = Itm.Size;
278 unsigned char Block[512];
279 while (Length > 0)
280 {
281 if (InFd.Read(Block,sizeof(Block),true) == false)
282 return false;
283 if (Length < sizeof(Block))
284 {
285 LastLongName.append(Block,Block+sizeof(Block));
286 break;
287 }
288 LastLongName.append(Block,Block+sizeof(Block));
289 Length -= sizeof(Block);
290 }
291 continue;
292 }
293
294 default:
295 BadRecord = true;
4c6a9fad 296 _error->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar->LinkFlag,Tar->Name);
b2e465d6
AL
297 break;
298 }
299
300 int Fd = -1;
301 if (BadRecord == false)
302 if (Stream.DoItem(Itm,Fd) == false)
303 return false;
304
305 // Copy the file over the FD
306 unsigned long Size = Itm.Size;
307 while (Size != 0)
308 {
309 unsigned char Junk[32*1024];
310 unsigned long Read = MIN(Size,sizeof(Junk));
311 if (InFd.Read(Junk,((Read+511)/512)*512) == false)
312 return false;
313
314 if (BadRecord == false)
315 {
316 if (Fd > 0)
317 {
318 if (write(Fd,Junk,Read) != (signed)Read)
319 return Stream.Fail(Itm,Fd);
320 }
321 else
322 {
323 /* An Fd of -2 means to send to a special processing
324 function */
325 if (Fd == -2)
326 if (Stream.Process(Itm,Junk,Read,Itm.Size - Size) == false)
327 return Stream.Fail(Itm,Fd);
328 }
329 }
330
331 Size -= Read;
332 }
333
334 // And finish up
335 if (Itm.Size != 0 && BadRecord == false)
336 if (Stream.FinishedFile(Itm,Fd) == false)
337 return false;
338
339 LastLongName.erase();
340 LastLongLink.erase();
341 }
342
343 return Done(false);
344}
345 /*}}}*/