]> git.saurik.com Git - apt.git/blob - apt-inst/contrib/extracttar.cc
Full translation for 0.5.5
[apt.git] / apt-inst / contrib / extracttar.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: extracttar.cc,v 1.7 2003/02/10 00:36:12 doogie 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 #ifdef __GNUG__
20 #pragma implementation "apt-pkg/extracttar.h"
21 #endif
22 #include <apti18n.h>
23 #include <apt-pkg/extracttar.h>
24
25 #include <apt-pkg/error.h>
26 #include <apt-pkg/strutl.h>
27 #include <apt-pkg/configuration.h>
28 #include <system.h>
29
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <signal.h>
33 #include <fcntl.h>
34 #include <iostream>
35 /*}}}*/
36 using namespace std;
37
38 // The on disk header for a tar file.
39 struct ExtractTar::TarHeader
40 {
41 char Name[100];
42 char Mode[8];
43 char UserID[8];
44 char GroupID[8];
45 char Size[12];
46 char MTime[12];
47 char Checksum[8];
48 char LinkFlag;
49 char LinkName[100];
50 char MagicNumber[8];
51 char UserName[32];
52 char GroupName[32];
53 char Major[8];
54 char Minor[8];
55 };
56
57 // ExtractTar::ExtractTar - Constructor /*{{{*/
58 // ---------------------------------------------------------------------
59 /* */
60 ExtractTar::ExtractTar(FileFd &Fd,unsigned long Max) : File(Fd),
61 MaxInSize(Max)
62
63 {
64 GZPid = -1;
65 InFd = -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 if (ExecWait(GZPid,_config->Find("dir::bin::gzip","/bin/gzip").c_str(),
96 Force) == false)
97 {
98 GZPid = -1;
99 return Force;
100 }
101
102 GZPid = -1;
103 return true;
104 }
105 /*}}}*/
106 // ExtractTar::StartGzip - Startup gzip /*{{{*/
107 // ---------------------------------------------------------------------
108 /* This creates a gzip sub process that has its input as the file itself.
109 If this tar file is embedded into something like an ar file then
110 gzip will efficiently ignore the extra bits. */
111 bool ExtractTar::StartGzip()
112 {
113 int Pipes[2];
114 if (pipe(Pipes) != 0)
115 return _error->Errno("pipe",_("Failed to create pipes"));
116
117 // Fork off the process
118 GZPid = ExecFork();
119
120 // Spawn the subprocess
121 if (GZPid == 0)
122 {
123 // Setup the FDs
124 dup2(Pipes[1],STDOUT_FILENO);
125 dup2(File.Fd(),STDIN_FILENO);
126 int Fd = open("/dev/null",O_RDWR);
127 if (Fd == -1)
128 _exit(101);
129 dup2(Fd,STDERR_FILENO);
130 close(Fd);
131 SetCloseExec(STDOUT_FILENO,false);
132 SetCloseExec(STDIN_FILENO,false);
133 SetCloseExec(STDERR_FILENO,false);
134
135 const char *Args[3];
136 Args[0] = _config->Find("dir::bin::gzip","/bin/gzip").c_str();
137 Args[1] = "-d";
138 Args[2] = 0;
139 execv(Args[0],(char **)Args);
140 cerr << _("Failed to exec gzip ") << Args[0] << endl;
141 _exit(100);
142 }
143
144 // Fix up our FDs
145 InFd.Fd(Pipes[0]);
146 close(Pipes[1]);
147 return true;
148 }
149 /*}}}*/
150 // ExtractTar::Go - Perform extraction /*{{{*/
151 // ---------------------------------------------------------------------
152 /* This reads each 512 byte block from the archive and extracts the header
153 information into the Item structure. Then it resolves the UID/GID and
154 invokes the correct processing function. */
155 bool ExtractTar::Go(pkgDirStream &Stream)
156 {
157 if (StartGzip() == false)
158 return false;
159
160 // Loop over all blocks
161 string LastLongLink;
162 string LastLongName;
163 while (1)
164 {
165 bool BadRecord = false;
166 unsigned char Block[512];
167 if (InFd.Read(Block,sizeof(Block),true) == false)
168 return false;
169
170 if (InFd.Eof() == true)
171 break;
172
173 // Get the checksum
174 TarHeader *Tar = (TarHeader *)Block;
175 unsigned long CheckSum;
176 if (StrToNum(Tar->Checksum,CheckSum,sizeof(Tar->Checksum),8) == false)
177 return _error->Error(_("Corrupted archive"));
178
179 /* Compute the checksum field. The actual checksum is blanked out
180 with spaces so it is not included in the computation */
181 unsigned long NewSum = 0;
182 memset(Tar->Checksum,' ',sizeof(Tar->Checksum));
183 for (int I = 0; I != sizeof(Block); I++)
184 NewSum += Block[I];
185
186 /* Check for a block of nulls - in this case we kill gzip, GNU tar
187 does this.. */
188 if (NewSum == ' '*sizeof(Tar->Checksum))
189 return Done(true);
190
191 if (NewSum != CheckSum)
192 return _error->Error(_("Tar Checksum failed, archive corrupted"));
193
194 // Decode all of the fields
195 pkgDirStream::Item Itm;
196 if (StrToNum(Tar->Mode,Itm.Mode,sizeof(Tar->Mode),8) == false ||
197 StrToNum(Tar->UserID,Itm.UID,sizeof(Tar->UserID),8) == false ||
198 StrToNum(Tar->GroupID,Itm.GID,sizeof(Tar->GroupID),8) == false ||
199 StrToNum(Tar->Size,Itm.Size,sizeof(Tar->Size),8) == false ||
200 StrToNum(Tar->MTime,Itm.MTime,sizeof(Tar->MTime),8) == false ||
201 StrToNum(Tar->Major,Itm.Major,sizeof(Tar->Major),8) == false ||
202 StrToNum(Tar->Minor,Itm.Minor,sizeof(Tar->Minor),8) == false)
203 return _error->Error(_("Corrupted archive"));
204
205 // Grab the filename
206 if (LastLongName.empty() == false)
207 Itm.Name = (char *)LastLongName.c_str();
208 else
209 {
210 Tar->Name[sizeof(Tar->Name)] = 0;
211 Itm.Name = Tar->Name;
212 }
213 if (Itm.Name[0] == '.' && Itm.Name[1] == '/' && Itm.Name[2] != 0)
214 Itm.Name += 2;
215
216 // Grab the link target
217 Tar->Name[sizeof(Tar->LinkName)] = 0;
218 Itm.LinkTarget = Tar->LinkName;
219
220 if (LastLongLink.empty() == false)
221 Itm.LinkTarget = (char *)LastLongLink.c_str();
222
223 // Convert the type over
224 switch (Tar->LinkFlag)
225 {
226 case NormalFile0:
227 case NormalFile:
228 Itm.Type = pkgDirStream::Item::File;
229 break;
230
231 case HardLink:
232 Itm.Type = pkgDirStream::Item::HardLink;
233 break;
234
235 case SymbolicLink:
236 Itm.Type = pkgDirStream::Item::SymbolicLink;
237 break;
238
239 case CharacterDevice:
240 Itm.Type = pkgDirStream::Item::CharDevice;
241 break;
242
243 case BlockDevice:
244 Itm.Type = pkgDirStream::Item::BlockDevice;
245 break;
246
247 case Directory:
248 Itm.Type = pkgDirStream::Item::Directory;
249 break;
250
251 case FIFO:
252 Itm.Type = pkgDirStream::Item::FIFO;
253 break;
254
255 case GNU_LongLink:
256 {
257 unsigned long Length = Itm.Size;
258 unsigned char Block[512];
259 while (Length > 0)
260 {
261 if (InFd.Read(Block,sizeof(Block),true) == false)
262 return false;
263 if (Length <= sizeof(Block))
264 {
265 LastLongLink.append(Block,Block+sizeof(Block));
266 break;
267 }
268 LastLongLink.append(Block,Block+sizeof(Block));
269 Length -= sizeof(Block);
270 }
271 continue;
272 }
273
274 case GNU_LongName:
275 {
276 unsigned long Length = Itm.Size;
277 unsigned char Block[512];
278 while (Length > 0)
279 {
280 if (InFd.Read(Block,sizeof(Block),true) == false)
281 return false;
282 if (Length < sizeof(Block))
283 {
284 LastLongName.append(Block,Block+sizeof(Block));
285 break;
286 }
287 LastLongName.append(Block,Block+sizeof(Block));
288 Length -= sizeof(Block);
289 }
290 continue;
291 }
292
293 default:
294 BadRecord = true;
295 _error->Warning(_("Unkown TAR header type %u, member %s"),(unsigned)Tar->LinkFlag,Tar->Name);
296 break;
297 }
298
299 int Fd = -1;
300 if (BadRecord == false)
301 if (Stream.DoItem(Itm,Fd) == false)
302 return false;
303
304 // Copy the file over the FD
305 unsigned long Size = Itm.Size;
306 while (Size != 0)
307 {
308 unsigned char Junk[32*1024];
309 unsigned long Read = MIN(Size,sizeof(Junk));
310 if (InFd.Read(Junk,((Read+511)/512)*512) == false)
311 return false;
312
313 if (BadRecord == false)
314 {
315 if (Fd > 0)
316 {
317 if (write(Fd,Junk,Read) != (signed)Read)
318 return Stream.Fail(Itm,Fd);
319 }
320 else
321 {
322 /* An Fd of -2 means to send to a special processing
323 function */
324 if (Fd == -2)
325 if (Stream.Process(Itm,Junk,Read,Itm.Size - Size) == false)
326 return Stream.Fail(Itm,Fd);
327 }
328 }
329
330 Size -= Read;
331 }
332
333 // And finish up
334 if (Itm.Size != 0 && BadRecord == false)
335 if (Stream.FinishedFile(Itm,Fd) == false)
336 return false;
337
338 LastLongName.erase();
339 LastLongLink.erase();
340 }
341
342 return Done(false);
343 }
344 /*}}}*/