]> git.saurik.com Git - apt.git/blob - apt-inst/contrib/extracttar.cc
Merge branch 'debian/sid' into debian/experimental
[apt.git] / apt-inst / contrib / extracttar.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: extracttar.cc,v 1.8.2.1 2004/01/16 18:58:50 mdz Exp $
4 /* ######################################################################
5
6 Extract a Tar - Tar Extractor
7
8 Some performance measurements showed that zlib performed quite poorly
9 in comparison 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 #include<config.h>
20
21 #include <apt-pkg/dirstream.h>
22 #include <apt-pkg/extracttar.h>
23 #include <apt-pkg/error.h>
24 #include <apt-pkg/strutl.h>
25 #include <apt-pkg/configuration.h>
26 #include <apt-pkg/fileutl.h>
27
28 #include <string.h>
29 #include <algorithm>
30 #include <string>
31 #include <unistd.h>
32 #include <signal.h>
33 #include <fcntl.h>
34 #include <iostream>
35
36 #include <apti18n.h>
37 /*}}}*/
38
39 using namespace std;
40
41 // The on disk header for a tar file.
42 struct 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 /* */
63 ExtractTar::ExtractTar(FileFd &Fd,unsigned long long Max,string DecompressionProgram)
64 : File(Fd), MaxInSize(Max), DecompressProg(DecompressionProgram)
65 {
66 GZPid = -1;
67 Eof = false;
68 }
69 /*}}}*/
70 // ExtractTar::ExtractTar - Destructor /*{{{*/
71 // ---------------------------------------------------------------------
72 /* */
73 ExtractTar::~ExtractTar()
74 {
75 // Error close
76 Done(true);
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. */
83 bool 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 string confvar = string("dir::bin::") + DecompressProg;
97 if (ExecWait(GZPid,_config->Find(confvar.c_str(),DecompressProg.c_str()).c_str(),
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. */
113 bool ExtractTar::StartGzip()
114 {
115 if (DecompressProg.empty())
116 {
117 InFd.OpenDescriptor(File.Fd(), FileFd::ReadOnly, FileFd::None, false);
118 return true;
119 }
120
121 int Pipes[2];
122 if (pipe(Pipes) != 0)
123 return _error->Errno("pipe",_("Failed to create pipes"));
124
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);
140 SetCloseExec(STDIN_FILENO,false);
141 SetCloseExec(STDERR_FILENO,false);
142
143 const char *Args[3];
144 string confvar = string("dir::bin::") + DecompressProg;
145 string argv0 = _config->Find(confvar.c_str(),DecompressProg.c_str());
146 Args[0] = argv0.c_str();
147 Args[1] = "-d";
148 Args[2] = 0;
149 execvp(Args[0],(char **)Args);
150 cerr << _("Failed to exec gzip ") << Args[0] << endl;
151 _exit(100);
152 }
153
154 // Fix up our FDs
155 InFd.OpenDescriptor(Pipes[0], FileFd::ReadOnly, FileFd::None, true);
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. */
165 bool ExtractTar::Go(pkgDirStream &Stream)
166 {
167 if (StartGzip() == false)
168 return false;
169
170 // Loop over all blocks
171 string LastLongLink, ItemLink;
172 string LastLongName, ItemName;
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)
187 return _error->Error(_("Corrupted archive"));
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)
202 return _error->Error(_("Tar checksum failed, archive corrupted"));
203
204 // Decode all of the fields
205 pkgDirStream::Item Itm;
206 if (StrToNum(Tar->Mode,Itm.Mode,sizeof(Tar->Mode),8) == false ||
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) ||
215 StrToNum(Tar->Major,Itm.Major,sizeof(Tar->Major),8) == false ||
216 StrToNum(Tar->Minor,Itm.Minor,sizeof(Tar->Minor),8) == false)
217 return _error->Error(_("Corrupted archive"));
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.
223 if (LastLongName.empty() == false)
224 Itm.Name = (char *)LastLongName.c_str();
225 else
226 Itm.Name = (char *)ItemName.assign(Tar->Name, sizeof(Tar->Name)).c_str();
227 if (Itm.Name[0] == '.' && Itm.Name[1] == '/' && Itm.Name[2] != 0)
228 Itm.Name += 2;
229
230 if (LastLongLink.empty() == false)
231 Itm.LinkTarget = (char *)LastLongLink.c_str();
232 else
233 Itm.LinkTarget = (char *)ItemLink.assign(Tar->LinkName, sizeof(Tar->LinkName)).c_str();
234
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 {
269 unsigned long long Length = Itm.Size;
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 {
288 unsigned long long Length = Itm.Size;
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;
307 _error->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar->LinkFlag,Tar->Name);
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
317 unsigned long long Size = Itm.Size;
318 while (Size != 0)
319 {
320 unsigned char Junk[32*1024];
321 unsigned long Read = min(Size, (unsigned long long)sizeof(Junk));
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
346 if (BadRecord == false)
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 /*}}}*/