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/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
)
69 // ExtractTar::ExtractTar - Destructor /*{{{*/
70 // ---------------------------------------------------------------------
72 ExtractTar::~ExtractTar()
78 // ExtractTar::Done - Reap the gzip sub process /*{{{*/
79 // ---------------------------------------------------------------------
80 /* If the force flag is given then error messages are suppressed - this
81 means we hit the end of the tar file but there was still gzip data. */
82 bool ExtractTar::Done(bool Force
)
88 /* If there is a pending error then we are cleaning up gzip and are
89 not interested in it's failures */
90 if (_error
->PendingError() == true)
93 // Make sure we clean it up!
95 string confvar
= string("dir::bin::") + DecompressProg
;
96 if (ExecWait(GZPid
,_config
->Find(confvar
.c_str(),DecompressProg
.c_str()).c_str(),
107 // ExtractTar::StartGzip - Startup gzip /*{{{*/
108 // ---------------------------------------------------------------------
109 /* This creates a gzip sub process that has its input as the file itself.
110 If this tar file is embedded into something like an ar file then
111 gzip will efficiently ignore the extra bits. */
112 bool ExtractTar::StartGzip()
114 if (DecompressProg
.empty())
116 InFd
.OpenDescriptor(File
.Fd(), FileFd::ReadOnly
, FileFd::None
, false);
121 if (pipe(Pipes
) != 0)
122 return _error
->Errno("pipe",_("Failed to create pipes"));
124 // Fork off the process
127 // Spawn the subprocess
131 dup2(Pipes
[1],STDOUT_FILENO
);
132 dup2(File
.Fd(),STDIN_FILENO
);
133 int Fd
= open("/dev/null",O_RDWR
);
136 dup2(Fd
,STDERR_FILENO
);
138 SetCloseExec(STDOUT_FILENO
,false);
139 SetCloseExec(STDIN_FILENO
,false);
140 SetCloseExec(STDERR_FILENO
,false);
143 string confvar
= string("dir::bin::") + DecompressProg
;
144 string argv0
= _config
->Find(confvar
.c_str(),DecompressProg
.c_str());
145 Args
[0] = argv0
.c_str();
148 execvp(Args
[0],(char **)Args
);
149 cerr
<< _("Failed to exec gzip ") << Args
[0] << endl
;
154 InFd
.OpenDescriptor(Pipes
[0], FileFd::ReadOnly
, FileFd::None
, true);
159 // ExtractTar::Go - Perform extraction /*{{{*/
160 // ---------------------------------------------------------------------
161 /* This reads each 512 byte block from the archive and extracts the header
162 information into the Item structure. Then it resolves the UID/GID and
163 invokes the correct processing function. */
164 bool ExtractTar::Go(pkgDirStream
&Stream
)
166 if (StartGzip() == false)
169 // Loop over all blocks
170 string LastLongLink
, ItemLink
;
171 string LastLongName
, ItemName
;
174 bool BadRecord
= false;
175 unsigned char Block
[512];
176 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
179 if (InFd
.Eof() == true)
183 TarHeader
*Tar
= (TarHeader
*)Block
;
184 unsigned long CheckSum
;
185 if (StrToNum(Tar
->Checksum
,CheckSum
,sizeof(Tar
->Checksum
),8) == false)
186 return _error
->Error(_("Corrupted archive"));
188 /* Compute the checksum field. The actual checksum is blanked out
189 with spaces so it is not included in the computation */
190 unsigned long NewSum
= 0;
191 memset(Tar
->Checksum
,' ',sizeof(Tar
->Checksum
));
192 for (int I
= 0; I
!= sizeof(Block
); I
++)
195 /* Check for a block of nulls - in this case we kill gzip, GNU tar
197 if (NewSum
== ' '*sizeof(Tar
->Checksum
))
200 if (NewSum
!= CheckSum
)
201 return _error
->Error(_("Tar checksum failed, archive corrupted"));
203 // Decode all of the fields
204 pkgDirStream::Item Itm
;
205 if (StrToNum(Tar
->Mode
,Itm
.Mode
,sizeof(Tar
->Mode
),8) == false ||
206 (Base256ToNum(Tar
->UserID
,Itm
.UID
,8) == false &&
207 StrToNum(Tar
->UserID
,Itm
.UID
,sizeof(Tar
->UserID
),8) == false) ||
208 (Base256ToNum(Tar
->GroupID
,Itm
.GID
,8) == false &&
209 StrToNum(Tar
->GroupID
,Itm
.GID
,sizeof(Tar
->GroupID
),8) == false) ||
210 (Base256ToNum(Tar
->Size
,Itm
.Size
,12) == false &&
211 StrToNum(Tar
->Size
,Itm
.Size
,sizeof(Tar
->Size
),8) == false) ||
212 (Base256ToNum(Tar
->MTime
,Itm
.MTime
,12) == false &&
213 StrToNum(Tar
->MTime
,Itm
.MTime
,sizeof(Tar
->MTime
),8) == false) ||
214 StrToNum(Tar
->Major
,Itm
.Major
,sizeof(Tar
->Major
),8) == false ||
215 StrToNum(Tar
->Minor
,Itm
.Minor
,sizeof(Tar
->Minor
),8) == false)
216 return _error
->Error(_("Corrupted archive"));
218 // Grab the filename and link target: use last long name if one was
219 // set, otherwise use the header value as-is, but remember that it may
220 // fill the entire 100-byte block and needs to be zero-terminated.
221 // See Debian Bug #689582.
222 if (LastLongName
.empty() == false)
223 Itm
.Name
= (char *)LastLongName
.c_str();
225 Itm
.Name
= (char *)ItemName
.assign(Tar
->Name
, sizeof(Tar
->Name
)).c_str();
226 if (Itm
.Name
[0] == '.' && Itm
.Name
[1] == '/' && Itm
.Name
[2] != 0)
229 if (LastLongLink
.empty() == false)
230 Itm
.LinkTarget
= (char *)LastLongLink
.c_str();
232 Itm
.LinkTarget
= (char *)ItemLink
.assign(Tar
->LinkName
, sizeof(Tar
->LinkName
)).c_str();
234 // Convert the type over
235 switch (Tar
->LinkFlag
)
239 Itm
.Type
= pkgDirStream::Item::File
;
243 Itm
.Type
= pkgDirStream::Item::HardLink
;
247 Itm
.Type
= pkgDirStream::Item::SymbolicLink
;
250 case CharacterDevice
:
251 Itm
.Type
= pkgDirStream::Item::CharDevice
;
255 Itm
.Type
= pkgDirStream::Item::BlockDevice
;
259 Itm
.Type
= pkgDirStream::Item::Directory
;
263 Itm
.Type
= pkgDirStream::Item::FIFO
;
268 unsigned long Length
= Itm
.Size
;
269 unsigned char Block
[512];
272 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
274 if (Length
<= sizeof(Block
))
276 LastLongLink
.append(Block
,Block
+sizeof(Block
));
279 LastLongLink
.append(Block
,Block
+sizeof(Block
));
280 Length
-= sizeof(Block
);
287 unsigned long Length
= Itm
.Size
;
288 unsigned char Block
[512];
291 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
293 if (Length
< sizeof(Block
))
295 LastLongName
.append(Block
,Block
+sizeof(Block
));
298 LastLongName
.append(Block
,Block
+sizeof(Block
));
299 Length
-= sizeof(Block
);
306 _error
->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar
->LinkFlag
,Tar
->Name
);
311 if (BadRecord
== false)
312 if (Stream
.DoItem(Itm
,Fd
) == false)
315 // Copy the file over the FD
316 unsigned long Size
= Itm
.Size
;
319 unsigned char Junk
[32*1024];
320 unsigned long Read
= min(Size
,(unsigned long)sizeof(Junk
));
321 if (InFd
.Read(Junk
,((Read
+511)/512)*512) == false)
324 if (BadRecord
== false)
328 if (write(Fd
,Junk
,Read
) != (signed)Read
)
329 return Stream
.Fail(Itm
,Fd
);
333 /* An Fd of -2 means to send to a special processing
336 if (Stream
.Process(Itm
,Junk
,Read
,Itm
.Size
- Size
) == false)
337 return Stream
.Fail(Itm
,Fd
);
345 if (BadRecord
== false)
346 if (Stream
.FinishedFile(Itm
,Fd
) == false)
349 LastLongName
.erase();
350 LastLongLink
.erase();