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 ExtractTar::ExtractTar(FileFd
&Fd
,unsigned long Max
,string DecompressionProgram
) : File(Fd
),
64 MaxInSize(Max
), DecompressProg(DecompressionProgram
)
71 // ExtractTar::ExtractTar - Destructor /*{{{*/
72 // ---------------------------------------------------------------------
74 ExtractTar::~ExtractTar()
80 // ExtractTar::Done - Reap the gzip sub process /*{{{*/
81 // ---------------------------------------------------------------------
82 /* If the force flag is given then error messages are suppressed - this
83 means we hit the end of the tar file but there was still gzip data. */
84 bool ExtractTar::Done(bool Force
)
90 /* If there is a pending error then we are cleaning up gzip and are
91 not interested in it's failures */
92 if (_error
->PendingError() == true)
95 // Make sure we clean it up!
97 string confvar
= string("dir::bin::") + DecompressProg
;
98 if (ExecWait(GZPid
,_config
->Find(confvar
.c_str(),DecompressProg
.c_str()).c_str(),
109 // ExtractTar::StartGzip - Startup gzip /*{{{*/
110 // ---------------------------------------------------------------------
111 /* This creates a gzip sub process that has its input as the file itself.
112 If this tar file is embedded into something like an ar file then
113 gzip will efficiently ignore the extra bits. */
114 bool ExtractTar::StartGzip()
116 if (DecompressProg
.empty())
118 InFd
.OpenDescriptor(File
.Fd(), FileFd::ReadOnly
, FileFd::None
, false);
123 if (pipe(Pipes
) != 0)
124 return _error
->Errno("pipe",_("Failed to create pipes"));
126 // Fork off the process
129 // Spawn the subprocess
133 dup2(Pipes
[1],STDOUT_FILENO
);
134 dup2(File
.Fd(),STDIN_FILENO
);
135 int Fd
= open("/dev/null",O_RDWR
);
138 dup2(Fd
,STDERR_FILENO
);
140 SetCloseExec(STDOUT_FILENO
,false);
141 SetCloseExec(STDIN_FILENO
,false);
142 SetCloseExec(STDERR_FILENO
,false);
145 string confvar
= string("dir::bin::") + DecompressProg
;
146 string argv0
= _config
->Find(confvar
.c_str(),DecompressProg
.c_str());
147 Args
[0] = argv0
.c_str();
150 execvp(Args
[0],(char **)Args
);
151 cerr
<< _("Failed to exec gzip ") << Args
[0] << endl
;
156 InFd
.OpenDescriptor(Pipes
[0], FileFd::ReadOnly
, FileFd::None
, true);
161 // ExtractTar::Go - Perform extraction /*{{{*/
162 // ---------------------------------------------------------------------
163 /* This reads each 512 byte block from the archive and extracts the header
164 information into the Item structure. Then it resolves the UID/GID and
165 invokes the correct processing function. */
166 bool ExtractTar::Go(pkgDirStream
&Stream
)
168 if (StartGzip() == false)
171 // Loop over all blocks
172 string LastLongLink
, ItemLink
;
173 string LastLongName
, ItemName
;
176 bool BadRecord
= false;
177 unsigned char Block
[512];
178 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
181 if (InFd
.Eof() == true)
185 TarHeader
*Tar
= (TarHeader
*)Block
;
186 unsigned long CheckSum
;
187 if (StrToNum(Tar
->Checksum
,CheckSum
,sizeof(Tar
->Checksum
),8) == false)
188 return _error
->Error(_("Corrupted archive"));
190 /* Compute the checksum field. The actual checksum is blanked out
191 with spaces so it is not included in the computation */
192 unsigned long NewSum
= 0;
193 memset(Tar
->Checksum
,' ',sizeof(Tar
->Checksum
));
194 for (int I
= 0; I
!= sizeof(Block
); I
++)
197 /* Check for a block of nulls - in this case we kill gzip, GNU tar
199 if (NewSum
== ' '*sizeof(Tar
->Checksum
))
202 if (NewSum
!= CheckSum
)
203 return _error
->Error(_("Tar checksum failed, archive corrupted"));
205 // Decode all of the fields
206 pkgDirStream::Item Itm
;
207 if (StrToNum(Tar
->Mode
,Itm
.Mode
,sizeof(Tar
->Mode
),8) == false ||
208 (Base256ToNum(Tar
->UserID
,Itm
.UID
,8) == false &&
209 StrToNum(Tar
->UserID
,Itm
.UID
,sizeof(Tar
->UserID
),8) == false) ||
210 (Base256ToNum(Tar
->GroupID
,Itm
.GID
,8) == false &&
211 StrToNum(Tar
->GroupID
,Itm
.GID
,sizeof(Tar
->GroupID
),8) == false) ||
212 (Base256ToNum(Tar
->Size
,Itm
.Size
,12) == false &&
213 StrToNum(Tar
->Size
,Itm
.Size
,sizeof(Tar
->Size
),8) == false) ||
214 (Base256ToNum(Tar
->MTime
,Itm
.MTime
,12) == false &&
215 StrToNum(Tar
->MTime
,Itm
.MTime
,sizeof(Tar
->MTime
),8) == false) ||
216 StrToNum(Tar
->Major
,Itm
.Major
,sizeof(Tar
->Major
),8) == false ||
217 StrToNum(Tar
->Minor
,Itm
.Minor
,sizeof(Tar
->Minor
),8) == false)
218 return _error
->Error(_("Corrupted archive"));
220 // Grab the filename and link target: use last long name if one was
221 // set, otherwise use the header value as-is, but remember that it may
222 // fill the entire 100-byte block and needs to be zero-terminated.
223 // See Debian Bug #689582.
224 if (LastLongName
.empty() == false)
225 Itm
.Name
= (char *)LastLongName
.c_str();
227 Itm
.Name
= (char *)ItemName
.assign(Tar
->Name
, sizeof(Tar
->Name
)).c_str();
228 if (Itm
.Name
[0] == '.' && Itm
.Name
[1] == '/' && Itm
.Name
[2] != 0)
231 if (LastLongLink
.empty() == false)
232 Itm
.LinkTarget
= (char *)LastLongLink
.c_str();
234 Itm
.LinkTarget
= (char *)ItemLink
.assign(Tar
->LinkName
, sizeof(Tar
->LinkName
)).c_str();
236 // Convert the type over
237 switch (Tar
->LinkFlag
)
241 Itm
.Type
= pkgDirStream::Item::File
;
245 Itm
.Type
= pkgDirStream::Item::HardLink
;
249 Itm
.Type
= pkgDirStream::Item::SymbolicLink
;
252 case CharacterDevice
:
253 Itm
.Type
= pkgDirStream::Item::CharDevice
;
257 Itm
.Type
= pkgDirStream::Item::BlockDevice
;
261 Itm
.Type
= pkgDirStream::Item::Directory
;
265 Itm
.Type
= pkgDirStream::Item::FIFO
;
270 unsigned long Length
= Itm
.Size
;
271 unsigned char Block
[512];
274 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
276 if (Length
<= sizeof(Block
))
278 LastLongLink
.append(Block
,Block
+sizeof(Block
));
281 LastLongLink
.append(Block
,Block
+sizeof(Block
));
282 Length
-= sizeof(Block
);
289 unsigned long Length
= Itm
.Size
;
290 unsigned char Block
[512];
293 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
295 if (Length
< sizeof(Block
))
297 LastLongName
.append(Block
,Block
+sizeof(Block
));
300 LastLongName
.append(Block
,Block
+sizeof(Block
));
301 Length
-= sizeof(Block
);
308 _error
->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar
->LinkFlag
,Tar
->Name
);
313 if (BadRecord
== false)
314 if (Stream
.DoItem(Itm
,Fd
) == false)
317 // Copy the file over the FD
318 unsigned long Size
= Itm
.Size
;
321 unsigned char Junk
[32*1024];
322 unsigned long Read
= min(Size
,(unsigned long)sizeof(Junk
));
323 if (InFd
.Read(Junk
,((Read
+511)/512)*512) == false)
326 if (BadRecord
== false)
330 if (write(Fd
,Junk
,Read
) != (signed)Read
)
331 return Stream
.Fail(Itm
,Fd
);
335 /* An Fd of -2 means to send to a special processing
338 if (Stream
.Process(Itm
,Junk
,Read
,Itm
.Size
- Size
) == false)
339 return Stream
.Fail(Itm
,Fd
);
347 if (BadRecord
== false)
348 if (Stream
.FinishedFile(Itm
,Fd
) == false)
351 LastLongName
.erase();
352 LastLongLink
.erase();