1 // -*- mode: cpp; mode: fold -*-
3 // $Id: extracttar.cc,v 1.9 2004/01/07 20:39:37 mdz Exp $
4 /* ######################################################################
6 Extract a Tar - Tar Extractor
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
16 ##################################################################### */
18 // Include Files /*{{{*/
20 #pragma implementation "apt-pkg/extracttar.h"
22 #include <apt-pkg/extracttar.h>
24 #include <apt-pkg/error.h>
25 #include <apt-pkg/strutl.h>
26 #include <apt-pkg/configuration.h>
39 // The on disk header for a tar file.
40 struct ExtractTar::TarHeader
58 // ExtractTar::ExtractTar - Constructor /*{{{*/
59 // ---------------------------------------------------------------------
61 ExtractTar::ExtractTar(FileFd
&Fd
,unsigned long Max
,string DecompressionProgram
) : File(Fd
),
62 MaxInSize(Max
), DecompressProg(DecompressionProgram
)
70 // ExtractTar::ExtractTar - Destructor /*{{{*/
71 // ---------------------------------------------------------------------
73 ExtractTar::~ExtractTar()
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
)
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)
94 // Make sure we clean it up!
96 string confvar
= string("dir::bin::") + DecompressProg
;
97 if (ExecWait(GZPid
,_config
->Find(confvar
.c_str(),DecompressProg
.c_str()).c_str(),
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()
116 if (pipe(Pipes
) != 0)
117 return _error
->Errno("pipe",_("Failed to create pipes"));
119 // Fork off the process
122 // Spawn the subprocess
126 dup2(Pipes
[1],STDOUT_FILENO
);
127 dup2(File
.Fd(),STDIN_FILENO
);
128 int Fd
= open("/dev/null",O_RDWR
);
131 dup2(Fd
,STDERR_FILENO
);
133 SetCloseExec(STDOUT_FILENO
,false);
134 SetCloseExec(STDIN_FILENO
,false);
135 SetCloseExec(STDERR_FILENO
,false);
138 string confvar
= string("dir::bin::") + DecompressProg
;
139 Args
[0] = _config
->Find(confvar
.c_str(),DecompressProg
.c_str()).c_str();
142 execvp(Args
[0],(char **)Args
);
143 cerr
<< _("Failed to exec gzip ") << Args
[0] << endl
;
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
)
160 if (StartGzip() == false)
163 // Loop over all blocks
168 bool BadRecord
= false;
169 unsigned char Block
[512];
170 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
173 if (InFd
.Eof() == true)
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"));
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
++)
189 /* Check for a block of nulls - in this case we kill gzip, GNU tar
191 if (NewSum
== ' '*sizeof(Tar
->Checksum
))
194 if (NewSum
!= CheckSum
)
195 return _error
->Error(_("Tar Checksum failed, archive corrupted"));
197 // Decode all of the fields
198 pkgDirStream::Item Itm
;
199 if (StrToNum(Tar
->Mode
,Itm
.Mode
,sizeof(Tar
->Mode
),8) == false ||
200 StrToNum(Tar
->UserID
,Itm
.UID
,sizeof(Tar
->UserID
),8) == false ||
201 StrToNum(Tar
->GroupID
,Itm
.GID
,sizeof(Tar
->GroupID
),8) == false ||
202 StrToNum(Tar
->Size
,Itm
.Size
,sizeof(Tar
->Size
),8) == false ||
203 StrToNum(Tar
->MTime
,Itm
.MTime
,sizeof(Tar
->MTime
),8) == false ||
204 StrToNum(Tar
->Major
,Itm
.Major
,sizeof(Tar
->Major
),8) == false ||
205 StrToNum(Tar
->Minor
,Itm
.Minor
,sizeof(Tar
->Minor
),8) == false)
206 return _error
->Error(_("Corrupted archive"));
209 if (LastLongName
.empty() == false)
210 Itm
.Name
= (char *)LastLongName
.c_str();
213 Tar
->Name
[sizeof(Tar
->Name
)] = 0;
214 Itm
.Name
= Tar
->Name
;
216 if (Itm
.Name
[0] == '.' && Itm
.Name
[1] == '/' && Itm
.Name
[2] != 0)
219 // Grab the link target
220 Tar
->Name
[sizeof(Tar
->LinkName
)] = 0;
221 Itm
.LinkTarget
= Tar
->LinkName
;
223 if (LastLongLink
.empty() == false)
224 Itm
.LinkTarget
= (char *)LastLongLink
.c_str();
226 // Convert the type over
227 switch (Tar
->LinkFlag
)
231 Itm
.Type
= pkgDirStream::Item::File
;
235 Itm
.Type
= pkgDirStream::Item::HardLink
;
239 Itm
.Type
= pkgDirStream::Item::SymbolicLink
;
242 case CharacterDevice
:
243 Itm
.Type
= pkgDirStream::Item::CharDevice
;
247 Itm
.Type
= pkgDirStream::Item::BlockDevice
;
251 Itm
.Type
= pkgDirStream::Item::Directory
;
255 Itm
.Type
= pkgDirStream::Item::FIFO
;
260 unsigned long Length
= Itm
.Size
;
261 unsigned char Block
[512];
264 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
266 if (Length
<= sizeof(Block
))
268 LastLongLink
.append(Block
,Block
+sizeof(Block
));
271 LastLongLink
.append(Block
,Block
+sizeof(Block
));
272 Length
-= sizeof(Block
);
279 unsigned long Length
= Itm
.Size
;
280 unsigned char Block
[512];
283 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
285 if (Length
< sizeof(Block
))
287 LastLongName
.append(Block
,Block
+sizeof(Block
));
290 LastLongName
.append(Block
,Block
+sizeof(Block
));
291 Length
-= sizeof(Block
);
298 _error
->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar
->LinkFlag
,Tar
->Name
);
303 if (BadRecord
== false)
304 if (Stream
.DoItem(Itm
,Fd
) == false)
307 // Copy the file over the FD
308 unsigned long Size
= Itm
.Size
;
311 unsigned char Junk
[32*1024];
312 unsigned long Read
= MIN(Size
,sizeof(Junk
));
313 if (InFd
.Read(Junk
,((Read
+511)/512)*512) == false)
316 if (BadRecord
== false)
320 if (write(Fd
,Junk
,Read
) != (signed)Read
)
321 return Stream
.Fail(Itm
,Fd
);
325 /* An Fd of -2 means to send to a special processing
328 if (Stream
.Process(Itm
,Junk
,Read
,Itm
.Size
- Size
) == false)
329 return Stream
.Fail(Itm
,Fd
);
337 if (Itm
.Size
!= 0 && BadRecord
== false)
338 if (Stream
.FinishedFile(Itm
,Fd
) == false)
341 LastLongName
.erase();
342 LastLongLink
.erase();