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 /*{{{*/
20 #pragma implementation "apt-pkg/extracttar.h"
22 #include <apt-pkg/extracttar.h>
24 #include <apt-pkg/error.h>
25 #include <apt-pkg/strutl.h>
26 #include <apt-pkg/configuration.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
;
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 StrToNum(Tar
->UserID
,Itm
.UID
,sizeof(Tar
->UserID
),8) == false ||
202 StrToNum(Tar
->GroupID
,Itm
.GID
,sizeof(Tar
->GroupID
),8) == false ||
203 StrToNum(Tar
->Size
,Itm
.Size
,sizeof(Tar
->Size
),8) == false ||
204 StrToNum(Tar
->MTime
,Itm
.MTime
,sizeof(Tar
->MTime
),8) == false ||
205 StrToNum(Tar
->Major
,Itm
.Major
,sizeof(Tar
->Major
),8) == false ||
206 StrToNum(Tar
->Minor
,Itm
.Minor
,sizeof(Tar
->Minor
),8) == false)
207 return _error
->Error(_("Corrupted archive"));
210 if (LastLongName
.empty() == false)
211 Itm
.Name
= (char *)LastLongName
.c_str();
214 Tar
->Name
[sizeof(Tar
->Name
)] = 0;
215 Itm
.Name
= Tar
->Name
;
217 if (Itm
.Name
[0] == '.' && Itm
.Name
[1] == '/' && Itm
.Name
[2] != 0)
220 // Grab the link target
221 Tar
->Name
[sizeof(Tar
->LinkName
)] = 0;
222 Itm
.LinkTarget
= Tar
->LinkName
;
224 if (LastLongLink
.empty() == false)
225 Itm
.LinkTarget
= (char *)LastLongLink
.c_str();
227 // Convert the type over
228 switch (Tar
->LinkFlag
)
232 Itm
.Type
= pkgDirStream::Item::File
;
236 Itm
.Type
= pkgDirStream::Item::HardLink
;
240 Itm
.Type
= pkgDirStream::Item::SymbolicLink
;
243 case CharacterDevice
:
244 Itm
.Type
= pkgDirStream::Item::CharDevice
;
248 Itm
.Type
= pkgDirStream::Item::BlockDevice
;
252 Itm
.Type
= pkgDirStream::Item::Directory
;
256 Itm
.Type
= pkgDirStream::Item::FIFO
;
261 unsigned long Length
= Itm
.Size
;
262 unsigned char Block
[512];
265 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
267 if (Length
<= sizeof(Block
))
269 LastLongLink
.append(Block
,Block
+sizeof(Block
));
272 LastLongLink
.append(Block
,Block
+sizeof(Block
));
273 Length
-= sizeof(Block
);
280 unsigned long Length
= Itm
.Size
;
281 unsigned char Block
[512];
284 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
286 if (Length
< sizeof(Block
))
288 LastLongName
.append(Block
,Block
+sizeof(Block
));
291 LastLongName
.append(Block
,Block
+sizeof(Block
));
292 Length
-= sizeof(Block
);
299 _error
->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar
->LinkFlag
,Tar
->Name
);
304 if (BadRecord
== false)
305 if (Stream
.DoItem(Itm
,Fd
) == false)
308 // Copy the file over the FD
309 unsigned long Size
= Itm
.Size
;
312 unsigned char Junk
[32*1024];
313 unsigned long Read
= min(Size
,(unsigned long)sizeof(Junk
));
314 if (InFd
.Read(Junk
,((Read
+511)/512)*512) == false)
317 if (BadRecord
== false)
321 if (write(Fd
,Junk
,Read
) != (signed)Read
)
322 return Stream
.Fail(Itm
,Fd
);
326 /* An Fd of -2 means to send to a special processing
329 if (Stream
.Process(Itm
,Junk
,Read
,Itm
.Size
- Size
) == false)
330 return Stream
.Fail(Itm
,Fd
);
338 if (Itm
.Size
!= 0 && BadRecord
== false)
339 if (Stream
.FinishedFile(Itm
,Fd
) == false)
342 LastLongName
.erase();
343 LastLongLink
.erase();