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 long Max
,string DecompressionProgram
)
64 : File(Fd
), 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()
115 if (DecompressProg
.empty())
117 InFd
.OpenDescriptor(File
.Fd(), FileFd::ReadOnly
, FileFd::None
, false);
122 if (pipe(Pipes
) != 0)
123 return _error
->Errno("pipe",_("Failed to create pipes"));
125 // Fork off the process
128 // Spawn the subprocess
132 dup2(Pipes
[1],STDOUT_FILENO
);
133 dup2(File
.Fd(),STDIN_FILENO
);
134 int Fd
= open("/dev/null",O_RDWR
);
137 dup2(Fd
,STDERR_FILENO
);
139 SetCloseExec(STDOUT_FILENO
,false);
140 SetCloseExec(STDIN_FILENO
,false);
141 SetCloseExec(STDERR_FILENO
,false);
144 string confvar
= string("dir::bin::") + DecompressProg
;
145 string argv0
= _config
->Find(confvar
.c_str(),DecompressProg
.c_str());
146 Args
[0] = argv0
.c_str();
149 execvp(Args
[0],(char **)Args
);
150 cerr
<< _("Failed to exec gzip ") << Args
[0] << endl
;
155 InFd
.OpenDescriptor(Pipes
[0], FileFd::ReadOnly
, FileFd::None
, true);
160 // ExtractTar::Go - Perform extraction /*{{{*/
161 // ---------------------------------------------------------------------
162 /* This reads each 512 byte block from the archive and extracts the header
163 information into the Item structure. Then it resolves the UID/GID and
164 invokes the correct processing function. */
165 bool ExtractTar::Go(pkgDirStream
&Stream
)
167 if (StartGzip() == false)
170 // Loop over all blocks
171 string LastLongLink
, ItemLink
;
172 string LastLongName
, ItemName
;
175 bool BadRecord
= false;
176 unsigned char Block
[512];
177 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
180 if (InFd
.Eof() == true)
184 TarHeader
*Tar
= (TarHeader
*)Block
;
185 unsigned long CheckSum
;
186 if (StrToNum(Tar
->Checksum
,CheckSum
,sizeof(Tar
->Checksum
),8) == false)
187 return _error
->Error(_("Corrupted archive"));
189 /* Compute the checksum field. The actual checksum is blanked out
190 with spaces so it is not included in the computation */
191 unsigned long NewSum
= 0;
192 memset(Tar
->Checksum
,' ',sizeof(Tar
->Checksum
));
193 for (int I
= 0; I
!= sizeof(Block
); I
++)
196 /* Check for a block of nulls - in this case we kill gzip, GNU tar
198 if (NewSum
== ' '*sizeof(Tar
->Checksum
))
201 if (NewSum
!= CheckSum
)
202 return _error
->Error(_("Tar checksum failed, archive corrupted"));
204 // Decode all of the fields
205 pkgDirStream::Item Itm
;
206 if (StrToNum(Tar
->Mode
,Itm
.Mode
,sizeof(Tar
->Mode
),8) == false ||
207 (Base256ToNum(Tar
->UserID
,Itm
.UID
,8) == false &&
208 StrToNum(Tar
->UserID
,Itm
.UID
,sizeof(Tar
->UserID
),8) == false) ||
209 (Base256ToNum(Tar
->GroupID
,Itm
.GID
,8) == false &&
210 StrToNum(Tar
->GroupID
,Itm
.GID
,sizeof(Tar
->GroupID
),8) == false) ||
211 (Base256ToNum(Tar
->Size
,Itm
.Size
,12) == false &&
212 StrToNum(Tar
->Size
,Itm
.Size
,sizeof(Tar
->Size
),8) == false) ||
213 (Base256ToNum(Tar
->MTime
,Itm
.MTime
,12) == false &&
214 StrToNum(Tar
->MTime
,Itm
.MTime
,sizeof(Tar
->MTime
),8) == false) ||
215 StrToNum(Tar
->Major
,Itm
.Major
,sizeof(Tar
->Major
),8) == false ||
216 StrToNum(Tar
->Minor
,Itm
.Minor
,sizeof(Tar
->Minor
),8) == false)
217 return _error
->Error(_("Corrupted archive"));
219 // Grab the filename and link target: use last long name if one was
220 // set, otherwise use the header value as-is, but remember that it may
221 // fill the entire 100-byte block and needs to be zero-terminated.
222 // See Debian Bug #689582.
223 if (LastLongName
.empty() == false)
224 Itm
.Name
= (char *)LastLongName
.c_str();
226 Itm
.Name
= (char *)ItemName
.assign(Tar
->Name
, sizeof(Tar
->Name
)).c_str();
227 if (Itm
.Name
[0] == '.' && Itm
.Name
[1] == '/' && Itm
.Name
[2] != 0)
230 if (LastLongLink
.empty() == false)
231 Itm
.LinkTarget
= (char *)LastLongLink
.c_str();
233 Itm
.LinkTarget
= (char *)ItemLink
.assign(Tar
->LinkName
, sizeof(Tar
->LinkName
)).c_str();
235 // Convert the type over
236 switch (Tar
->LinkFlag
)
240 Itm
.Type
= pkgDirStream::Item::File
;
244 Itm
.Type
= pkgDirStream::Item::HardLink
;
248 Itm
.Type
= pkgDirStream::Item::SymbolicLink
;
251 case CharacterDevice
:
252 Itm
.Type
= pkgDirStream::Item::CharDevice
;
256 Itm
.Type
= pkgDirStream::Item::BlockDevice
;
260 Itm
.Type
= pkgDirStream::Item::Directory
;
264 Itm
.Type
= pkgDirStream::Item::FIFO
;
269 unsigned long long Length
= Itm
.Size
;
270 unsigned char Block
[512];
273 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
275 if (Length
<= sizeof(Block
))
277 LastLongLink
.append(Block
,Block
+sizeof(Block
));
280 LastLongLink
.append(Block
,Block
+sizeof(Block
));
281 Length
-= sizeof(Block
);
288 unsigned long long Length
= Itm
.Size
;
289 unsigned char Block
[512];
292 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
294 if (Length
< sizeof(Block
))
296 LastLongName
.append(Block
,Block
+sizeof(Block
));
299 LastLongName
.append(Block
,Block
+sizeof(Block
));
300 Length
-= sizeof(Block
);
307 _error
->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar
->LinkFlag
,Tar
->Name
);
312 if (BadRecord
== false)
313 if (Stream
.DoItem(Itm
,Fd
) == false)
316 // Copy the file over the FD
317 unsigned long long Size
= Itm
.Size
;
320 unsigned char Junk
[32*1024];
321 unsigned long Read
= min(Size
, (unsigned long long)sizeof(Junk
));
322 if (InFd
.Read(Junk
,((Read
+511)/512)*512) == false)
325 if (BadRecord
== false)
329 if (write(Fd
,Junk
,Read
) != (signed)Read
)
330 return Stream
.Fail(Itm
,Fd
);
334 /* An Fd of -2 means to send to a special processing
337 if (Stream
.Process(Itm
,Junk
,Read
,Itm
.Size
- Size
) == false)
338 return Stream
.Fail(Itm
,Fd
);
346 if (BadRecord
== false)
347 if (Stream
.FinishedFile(Itm
,Fd
) == false)
350 LastLongName
.erase();
351 LastLongLink
.erase();