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