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 #if APT_PKG_ABI >= 413 
  64 ExtractTar::ExtractTar(FileFd 
&Fd
,unsigned long long Max
,string DecompressionProgram
) 
  65         : File(Fd
), MaxInSize(Max
), DecompressProg(DecompressionProgram
) 
  67 ExtractTar::ExtractTar(FileFd 
&Fd
,unsigned long Max
,string DecompressionProgram
) 
  68         : File(Fd
), MaxInSize(Max
), DecompressProg(DecompressionProgram
) 
  75 // ExtractTar::ExtractTar - Destructor                                  /*{{{*/ 
  76 // --------------------------------------------------------------------- 
  78 ExtractTar::~ExtractTar() 
  84 // ExtractTar::Done - Reap the gzip sub process                         /*{{{*/ 
  85 // --------------------------------------------------------------------- 
  86 /* If the force flag is given then error messages are suppressed - this 
  87    means we hit the end of the tar file but there was still gzip data. */ 
  88 bool ExtractTar::Done(bool Force
) 
  94    /* If there is a pending error then we are cleaning up gzip and are 
  95       not interested in it's failures */ 
  96    if (_error
->PendingError() == true) 
  99    // Make sure we clean it up! 
 101    string confvar 
= string("dir::bin::") + DecompressProg
; 
 102    if (ExecWait(GZPid
,_config
->Find(confvar
.c_str(),DecompressProg
.c_str()).c_str(), 
 113 // ExtractTar::StartGzip - Startup gzip                                 /*{{{*/ 
 114 // --------------------------------------------------------------------- 
 115 /* This creates a gzip sub process that has its input as the file itself. 
 116    If this tar file is embedded into something like an ar file then  
 117    gzip will efficiently ignore the extra bits. */ 
 118 bool ExtractTar::StartGzip() 
 120    if (DecompressProg
.empty()) 
 122       InFd
.OpenDescriptor(File
.Fd(), FileFd::ReadOnly
, FileFd::None
, false); 
 127    if (pipe(Pipes
) != 0) 
 128       return _error
->Errno("pipe",_("Failed to create pipes")); 
 130    // Fork off the process 
 133    // Spawn the subprocess 
 137       dup2(Pipes
[1],STDOUT_FILENO
); 
 138       dup2(File
.Fd(),STDIN_FILENO
); 
 139       int Fd 
= open("/dev/null",O_RDWR
); 
 142       dup2(Fd
,STDERR_FILENO
); 
 144       SetCloseExec(STDOUT_FILENO
,false); 
 145       SetCloseExec(STDIN_FILENO
,false); 
 146       SetCloseExec(STDERR_FILENO
,false); 
 149       string confvar 
= string("dir::bin::") + DecompressProg
; 
 150       string argv0 
= _config
->Find(confvar
.c_str(),DecompressProg
.c_str()); 
 151       Args
[0] = argv0
.c_str(); 
 154       execvp(Args
[0],(char **)Args
); 
 155       cerr 
<< _("Failed to exec gzip ") << Args
[0] << endl
; 
 160    InFd
.OpenDescriptor(Pipes
[0], FileFd::ReadOnly
, FileFd::None
, true); 
 165 // ExtractTar::Go - Perform extraction                                  /*{{{*/ 
 166 // --------------------------------------------------------------------- 
 167 /* This reads each 512 byte block from the archive and extracts the header 
 168    information into the Item structure. Then it resolves the UID/GID and 
 169    invokes the correct processing function. */ 
 170 bool ExtractTar::Go(pkgDirStream 
&Stream
) 
 172    if (StartGzip() == false) 
 175    // Loop over all blocks 
 176    string LastLongLink
, ItemLink
; 
 177    string LastLongName
, ItemName
; 
 180       bool BadRecord 
= false;       
 181       unsigned char Block
[512];       
 182       if (InFd
.Read(Block
,sizeof(Block
),true) == false) 
 185       if (InFd
.Eof() == true) 
 189       TarHeader 
*Tar 
= (TarHeader 
*)Block
; 
 190       unsigned long CheckSum
; 
 191       if (StrToNum(Tar
->Checksum
,CheckSum
,sizeof(Tar
->Checksum
),8) == false) 
 192          return _error
->Error(_("Corrupted archive")); 
 194       /* Compute the checksum field. The actual checksum is blanked out 
 195          with spaces so it is not included in the computation */ 
 196       unsigned long NewSum 
= 0; 
 197       memset(Tar
->Checksum
,' ',sizeof(Tar
->Checksum
)); 
 198       for (int I 
= 0; I 
!= sizeof(Block
); I
++) 
 201       /* Check for a block of nulls - in this case we kill gzip, GNU tar 
 203       if (NewSum 
== ' '*sizeof(Tar
->Checksum
)) 
 206       if (NewSum 
!= CheckSum
) 
 207          return _error
->Error(_("Tar checksum failed, archive corrupted")); 
 209       // Decode all of the fields 
 210       pkgDirStream::Item Itm
; 
 211       if (StrToNum(Tar
->Mode
,Itm
.Mode
,sizeof(Tar
->Mode
),8) == false || 
 212           (Base256ToNum(Tar
->UserID
,Itm
.UID
,8) == false && 
 213              StrToNum(Tar
->UserID
,Itm
.UID
,sizeof(Tar
->UserID
),8) == false) || 
 214           (Base256ToNum(Tar
->GroupID
,Itm
.GID
,8) == false && 
 215              StrToNum(Tar
->GroupID
,Itm
.GID
,sizeof(Tar
->GroupID
),8) == false) || 
 216           (Base256ToNum(Tar
->Size
,Itm
.Size
,12) == false && 
 217              StrToNum(Tar
->Size
,Itm
.Size
,sizeof(Tar
->Size
),8) == false) || 
 218           (Base256ToNum(Tar
->MTime
,Itm
.MTime
,12) == false && 
 219              StrToNum(Tar
->MTime
,Itm
.MTime
,sizeof(Tar
->MTime
),8) == false) || 
 220           StrToNum(Tar
->Major
,Itm
.Major
,sizeof(Tar
->Major
),8) == false || 
 221           StrToNum(Tar
->Minor
,Itm
.Minor
,sizeof(Tar
->Minor
),8) == false) 
 222          return _error
->Error(_("Corrupted archive")); 
 224       // Grab the filename and link target: use last long name if one was 
 225       // set, otherwise use the header value as-is, but remember that it may 
 226       // fill the entire 100-byte block and needs to be zero-terminated. 
 227       // See Debian Bug #689582. 
 228       if (LastLongName
.empty() == false) 
 229          Itm
.Name 
= (char *)LastLongName
.c_str(); 
 231          Itm
.Name 
= (char *)ItemName
.assign(Tar
->Name
, sizeof(Tar
->Name
)).c_str(); 
 232       if (Itm
.Name
[0] == '.' && Itm
.Name
[1] == '/' && Itm
.Name
[2] != 0) 
 235       if (LastLongLink
.empty() == false) 
 236          Itm
.LinkTarget 
= (char *)LastLongLink
.c_str(); 
 238          Itm
.LinkTarget 
= (char *)ItemLink
.assign(Tar
->LinkName
, sizeof(Tar
->LinkName
)).c_str(); 
 240       // Convert the type over 
 241       switch (Tar
->LinkFlag
) 
 245          Itm
.Type 
= pkgDirStream::Item::File
; 
 249          Itm
.Type 
= pkgDirStream::Item::HardLink
; 
 253          Itm
.Type 
= pkgDirStream::Item::SymbolicLink
; 
 256          case CharacterDevice
: 
 257          Itm
.Type 
= pkgDirStream::Item::CharDevice
; 
 261          Itm
.Type 
= pkgDirStream::Item::BlockDevice
; 
 265          Itm
.Type 
= pkgDirStream::Item::Directory
; 
 269          Itm
.Type 
= pkgDirStream::Item::FIFO
; 
 274             unsigned long long Length 
= Itm
.Size
; 
 275             unsigned char Block
[512]; 
 278                if (InFd
.Read(Block
,sizeof(Block
),true) == false) 
 280                if (Length 
<= sizeof(Block
)) 
 282                   LastLongLink
.append(Block
,Block
+sizeof(Block
)); 
 285                LastLongLink
.append(Block
,Block
+sizeof(Block
)); 
 286                Length 
-= sizeof(Block
); 
 293             unsigned long long Length 
= Itm
.Size
; 
 294             unsigned char Block
[512]; 
 297                if (InFd
.Read(Block
,sizeof(Block
),true) == false) 
 299                if (Length 
< sizeof(Block
)) 
 301                   LastLongName
.append(Block
,Block
+sizeof(Block
)); 
 304                LastLongName
.append(Block
,Block
+sizeof(Block
)); 
 305                Length 
-= sizeof(Block
); 
 312          _error
->Warning(_("Unknown TAR header type %u, member %s"),(unsigned)Tar
->LinkFlag
,Tar
->Name
); 
 317       if (BadRecord 
== false) 
 318          if (Stream
.DoItem(Itm
,Fd
) == false) 
 321       // Copy the file over the FD 
 322       unsigned long long Size 
= Itm
.Size
; 
 325          unsigned char Junk
[32*1024]; 
 326          unsigned long Read 
= min(Size
, (unsigned long long)sizeof(Junk
)); 
 327          if (InFd
.Read(Junk
,((Read
+511)/512)*512) == false) 
 330          if (BadRecord 
== false) 
 334                if (write(Fd
,Junk
,Read
) != (signed)Read
) 
 335                   return Stream
.Fail(Itm
,Fd
); 
 339                /* An Fd of -2 means to send to a special processing 
 342                   if (Stream
.Process(Itm
,Junk
,Read
,Itm
.Size 
- Size
) == false) 
 343                      return Stream
.Fail(Itm
,Fd
); 
 351       if (BadRecord 
== false) 
 352          if (Stream
.FinishedFile(Itm
,Fd
) == false) 
 355       LastLongName
.erase(); 
 356       LastLongLink
.erase();