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