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 /*{{{*/
19 #include <apt-pkg/extracttar.h>
21 #include <apt-pkg/error.h>
22 #include <apt-pkg/strutl.h>
23 #include <apt-pkg/configuration.h>
36 // The on disk header for a tar file.
37 struct ExtractTar::TarHeader
55 // ExtractTar::ExtractTar - Constructor /*{{{*/
56 // ---------------------------------------------------------------------
58 ExtractTar::ExtractTar(FileFd
&Fd
,unsigned long Max
,string DecompressionProgram
) : File(Fd
),
59 MaxInSize(Max
), DecompressProg(DecompressionProgram
)
67 // ExtractTar::ExtractTar - Destructor /*{{{*/
68 // ---------------------------------------------------------------------
70 ExtractTar::~ExtractTar()
76 // ExtractTar::Done - Reap the gzip sub process /*{{{*/
77 // ---------------------------------------------------------------------
78 /* If the force flag is given then error messages are suppressed - this
79 means we hit the end of the tar file but there was still gzip data. */
80 bool ExtractTar::Done(bool Force
)
86 /* If there is a pending error then we are cleaning up gzip and are
87 not interested in it's failures */
88 if (_error
->PendingError() == true)
91 // Make sure we clean it up!
93 string confvar
= string("dir::bin::") + DecompressProg
;
94 if (ExecWait(GZPid
,_config
->Find(confvar
.c_str(),DecompressProg
.c_str()).c_str(),
105 // ExtractTar::StartGzip - Startup gzip /*{{{*/
106 // ---------------------------------------------------------------------
107 /* This creates a gzip sub process that has its input as the file itself.
108 If this tar file is embedded into something like an ar file then
109 gzip will efficiently ignore the extra bits. */
110 bool ExtractTar::StartGzip()
113 if (pipe(Pipes
) != 0)
114 return _error
->Errno("pipe",_("Failed to create pipes"));
116 // Fork off the process
119 // Spawn the subprocess
123 dup2(Pipes
[1],STDOUT_FILENO
);
124 dup2(File
.Fd(),STDIN_FILENO
);
125 int Fd
= open("/dev/null",O_RDWR
);
128 dup2(Fd
,STDERR_FILENO
);
130 SetCloseExec(STDOUT_FILENO
,false);
131 SetCloseExec(STDIN_FILENO
,false);
132 SetCloseExec(STDERR_FILENO
,false);
135 string confvar
= string("dir::bin::") + DecompressProg
;
136 string argv0
= _config
->Find(confvar
.c_str(),DecompressProg
.c_str());
137 Args
[0] = argv0
.c_str();
140 execvp(Args
[0],(char **)Args
);
141 cerr
<< _("Failed to exec gzip ") << Args
[0] << endl
;
151 // ExtractTar::Go - Perform extraction /*{{{*/
152 // ---------------------------------------------------------------------
153 /* This reads each 512 byte block from the archive and extracts the header
154 information into the Item structure. Then it resolves the UID/GID and
155 invokes the correct processing function. */
156 bool ExtractTar::Go(pkgDirStream
&Stream
)
158 if (StartGzip() == false)
161 // Loop over all blocks
166 bool BadRecord
= false;
167 unsigned char Block
[512];
168 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
171 if (InFd
.Eof() == true)
175 TarHeader
*Tar
= (TarHeader
*)Block
;
176 unsigned long CheckSum
;
177 if (StrToNum(Tar
->Checksum
,CheckSum
,sizeof(Tar
->Checksum
),8) == false)
178 return _error
->Error(_("Corrupted archive"));
180 /* Compute the checksum field. The actual checksum is blanked out
181 with spaces so it is not included in the computation */
182 unsigned long NewSum
= 0;
183 memset(Tar
->Checksum
,' ',sizeof(Tar
->Checksum
));
184 for (int I
= 0; I
!= sizeof(Block
); I
++)
187 /* Check for a block of nulls - in this case we kill gzip, GNU tar
189 if (NewSum
== ' '*sizeof(Tar
->Checksum
))
192 if (NewSum
!= CheckSum
)
193 return _error
->Error(_("Tar checksum failed, archive corrupted"));
195 // Decode all of the fields
196 pkgDirStream::Item Itm
;
197 if (StrToNum(Tar
->Mode
,Itm
.Mode
,sizeof(Tar
->Mode
),8) == false ||
198 StrToNum(Tar
->UserID
,Itm
.UID
,sizeof(Tar
->UserID
),8) == false ||
199 StrToNum(Tar
->GroupID
,Itm
.GID
,sizeof(Tar
->GroupID
),8) == false ||
200 StrToNum(Tar
->Size
,Itm
.Size
,sizeof(Tar
->Size
),8) == false ||
201 StrToNum(Tar
->MTime
,Itm
.MTime
,sizeof(Tar
->MTime
),8) == false ||
202 StrToNum(Tar
->Major
,Itm
.Major
,sizeof(Tar
->Major
),8) == false ||
203 StrToNum(Tar
->Minor
,Itm
.Minor
,sizeof(Tar
->Minor
),8) == false)
204 return _error
->Error(_("Corrupted archive"));
207 if (LastLongName
.empty() == false)
208 Itm
.Name
= (char *)LastLongName
.c_str();
211 Tar
->Name
[sizeof(Tar
->Name
)] = 0;
212 Itm
.Name
= Tar
->Name
;
214 if (Itm
.Name
[0] == '.' && Itm
.Name
[1] == '/' && Itm
.Name
[2] != 0)
217 // Grab the link target
218 Tar
->Name
[sizeof(Tar
->LinkName
)] = 0;
219 Itm
.LinkTarget
= Tar
->LinkName
;
221 if (LastLongLink
.empty() == false)
222 Itm
.LinkTarget
= (char *)LastLongLink
.c_str();
224 // Convert the type over
225 switch (Tar
->LinkFlag
)
229 Itm
.Type
= pkgDirStream::Item::File
;
233 Itm
.Type
= pkgDirStream::Item::HardLink
;
237 Itm
.Type
= pkgDirStream::Item::SymbolicLink
;
240 case CharacterDevice
:
241 Itm
.Type
= pkgDirStream::Item::CharDevice
;
245 Itm
.Type
= pkgDirStream::Item::BlockDevice
;
249 Itm
.Type
= pkgDirStream::Item::Directory
;
253 Itm
.Type
= pkgDirStream::Item::FIFO
;
258 unsigned long Length
= Itm
.Size
;
259 unsigned char Block
[512];
262 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
264 if (Length
<= sizeof(Block
))
266 LastLongLink
.append(Block
,Block
+sizeof(Block
));
269 LastLongLink
.append(Block
,Block
+sizeof(Block
));
270 Length
-= sizeof(Block
);
277 unsigned long Length
= Itm
.Size
;
278 unsigned char Block
[512];
281 if (InFd
.Read(Block
,sizeof(Block
),true) == false)
283 if (Length
< sizeof(Block
))
285 LastLongName
.append(Block
,Block
+sizeof(Block
));
288 LastLongName
.append(Block
,Block
+sizeof(Block
));
289 Length
-= sizeof(Block
);
296 _error
->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar
->LinkFlag
,Tar
->Name
);
301 if (BadRecord
== false)
302 if (Stream
.DoItem(Itm
,Fd
) == false)
305 // Copy the file over the FD
306 unsigned long Size
= Itm
.Size
;
309 unsigned char Junk
[32*1024];
310 unsigned long Read
= min(Size
,(unsigned long)sizeof(Junk
));
311 if (InFd
.Read(Junk
,((Read
+511)/512)*512) == false)
314 if (BadRecord
== false)
318 if (write(Fd
,Junk
,Read
) != (signed)Read
)
319 return Stream
.Fail(Itm
,Fd
);
323 /* An Fd of -2 means to send to a special processing
326 if (Stream
.Process(Itm
,Junk
,Read
,Itm
.Size
- Size
) == false)
327 return Stream
.Fail(Itm
,Fd
);
335 if (Itm
.Size
>= 0 && BadRecord
== false)
336 if (Stream
.FinishedFile(Itm
,Fd
) == false)
339 LastLongName
.erase();
340 LastLongLink
.erase();