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