1 // -*- mode: cpp; mode: fold -*-
3 // $Id: extracttar.cc,v 1.8.2.1 2004/01/16 18:58:50 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 /*{{{*/
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>
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 string argv0
= _config
->Find(confvar
.c_str(),DecompressProg
.c_str());
140 Args
[0] = argv0
.c_str();
143 execvp(Args
[0],(char **)Args
);
144 cerr
<< _("Failed to exec gzip ") << Args
[0] << endl
;
149 InFd
.OpenDescriptor(Pipes
[0], FileFd::ReadOnly
, FileFd::None
, true);
154 // ExtractTar::Go - Perform extraction /*{{{*/
155 // ---------------------------------------------------------------------
156 /* This reads each 512 byte block from the archive and extracts the header
157 information into the Item structure. Then it resolves the UID/GID and
158 invokes the correct processing function. */
159 bool ExtractTar::Go(pkgDirStream
&Stream
)
161 if (StartGzip() == false)
164 // Loop over all blocks
169 bool BadRecord
= false;
170 unsigned char Block
[512];
171 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
174 if (InFd
.Eof() == true)
178 TarHeader
*Tar
= (TarHeader
*)Block
;
179 unsigned long CheckSum
;
180 if (StrToNum(Tar
->Checksum
,CheckSum
,sizeof(Tar
->Checksum
),8) == false)
181 return _error
->Error(_("Corrupted archive"));
183 /* Compute the checksum field. The actual checksum is blanked out
184 with spaces so it is not included in the computation */
185 unsigned long NewSum
= 0;
186 memset(Tar
->Checksum
,' ',sizeof(Tar
->Checksum
));
187 for (int I
= 0; I
!= sizeof(Block
); I
++)
190 /* Check for a block of nulls - in this case we kill gzip, GNU tar
192 if (NewSum
== ' '*sizeof(Tar
->Checksum
))
195 if (NewSum
!= CheckSum
)
196 return _error
->Error(_("Tar checksum failed, archive corrupted"));
198 // Decode all of the fields
199 pkgDirStream::Item Itm
;
200 if (StrToNum(Tar
->Mode
,Itm
.Mode
,sizeof(Tar
->Mode
),8) == false ||
201 (Base256ToNum(Tar
->UserID
,Itm
.UID
,8) == false &&
202 StrToNum(Tar
->UserID
,Itm
.UID
,sizeof(Tar
->UserID
),8) == false) ||
203 (Base256ToNum(Tar
->GroupID
,Itm
.GID
,8) == false &&
204 StrToNum(Tar
->GroupID
,Itm
.GID
,sizeof(Tar
->GroupID
),8) == false) ||
205 (Base256ToNum(Tar
->Size
,Itm
.Size
,12) == false &&
206 StrToNum(Tar
->Size
,Itm
.Size
,sizeof(Tar
->Size
),8) == false) ||
207 (Base256ToNum(Tar
->MTime
,Itm
.MTime
,12) == false &&
208 StrToNum(Tar
->MTime
,Itm
.MTime
,sizeof(Tar
->MTime
),8) == false) ||
209 StrToNum(Tar
->Major
,Itm
.Major
,sizeof(Tar
->Major
),8) == false ||
210 StrToNum(Tar
->Minor
,Itm
.Minor
,sizeof(Tar
->Minor
),8) == false)
211 return _error
->Error(_("Corrupted archive"));
214 if (LastLongName
.empty() == false)
215 Itm
.Name
= (char *)LastLongName
.c_str();
218 Tar
->Name
[sizeof(Tar
->Name
)-1] = 0;
219 Itm
.Name
= Tar
->Name
;
221 if (Itm
.Name
[0] == '.' && Itm
.Name
[1] == '/' && Itm
.Name
[2] != 0)
224 // Grab the link target
225 Tar
->Name
[sizeof(Tar
->LinkName
)-1] = 0;
226 Itm
.LinkTarget
= Tar
->LinkName
;
228 if (LastLongLink
.empty() == false)
229 Itm
.LinkTarget
= (char *)LastLongLink
.c_str();
231 // Convert the type over
232 switch (Tar
->LinkFlag
)
236 Itm
.Type
= pkgDirStream::Item::File
;
240 Itm
.Type
= pkgDirStream::Item::HardLink
;
244 Itm
.Type
= pkgDirStream::Item::SymbolicLink
;
247 case CharacterDevice
:
248 Itm
.Type
= pkgDirStream::Item::CharDevice
;
252 Itm
.Type
= pkgDirStream::Item::BlockDevice
;
256 Itm
.Type
= pkgDirStream::Item::Directory
;
260 Itm
.Type
= pkgDirStream::Item::FIFO
;
265 unsigned long Length
= Itm
.Size
;
266 unsigned char Block
[512];
269 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
271 if (Length
<= sizeof(Block
))
273 LastLongLink
.append(Block
,Block
+sizeof(Block
));
276 LastLongLink
.append(Block
,Block
+sizeof(Block
));
277 Length
-= sizeof(Block
);
284 unsigned long Length
= Itm
.Size
;
285 unsigned char Block
[512];
288 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
290 if (Length
< sizeof(Block
))
292 LastLongName
.append(Block
,Block
+sizeof(Block
));
295 LastLongName
.append(Block
,Block
+sizeof(Block
));
296 Length
-= sizeof(Block
);
303 _error
->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar
->LinkFlag
,Tar
->Name
);
308 if (BadRecord
== false)
309 if (Stream
.DoItem(Itm
,Fd
) == false)
312 // Copy the file over the FD
313 unsigned long Size
= Itm
.Size
;
316 unsigned char Junk
[32*1024];
317 unsigned long Read
= min(Size
,(unsigned long)sizeof(Junk
));
318 if (InFd
.Read(Junk
,((Read
+511)/512)*512) == false)
321 if (BadRecord
== false)
325 if (write(Fd
,Junk
,Read
) != (signed)Read
)
326 return Stream
.Fail(Itm
,Fd
);
330 /* An Fd of -2 means to send to a special processing
333 if (Stream
.Process(Itm
,Junk
,Read
,Itm
.Size
- Size
) == false)
334 return Stream
.Fail(Itm
,Fd
);
342 if (BadRecord
== false)
343 if (Stream
.FinishedFile(Itm
,Fd
) == false)
346 LastLongName
.erase();
347 LastLongLink
.erase();