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 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
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/fileutl.h>
41 // The on disk header for a tar file.
42 struct ExtractTar::TarHeader
60 // ExtractTar::ExtractTar - Constructor /*{{{*/
61 // ---------------------------------------------------------------------
63 #if APT_PKG_ABI >= 413
64 ExtractTar::ExtractTar(FileFd
&Fd
,unsigned long long Max
,string DecompressionProgram
)
65 : File(Fd
), MaxInSize(Max
), DecompressProg(DecompressionProgram
)
67 ExtractTar::ExtractTar(FileFd
&Fd
,unsigned long Max
,string DecompressionProgram
)
68 : File(Fd
), MaxInSize(Max
), DecompressProg(DecompressionProgram
)
75 // ExtractTar::ExtractTar - Destructor /*{{{*/
76 // ---------------------------------------------------------------------
78 ExtractTar::~ExtractTar()
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
)
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)
99 // Make sure we clean it up!
101 string confvar
= string("dir::bin::") + DecompressProg
;
102 if (ExecWait(GZPid
,_config
->Find(confvar
.c_str(),DecompressProg
.c_str()).c_str(),
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()
120 if (DecompressProg
.empty())
122 InFd
.OpenDescriptor(File
.Fd(), FileFd::ReadOnly
, FileFd::None
, false);
127 if (pipe(Pipes
) != 0)
128 return _error
->Errno("pipe",_("Failed to create pipes"));
130 // Fork off the process
133 // Spawn the subprocess
137 dup2(Pipes
[1],STDOUT_FILENO
);
138 dup2(File
.Fd(),STDIN_FILENO
);
139 int Fd
= open("/dev/null",O_RDWR
);
142 dup2(Fd
,STDERR_FILENO
);
144 SetCloseExec(STDOUT_FILENO
,false);
145 SetCloseExec(STDIN_FILENO
,false);
146 SetCloseExec(STDERR_FILENO
,false);
149 string confvar
= string("dir::bin::") + DecompressProg
;
150 string argv0
= _config
->Find(confvar
.c_str(),DecompressProg
.c_str());
151 Args
[0] = argv0
.c_str();
154 execvp(Args
[0],(char **)Args
);
155 cerr
<< _("Failed to exec gzip ") << Args
[0] << endl
;
160 InFd
.OpenDescriptor(Pipes
[0], FileFd::ReadOnly
, FileFd::None
, true);
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
)
172 if (StartGzip() == false)
175 // Loop over all blocks
176 string LastLongLink
, ItemLink
;
177 string LastLongName
, ItemName
;
180 bool BadRecord
= false;
181 unsigned char Block
[512];
182 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
185 if (InFd
.Eof() == true)
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"));
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
++)
201 /* Check for a block of nulls - in this case we kill gzip, GNU tar
203 if (NewSum
== ' '*sizeof(Tar
->Checksum
))
206 if (NewSum
!= CheckSum
)
207 return _error
->Error(_("Tar checksum failed, archive corrupted"));
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"));
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();
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)
235 if (LastLongLink
.empty() == false)
236 Itm
.LinkTarget
= (char *)LastLongLink
.c_str();
238 Itm
.LinkTarget
= (char *)ItemLink
.assign(Tar
->LinkName
, sizeof(Tar
->LinkName
)).c_str();
240 // Convert the type over
241 switch (Tar
->LinkFlag
)
245 Itm
.Type
= pkgDirStream::Item::File
;
249 Itm
.Type
= pkgDirStream::Item::HardLink
;
253 Itm
.Type
= pkgDirStream::Item::SymbolicLink
;
256 case CharacterDevice
:
257 Itm
.Type
= pkgDirStream::Item::CharDevice
;
261 Itm
.Type
= pkgDirStream::Item::BlockDevice
;
265 Itm
.Type
= pkgDirStream::Item::Directory
;
269 Itm
.Type
= pkgDirStream::Item::FIFO
;
274 unsigned long long Length
= Itm
.Size
;
275 unsigned char Block
[512];
278 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
280 if (Length
<= sizeof(Block
))
282 LastLongLink
.append(Block
,Block
+sizeof(Block
));
285 LastLongLink
.append(Block
,Block
+sizeof(Block
));
286 Length
-= sizeof(Block
);
293 unsigned long long Length
= Itm
.Size
;
294 unsigned char Block
[512];
297 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
299 if (Length
< sizeof(Block
))
301 LastLongName
.append(Block
,Block
+sizeof(Block
));
304 LastLongName
.append(Block
,Block
+sizeof(Block
));
305 Length
-= sizeof(Block
);
312 _error
->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar
->LinkFlag
,Tar
->Name
);
317 if (BadRecord
== false)
318 if (Stream
.DoItem(Itm
,Fd
) == false)
321 // Copy the file over the FD
322 unsigned long long Size
= Itm
.Size
;
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)
330 if (BadRecord
== false)
334 if (write(Fd
,Junk
,Read
) != (signed)Read
)
335 return Stream
.Fail(Itm
,Fd
);
339 /* An Fd of -2 means to send to a special processing
342 if (Stream
.Process(Itm
,Junk
,Read
,Itm
.Size
- Size
) == false)
343 return Stream
.Fail(Itm
,Fd
);
351 if (BadRecord
== false)
352 if (Stream
.FinishedFile(Itm
,Fd
) == false)
355 LastLongName
.erase();
356 LastLongLink
.erase();