summaryrefslogtreecommitdiffstats
path: root/apt-inst
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--apt-inst/CMakeLists.txt28
-rw-r--r--apt-inst/contrib/arfile.cc179
-rw-r--r--apt-inst/contrib/arfile.h69
-rw-r--r--apt-inst/contrib/extracttar.cc329
-rw-r--r--apt-inst/contrib/extracttar.h61
-rw-r--r--apt-inst/deb/debfile.cc265
-rw-r--r--apt-inst/deb/debfile.h95
-rw-r--r--apt-inst/dirstream.cc118
-rw-r--r--apt-inst/dirstream.h57
-rw-r--r--apt-inst/dpkg-diffs.txt5
-rw-r--r--apt-inst/extract.cc514
-rw-r--r--apt-inst/extract.h49
-rw-r--r--apt-inst/filelist.cc586
-rw-r--r--apt-inst/filelist.h312
14 files changed, 2667 insertions, 0 deletions
diff --git a/apt-inst/CMakeLists.txt b/apt-inst/CMakeLists.txt
new file mode 100644
index 0000000..31da115
--- /dev/null
+++ b/apt-inst/CMakeLists.txt
@@ -0,0 +1,28 @@
+# Include apt-pkg directly, as some files have #include <system.h>
+include_directories(${PROJECT_BINARY_DIR}/include/apt-pkg)
+
+# Set the version of the library
+set(MAJOR 2.0)
+set(MINOR 0)
+set(APT_INST_MAJOR ${MAJOR} PARENT_SCOPE)
+
+# Definition of the C++ files used to build the library - note that this
+# is expanded at CMake time, so you have to rerun cmake if you add or remove
+# a file (you can just run cmake . in the build directory)
+file(GLOB_RECURSE library "*.cc")
+file(GLOB_RECURSE headers "*.h")
+
+# Create a library using the C++ files
+add_library(apt-inst SHARED ${library})
+
+# Link the library and set the SONAME
+target_link_libraries(apt-inst PUBLIC apt-pkg ${CMAKE_THREAD_LIBS_INIT})
+target_link_libraries(apt-inst PRIVATE ${CMAKE_THREAD_LIBS_INIT})
+set_target_properties(apt-inst PROPERTIES VERSION ${MAJOR}.${MINOR})
+set_target_properties(apt-inst PROPERTIES SOVERSION ${MAJOR})
+add_version_script(apt-inst)
+
+# Install the library and the headers
+install(TARGETS apt-inst LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
+install(FILES ${headers} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/apt-pkg)
+flatify(${PROJECT_BINARY_DIR}/include/apt-pkg/ "${headers}")
diff --git a/apt-inst/contrib/arfile.cc b/apt-inst/contrib/arfile.cc
new file mode 100644
index 0000000..6d4a1f1
--- /dev/null
+++ b/apt-inst/contrib/arfile.cc
@@ -0,0 +1,179 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ AR File - Handle an 'AR' archive
+
+ AR Archives have plain text headers at the start of each file
+ section. The headers are aligned on a 2 byte boundary.
+
+ Information about the structure of AR files can be found in ar(5)
+ on a BSD system, or in the binutils source.
+
+ ##################################################################### */
+ /*}}}*/
+// Include Files /*{{{*/
+#include <config.h>
+
+#include <apt-pkg/arfile.h>
+#include <apt-pkg/error.h>
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/strutl.h>
+
+#include <string>
+#include <string.h>
+#include <sys/types.h>
+
+#include <apti18n.h>
+ /*}}}*/
+
+struct ARArchive::MemberHeader
+{
+ char Name[16];
+ char MTime[12];
+ char UID[6];
+ char GID[6];
+ char Mode[8];
+ char Size[10];
+ char Magic[2];
+};
+
+// ARArchive::ARArchive - Constructor /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+ARArchive::ARArchive(FileFd &File) : List(0), File(File)
+{
+ LoadHeaders();
+}
+ /*}}}*/
+// ARArchive::~ARArchive - Destructor /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+ARArchive::~ARArchive()
+{
+ while (List != 0)
+ {
+ Member *Tmp = List;
+ List = List->Next;
+ delete Tmp;
+ }
+}
+ /*}}}*/
+// ARArchive::LoadHeaders - Load the headers from each file /*{{{*/
+// ---------------------------------------------------------------------
+/* AR files are structured with a 8 byte magic string followed by a 60
+ byte plain text header then the file data, another header, data, etc */
+bool ARArchive::LoadHeaders()
+{
+ off_t Left = File.Size();
+
+ // Check the magic byte
+ char Magic[8];
+ if (File.Read(Magic,sizeof(Magic)) == false)
+ return false;
+ if (memcmp(Magic,"!<arch>\012",sizeof(Magic)) != 0)
+ return _error->Error(_("Invalid archive signature"));
+ Left -= sizeof(Magic);
+
+ // Read the member list
+ while (Left > 0)
+ {
+ MemberHeader Head;
+ if (File.Read(&Head,sizeof(Head)) == false)
+ return _error->Error(_("Error reading archive member header"));
+ Left -= sizeof(Head);
+
+ // Convert all of the integer members
+ Member *Memb = new Member();
+ if (StrToNum(Head.MTime,Memb->MTime,sizeof(Head.MTime)) == false ||
+ StrToNum(Head.UID,Memb->UID,sizeof(Head.UID)) == false ||
+ StrToNum(Head.GID,Memb->GID,sizeof(Head.GID)) == false ||
+ StrToNum(Head.Mode,Memb->Mode,sizeof(Head.Mode),8) == false ||
+ StrToNum(Head.Size,Memb->Size,sizeof(Head.Size)) == false)
+ {
+ delete Memb;
+ return _error->Error(_("Invalid archive member header"));
+ }
+
+ if (Left < 0 || Memb->Size > static_cast<unsigned long long>(Left))
+ {
+ delete Memb;
+ return _error->Error(_("Invalid archive member header"));
+ }
+ // Check for an extra long name string
+ if (memcmp(Head.Name,"#1/",3) == 0)
+ {
+ char S[300];
+ unsigned long Len;
+ if (StrToNum(Head.Name+3,Len,sizeof(Head.Size)-3) == false ||
+ Len >= sizeof(S))
+ {
+ delete Memb;
+ return _error->Error(_("Invalid archive member header"));
+ }
+
+ if (Len > Memb->Size)
+ {
+ delete Memb;
+ return _error->Error(_("Invalid archive member header"));
+ }
+
+ if (File.Read(S,Len) == false)
+ {
+ delete Memb;
+ return false;
+ }
+ S[Len] = 0;
+ Memb->Name = S;
+ Memb->Size -= Len;
+ Left -= Len;
+ }
+ else
+ {
+ unsigned int I = sizeof(Head.Name) - 1;
+ for (; Head.Name[I] == ' ' || Head.Name[I] == '/'; I--)
+ {
+ if (I == 0)
+ {
+ delete Memb;
+ return _error->Error(_("Invalid archive member header"));
+ }
+ }
+ Memb->Name = std::string(Head.Name,I+1);
+ }
+
+ // Account for the AR header alignment
+ off_t Skip = Memb->Size % 2;
+
+ // Add it to the list
+ Memb->Next = List;
+ List = Memb;
+ Memb->Start = File.Tell();
+ if (File.Skip(Memb->Size + Skip) == false)
+ return false;
+ if (Left < (off_t)(Memb->Size + Skip))
+ return _error->Error(_("Archive is too short"));
+ Left -= Memb->Size + Skip;
+ }
+ if (Left != 0)
+ return _error->Error(_("Failed to read the archive headers"));
+
+ return true;
+}
+ /*}}}*/
+// ARArchive::FindMember - Find a name in the member list /*{{{*/
+// ---------------------------------------------------------------------
+/* Find a member with the given name */
+const ARArchive::Member *ARArchive::FindMember(const char *Name) const
+{
+ const Member *Res = List;
+ while (Res != 0)
+ {
+ if (Res->Name == Name)
+ return Res;
+ Res = Res->Next;
+ }
+
+ return 0;
+}
+ /*}}}*/
diff --git a/apt-inst/contrib/arfile.h b/apt-inst/contrib/arfile.h
new file mode 100644
index 0000000..8124208
--- /dev/null
+++ b/apt-inst/contrib/arfile.h
@@ -0,0 +1,69 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ AR File - Handle an 'AR' archive
+
+ This is a reader for the usual 4.4 BSD AR format. It allows raw
+ stream access to a single member at a time. Basically all this class
+ provides is header parsing and verification. It is up to the client
+ to correctly make use of the stream start/stop points.
+
+ ##################################################################### */
+ /*}}}*/
+#ifndef PKGLIB_ARFILE_H
+#define PKGLIB_ARFILE_H
+
+#include <apt-pkg/macros.h>
+#include <string>
+#ifndef APT_8_CLEANER_HEADERS
+#include <apt-pkg/fileutl.h>
+#endif
+
+class FileFd;
+
+class ARArchive
+{
+ struct MemberHeader;
+ public:
+ struct Member;
+
+ protected:
+
+ // Linked list of members
+ Member *List;
+
+ bool LoadHeaders();
+
+ public:
+
+ // The stream file
+ FileFd &File;
+
+ // Locate a member by name
+ const Member *FindMember(const char *Name) const;
+ inline Member *Members() { return List; }
+
+ ARArchive(FileFd &File);
+ ~ARArchive();
+};
+
+// A member of the archive
+struct ARArchive::Member
+{
+ // Fields from the header
+ std::string Name;
+ unsigned long MTime;
+ unsigned long UID;
+ unsigned long GID;
+ unsigned long Mode;
+ unsigned long long Size;
+
+ // Location of the data.
+ unsigned long long Start;
+ Member *Next;
+
+ Member() : Start(0), Next(0) {};
+};
+
+#endif
diff --git a/apt-inst/contrib/extracttar.cc b/apt-inst/contrib/extracttar.cc
new file mode 100644
index 0000000..cbee4d1
--- /dev/null
+++ b/apt-inst/contrib/extracttar.cc
@@ -0,0 +1,329 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ Extract a Tar - Tar Extractor
+
+ Some performance measurements showed that zlib performed quite poorly
+ in comparison to a forked gzip process. This tar extractor makes use
+ of the fact that dup'd file descriptors have the same seek pointer
+ and that gzip will not read past the end of a compressed stream,
+ even if there is more data. We use the dup property to track extraction
+ progress and the gzip feature to just feed gzip a fd in the middle
+ of an AR file.
+
+ ##################################################################### */
+ /*}}}*/
+// Include Files /*{{{*/
+#include <config.h>
+
+#include <apt-pkg/configuration.h>
+#include <apt-pkg/dirstream.h>
+#include <apt-pkg/error.h>
+#include <apt-pkg/extracttar.h>
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/strutl.h>
+
+#include <algorithm>
+#include <iostream>
+#include <string>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <apti18n.h>
+ /*}}}*/
+
+using namespace std;
+
+// The on disk header for a tar file.
+struct ExtractTar::TarHeader
+{
+ char Name[100];
+ char Mode[8];
+ char UserID[8];
+ char GroupID[8];
+ char Size[12];
+ char MTime[12];
+ char Checksum[8];
+ char LinkFlag;
+ char LinkName[100];
+ char MagicNumber[8];
+ char UserName[32];
+ char GroupName[32];
+ char Major[8];
+ char Minor[8];
+};
+
+// We need to read long names (names and link targets) into memory, so let's
+// have a limit (shamelessly stolen from libarchive) to avoid people OOMing
+// us with large streams.
+static const unsigned long long APT_LONGNAME_LIMIT = 1048576llu;
+
+// A file size limit that we allow extracting. Currently, that's 128 GB.
+// We also should leave some wiggle room for code adding files to it, and
+// possibly conversion for signed, so this should not be larger than like 2**62.
+static const unsigned long long APT_FILESIZE_LIMIT = 1llu << 37;
+
+// ExtractTar::ExtractTar - Constructor /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+ExtractTar::ExtractTar(FileFd &Fd,unsigned long long Max,string DecompressionProgram)
+ : File(Fd), MaxInSize(Max), DecompressProg(DecompressionProgram)
+{
+ GZPid = -1;
+ Eof = false;
+}
+ /*}}}*/
+// ExtractTar::ExtractTar - Destructor /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+ExtractTar::~ExtractTar()
+{
+ // Error close
+ Done();
+}
+ /*}}}*/
+// ExtractTar::Done - Reap the gzip sub process /*{{{*/
+bool ExtractTar::Done(bool)
+{
+ return Done();
+}
+bool ExtractTar::Done()
+{
+ return InFd.Close();
+}
+ /*}}}*/
+// ExtractTar::StartGzip - Startup gzip /*{{{*/
+// ---------------------------------------------------------------------
+/* This creates a gzip sub process that has its input as the file itself.
+ If this tar file is embedded into something like an ar file then
+ gzip will efficiently ignore the extra bits. */
+bool ExtractTar::StartGzip()
+{
+ if (DecompressProg.empty())
+ {
+ InFd.OpenDescriptor(File.Fd(), FileFd::ReadOnly, FileFd::None, false);
+ return true;
+ }
+
+ std::vector<APT::Configuration::Compressor> const compressors = APT::Configuration::getCompressors();
+ std::vector<APT::Configuration::Compressor>::const_iterator compressor = compressors.begin();
+ for (; compressor != compressors.end(); ++compressor) {
+ if (compressor->Name == DecompressProg) {
+ return InFd.OpenDescriptor(File.Fd(), FileFd::ReadOnly, *compressor, false);
+ }
+ }
+
+ return _error->Error(_("Cannot find a configured compressor for '%s'"),
+ DecompressProg.c_str());
+
+}
+ /*}}}*/
+// ExtractTar::Go - Perform extraction /*{{{*/
+// ---------------------------------------------------------------------
+/* This reads each 512 byte block from the archive and extracts the header
+ information into the Item structure. Then it resolves the UID/GID and
+ invokes the correct processing function. */
+bool ExtractTar::Go(pkgDirStream &Stream)
+{
+ if (StartGzip() == false)
+ return false;
+
+ // Loop over all blocks
+ string LastLongLink, ItemLink;
+ string LastLongName, ItemName;
+ while (1)
+ {
+ bool BadRecord = false;
+ unsigned char Block[512];
+ if (InFd.Read(Block,sizeof(Block),true) == false)
+ return false;
+
+ if (InFd.Eof() == true)
+ break;
+
+ // Get the checksum
+ TarHeader *Tar = (TarHeader *)Block;
+ unsigned long CheckSum;
+ if (StrToNum(Tar->Checksum,CheckSum,sizeof(Tar->Checksum),8) == false)
+ return _error->Error(_("Corrupted archive"));
+
+ /* Compute the checksum field. The actual checksum is blanked out
+ with spaces so it is not included in the computation */
+ unsigned long NewSum = 0;
+ memset(Tar->Checksum,' ',sizeof(Tar->Checksum));
+ for (int I = 0; I != sizeof(Block); I++)
+ NewSum += Block[I];
+
+ /* Check for a block of nulls - in this case we kill gzip, GNU tar
+ does this.. */
+ if (NewSum == ' '*sizeof(Tar->Checksum))
+ return Done();
+
+ if (NewSum != CheckSum)
+ return _error->Error(_("Tar checksum failed, archive corrupted"));
+
+ // Decode all of the fields
+ pkgDirStream::Item Itm;
+ if (StrToNum(Tar->Mode,Itm.Mode,sizeof(Tar->Mode),8) == false ||
+ (Base256ToNum(Tar->UserID,Itm.UID,8) == false &&
+ StrToNum(Tar->UserID,Itm.UID,sizeof(Tar->UserID),8) == false) ||
+ (Base256ToNum(Tar->GroupID,Itm.GID,8) == false &&
+ StrToNum(Tar->GroupID,Itm.GID,sizeof(Tar->GroupID),8) == false) ||
+ (Base256ToNum(Tar->Size,Itm.Size,12) == false &&
+ StrToNum(Tar->Size,Itm.Size,sizeof(Tar->Size),8) == false) ||
+ (Base256ToNum(Tar->MTime,Itm.MTime,12) == false &&
+ StrToNum(Tar->MTime,Itm.MTime,sizeof(Tar->MTime),8) == false) ||
+ StrToNum(Tar->Major,Itm.Major,sizeof(Tar->Major),8) == false ||
+ StrToNum(Tar->Minor,Itm.Minor,sizeof(Tar->Minor),8) == false)
+ return _error->Error(_("Corrupted archive"));
+
+ // Security check. Prevents overflows below the code when rounding up in skip/copy code,
+ // and provides modest protection against decompression bombs.
+ if (Itm.Size > APT_FILESIZE_LIMIT)
+ return _error->Error("Tar member too large: %llu > %llu bytes", Itm.Size, APT_FILESIZE_LIMIT);
+
+ // Grab the filename and link target: use last long name if one was
+ // set, otherwise use the header value as-is, but remember that it may
+ // fill the entire 100-byte block and needs to be zero-terminated.
+ // See Debian Bug #689582.
+ if (LastLongName.empty() == false)
+ Itm.Name = (char *)LastLongName.c_str();
+ else
+ Itm.Name = (char *)ItemName.assign(Tar->Name, sizeof(Tar->Name)).c_str();
+ if (Itm.Name[0] == '.' && Itm.Name[1] == '/' && Itm.Name[2] != 0)
+ Itm.Name += 2;
+
+ if (LastLongLink.empty() == false)
+ Itm.LinkTarget = (char *)LastLongLink.c_str();
+ else
+ Itm.LinkTarget = (char *)ItemLink.assign(Tar->LinkName, sizeof(Tar->LinkName)).c_str();
+
+ // Convert the type over
+ switch (Tar->LinkFlag)
+ {
+ case NormalFile0:
+ case NormalFile:
+ Itm.Type = pkgDirStream::Item::File;
+ break;
+
+ case HardLink:
+ Itm.Type = pkgDirStream::Item::HardLink;
+ break;
+
+ case SymbolicLink:
+ Itm.Type = pkgDirStream::Item::SymbolicLink;
+ break;
+
+ case CharacterDevice:
+ Itm.Type = pkgDirStream::Item::CharDevice;
+ break;
+
+ case BlockDevice:
+ Itm.Type = pkgDirStream::Item::BlockDevice;
+ break;
+
+ case Directory:
+ Itm.Type = pkgDirStream::Item::Directory;
+ break;
+
+ case FIFO:
+ Itm.Type = pkgDirStream::Item::FIFO;
+ break;
+
+ case GNU_LongLink:
+ {
+ unsigned long long Length = Itm.Size;
+ unsigned char Block[512];
+ if (Length > APT_LONGNAME_LIMIT)
+ return _error->Error("Long name to large: %llu bytes > %llu bytes", Length, APT_LONGNAME_LIMIT);
+ while (Length > 0)
+ {
+ if (InFd.Read(Block,sizeof(Block),true) == false)
+ return false;
+ if (Length <= sizeof(Block))
+ {
+ LastLongLink.append(Block,Block+sizeof(Block));
+ break;
+ }
+ LastLongLink.append(Block,Block+sizeof(Block));
+ Length -= sizeof(Block);
+ }
+ continue;
+ }
+
+ case GNU_LongName:
+ {
+ unsigned long long Length = Itm.Size;
+ unsigned char Block[512];
+ if (Length > APT_LONGNAME_LIMIT)
+ return _error->Error("Long name to large: %llu bytes > %llu bytes", Length, APT_LONGNAME_LIMIT);
+ while (Length > 0)
+ {
+ if (InFd.Read(Block,sizeof(Block),true) == false)
+ return false;
+ if (Length < sizeof(Block))
+ {
+ LastLongName.append(Block,Block+sizeof(Block));
+ break;
+ }
+ LastLongName.append(Block,Block+sizeof(Block));
+ Length -= sizeof(Block);
+ }
+ continue;
+ }
+
+ default:
+ BadRecord = true;
+ _error->Warning(_("Unknown TAR header type %u"), (unsigned)Tar->LinkFlag);
+ break;
+ }
+
+ int Fd = -1;
+ if (BadRecord == false)
+ if (Stream.DoItem(Itm,Fd) == false)
+ return false;
+
+ // Copy the file over the FD
+ unsigned long long Size = Itm.Size;
+ while (Size != 0)
+ {
+ unsigned char Junk[32*1024];
+ unsigned long Read = min(Size, (unsigned long long)sizeof(Junk));
+ if (InFd.Read(Junk,((Read+511)/512)*512) == false)
+ return false;
+
+ if (BadRecord == false)
+ {
+ if (Fd > 0)
+ {
+ if (write(Fd,Junk,Read) != (signed)Read)
+ return Stream.Fail(Itm,Fd);
+ }
+ else
+ {
+ /* An Fd of -2 means to send to a special processing
+ function */
+ if (Fd == -2)
+ if (Stream.Process(Itm,Junk,Read,Itm.Size - Size) == false)
+ return Stream.Fail(Itm,Fd);
+ }
+ }
+
+ Size -= Read;
+ }
+
+ // And finish up
+ if (BadRecord == false)
+ if (Stream.FinishedFile(Itm,Fd) == false)
+ return false;
+
+ LastLongName.erase();
+ LastLongLink.erase();
+ }
+
+ return Done();
+}
+ /*}}}*/
diff --git a/apt-inst/contrib/extracttar.h b/apt-inst/contrib/extracttar.h
new file mode 100644
index 0000000..c0b340e
--- /dev/null
+++ b/apt-inst/contrib/extracttar.h
@@ -0,0 +1,61 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ Extract a Tar - Tar Extractor
+
+ The tar extractor takes an ordinary gzip compressed tar stream from
+ the given file and explodes it, passing the individual items to the
+ given Directory Stream for processing.
+
+ ##################################################################### */
+ /*}}}*/
+#ifndef PKGLIB_EXTRACTTAR_H
+#define PKGLIB_EXTRACTTAR_H
+
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/macros.h>
+
+#include <string>
+
+#ifndef APT_8_CLEANER_HEADERS
+#include <apt-pkg/dirstream.h>
+#include <algorithm>
+using std::min;
+#endif
+
+class pkgDirStream;
+
+class ExtractTar
+{
+ protected:
+
+ struct TarHeader;
+
+ // The varios types items can be
+ enum ItemType {NormalFile0 = '\0',NormalFile = '0',HardLink = '1',
+ SymbolicLink = '2',CharacterDevice = '3',
+ BlockDevice = '4',Directory = '5',FIFO = '6',
+ GNU_LongLink = 'K',GNU_LongName = 'L'};
+
+ FileFd &File;
+ unsigned long long MaxInSize;
+ int GZPid;
+ FileFd InFd;
+ bool Eof;
+ std::string DecompressProg;
+
+ // Fork and reap gzip
+ bool StartGzip();
+ bool Done();
+ APT_DEPRECATED_MSG("Parameter Force is ignored, use Done() instead.") bool Done(bool Force);
+
+ public:
+
+ bool Go(pkgDirStream &Stream);
+
+ ExtractTar(FileFd &Fd,unsigned long long Max,std::string DecompressionProgram);
+ virtual ~ExtractTar();
+};
+
+#endif
diff --git a/apt-inst/deb/debfile.cc b/apt-inst/deb/debfile.cc
new file mode 100644
index 0000000..bef0cd0
--- /dev/null
+++ b/apt-inst/deb/debfile.cc
@@ -0,0 +1,265 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ Debian Archive File (.deb)
+
+ .DEB archives are AR files containing two tars and an empty marker
+ member called 'debian-binary'. The two tars contain the meta data and
+ the actual archive contents. Thus this class is a very simple wrapper
+ around ar/tar to simply extract the right tar files.
+
+ It also uses the deb package list parser to parse the control file
+ into the cache.
+
+ ##################################################################### */
+ /*}}}*/
+// Include Files /*{{{*/
+#include <config.h>
+
+#include <apt-pkg/aptconfiguration.h>
+#include <apt-pkg/arfile.h>
+#include <apt-pkg/debfile.h>
+#include <apt-pkg/dirstream.h>
+#include <apt-pkg/error.h>
+#include <apt-pkg/extracttar.h>
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/tagfile.h>
+
+#include <string>
+#include <vector>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <apti18n.h>
+ /*}}}*/
+
+// DebFile::debDebFile - Constructor /*{{{*/
+// ---------------------------------------------------------------------
+/* Open the AR file and check for consistency */
+debDebFile::debDebFile(FileFd &File) : File(File), AR(File)
+{
+ if (_error->PendingError() == true)
+ return;
+
+ if (!CheckMember("debian-binary")) {
+ _error->Error(_("This is not a valid DEB archive, missing '%s' member"), "debian-binary");
+ return;
+ }
+
+ if (!CheckMember("control.tar") &&
+ !CheckMember("control.tar.gz") &&
+ !CheckMember("control.tar.xz") &&
+ !CheckMember("control.tar.zst"))
+ {
+ _error->Error(_("This is not a valid DEB archive, missing '%s' member"), "control.tar");
+ return;
+ }
+
+ if (!CheckMember("data.tar") &&
+ !CheckMember("data.tar.gz") &&
+ !CheckMember("data.tar.bz2") &&
+ !CheckMember("data.tar.lzma") &&
+ !CheckMember("data.tar.xz") &&
+ !CheckMember("data.tar.zst"))
+ {
+ _error->Error(_("This is not a valid DEB archive, missing '%s' member"), "data.tar");
+ return;
+ }
+}
+ /*}}}*/
+// DebFile::CheckMember - Check if a named member is in the archive /*{{{*/
+// ---------------------------------------------------------------------
+/* This is used to check for a correct deb and to give nicer error messages
+ for people playing around. */
+bool debDebFile::CheckMember(const char *Name)
+{
+ if (AR.FindMember(Name) == 0)
+ return false;
+ return true;
+}
+ /*}}}*/
+// DebFile::GotoMember - Jump to a Member /*{{{*/
+// ---------------------------------------------------------------------
+/* Jump in the file to the start of a named member and return the information
+ about that member. The caller can then read from the file up to the
+ returned size. Note, since this relies on the file position this is
+ a destructive operation, it also changes the last returned Member
+ structure - so don't nest them! */
+const ARArchive::Member *debDebFile::GotoMember(const char *Name)
+{
+ // Get the archive member and positition the file
+ const ARArchive::Member *Member = AR.FindMember(Name);
+ if (Member == 0)
+ {
+ return 0;
+ }
+ if (File.Seek(Member->Start) == false)
+ return 0;
+
+ return Member;
+}
+ /*}}}*/
+// DebFile::ExtractTarMember - Extract the contents of a tar member /*{{{*/
+// ---------------------------------------------------------------------
+/* Simple wrapper around tar.. */
+bool debDebFile::ExtractTarMember(pkgDirStream &Stream,const char *Name)
+{
+ // Get the archive member
+ const ARArchive::Member *Member = NULL;
+ std::string Compressor;
+
+ std::vector<APT::Configuration::Compressor> compressor = APT::Configuration::getCompressors();
+ for (std::vector<APT::Configuration::Compressor>::const_iterator c = compressor.begin();
+ c != compressor.end(); ++c)
+ {
+ Member = AR.FindMember(std::string(Name).append(c->Extension).c_str());
+ if (Member == NULL)
+ continue;
+ Compressor = c->Name;
+ break;
+ }
+
+ if (Member == NULL)
+ Member = AR.FindMember(std::string(Name).c_str());
+
+ if (Member == NULL)
+ {
+ std::string ext = std::string(Name) + ".{";
+ for (std::vector<APT::Configuration::Compressor>::const_iterator c = compressor.begin();
+ c != compressor.end(); ++c) {
+ if (!c->Extension.empty())
+ ext.append(c->Extension.substr(1));
+ }
+ ext.append("}");
+ return _error->Error(_("Internal error, could not locate member %s"), ext.c_str());
+ }
+
+ if (File.Seek(Member->Start) == false)
+ return false;
+
+ // Prepare Tar
+ ExtractTar Tar(File,Member->Size,Compressor);
+ if (_error->PendingError() == true)
+ return false;
+ return Tar.Go(Stream);
+}
+ /*}}}*/
+// DebFile::ExtractArchive - Extract the archive data itself /*{{{*/
+// ---------------------------------------------------------------------
+/* Simple wrapper around DebFile::ExtractTarMember. */
+bool debDebFile::ExtractArchive(pkgDirStream &Stream)
+{
+ return ExtractTarMember(Stream, "data.tar");
+}
+ /*}}}*/
+
+// DebFile::ControlExtract::DoItem - Control Tar Extraction /*{{{*/
+// ---------------------------------------------------------------------
+/* This directory stream handler for the control tar handles extracting
+ it into the temporary meta directory. It only extracts files, it does
+ not create directories, links or anything else. */
+bool debDebFile::ControlExtract::DoItem(Item &Itm,int &Fd)
+{
+ if (Itm.Type != Item::File)
+ return true;
+
+ /* Cleanse the file name, prevent people from trying to unpack into
+ absolute paths, .., etc */
+ for (char *I = Itm.Name; *I != 0; I++)
+ if (*I == '/')
+ *I = '_';
+
+ /* Force the ownership to be root and ensure correct permissions,
+ go-w, the rest are left untouched */
+ Itm.UID = 0;
+ Itm.GID = 0;
+ Itm.Mode &= ~(S_IWGRP | S_IWOTH);
+
+ return pkgDirStream::DoItem(Itm,Fd);
+}
+ /*}}}*/
+
+// MemControlExtract::DoItem - Check if it is the control file /*{{{*/
+// ---------------------------------------------------------------------
+/* This sets up to extract the control block member file into a memory
+ block of just the right size. All other files go into the bit bucket. */
+
+// Upper size limit for control files. Two reasons for having a limit here:
+//
+// 1. We read those files into memory and want to avoid being killed by OOM
+//
+// 2. We allocate (Itm.Size+2)-large arrays, so this can overflow if Itm.Size
+// becomes 2**64-2 or larger. This is obviously
+//
+// 64 MiB seems like a terribly large size that everyone should be happy with.
+static const unsigned long long DEB_CONTROL_SIZE_LIMIT = 64 * 1024 * 1024;
+bool debDebFile::MemControlExtract::DoItem(Item &Itm,int &Fd)
+{
+ // At the control file, allocate buffer memory.
+ if (Member == Itm.Name)
+ {
+ if (Itm.Size > DEB_CONTROL_SIZE_LIMIT)
+ return _error->Error("Control file too large: %llu > %llu bytes", Itm.Size, DEB_CONTROL_SIZE_LIMIT);
+ delete [] Control;
+ Control = new char[Itm.Size+2];
+ IsControl = true;
+ Fd = -2; // Signal to pass to Process
+ Length = Itm.Size;
+ }
+ else
+ IsControl = false;
+
+ return true;
+}
+ /*}}}*/
+// MemControlExtract::Process - Process extracting the control file /*{{{*/
+// ---------------------------------------------------------------------
+/* Just memcopy the block from the tar extractor and put it in the right
+ place in the pre-allocated memory block. */
+bool debDebFile::MemControlExtract::Process(Item &/*Itm*/,const unsigned char *Data,
+ unsigned long long Size,unsigned long long Pos)
+{
+ memcpy(Control + Pos, Data,Size);
+ return true;
+}
+ /*}}}*/
+// MemControlExtract::Read - Read the control information from the deb /*{{{*/
+// ---------------------------------------------------------------------
+/* This uses the internal tar extractor to fetch the control file, and then
+ it parses it into a tag section parser. */
+bool debDebFile::MemControlExtract::Read(debDebFile &Deb)
+{
+ if (Deb.ExtractTarMember(*this, "control.tar") == false)
+ return false;
+
+ if (Control == 0)
+ return true;
+
+ Control[Length] = '\n';
+ Control[Length+1] = '\n';
+ if (Section.Scan(Control,Length+2) == false)
+ return _error->Error(_("Unparsable control file"));
+ return true;
+}
+ /*}}}*/
+// MemControlExtract::TakeControl - Parse a memory block /*{{{*/
+// ---------------------------------------------------------------------
+/* The given memory block is loaded into the parser and parsed as a control
+ record. */
+bool debDebFile::MemControlExtract::TakeControl(const void *Data,unsigned long long Size)
+{
+ if (Size > DEB_CONTROL_SIZE_LIMIT)
+ return _error->Error("Control file too large: %llu > %llu bytes", Size, DEB_CONTROL_SIZE_LIMIT);
+
+ delete [] Control;
+ Control = new char[Size+2];
+ Length = Size;
+ memcpy(Control,Data,Size);
+
+ Control[Length] = '\n';
+ Control[Length+1] = '\n';
+ return Section.Scan(Control,Length+2);
+}
+ /*}}}*/
+
diff --git a/apt-inst/deb/debfile.h b/apt-inst/deb/debfile.h
new file mode 100644
index 0000000..23a76bf
--- /dev/null
+++ b/apt-inst/deb/debfile.h
@@ -0,0 +1,95 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ Debian Archive File (.deb)
+
+ This Class handles all the operations performed directly on .deb
+ files. It makes use of the AR and TAR classes to give the necessary
+ external interface.
+
+ There are only two things that can be done with a raw package,
+ extract it's control information and extract the contents itself.
+
+ This should probably subclass an as-yet unwritten super class to
+ produce a generic archive mechanism.
+
+ The memory control file extractor is useful to extract a single file
+ into memory from the control.tar.gz
+
+ ##################################################################### */
+ /*}}}*/
+#ifndef PKGLIB_DEBFILE_H
+#define PKGLIB_DEBFILE_H
+
+#include <apt-pkg/arfile.h>
+#include <apt-pkg/dirstream.h>
+#include <apt-pkg/macros.h>
+#include <apt-pkg/tagfile.h>
+
+#include <string>
+
+#ifndef APT_8_CLEANER_HEADERS
+#include <apt-pkg/md5.h>
+#endif
+#ifndef APT_10_CLEANER_HEADERS
+#include <apt-pkg/pkgcache.h>
+#endif
+
+class FileFd;
+
+class debDebFile
+{
+ protected:
+
+ FileFd &File;
+ ARArchive AR;
+
+ bool CheckMember(const char *Name);
+
+ public:
+ class ControlExtract;
+ class MemControlExtract;
+
+ bool ExtractTarMember(pkgDirStream &Stream, const char *Name);
+ bool ExtractArchive(pkgDirStream &Stream);
+ const ARArchive::Member *GotoMember(const char *Name);
+ inline FileFd &GetFile() {return File;};
+
+ debDebFile(FileFd &File);
+};
+
+class debDebFile::ControlExtract : public pkgDirStream
+{
+ public:
+
+ virtual bool DoItem(Item &Itm,int &Fd) APT_OVERRIDE;
+};
+
+class debDebFile::MemControlExtract : public pkgDirStream
+{
+ bool IsControl;
+
+ public:
+
+ char *Control;
+ pkgTagSection Section;
+ unsigned long Length;
+ std::string Member;
+
+ // Members from DirStream
+ virtual bool DoItem(Item &Itm,int &Fd) APT_OVERRIDE;
+ virtual bool Process(Item &Itm,const unsigned char *Data,
+ unsigned long long Size,unsigned long long Pos) APT_OVERRIDE;
+
+ // Helpers
+ bool Read(debDebFile &Deb);
+ bool TakeControl(const void *Data,unsigned long long Size);
+
+ MemControlExtract() : IsControl(false), Control(0), Length(0), Member("control") {};
+ MemControlExtract(std::string Member) : IsControl(false), Control(0), Length(0), Member(Member) {};
+ ~MemControlExtract() {delete [] Control;};
+};
+ /*}}}*/
+
+#endif
diff --git a/apt-inst/dirstream.cc b/apt-inst/dirstream.cc
new file mode 100644
index 0000000..d6cf0ab
--- /dev/null
+++ b/apt-inst/dirstream.cc
@@ -0,0 +1,118 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ Directory Stream
+
+ This class provides a simple basic extractor that can be used for
+ a number of purposes.
+
+ ##################################################################### */
+ /*}}}*/
+// Include Files /*{{{*/
+#include <config.h>
+
+#include <apt-pkg/dirstream.h>
+#include <apt-pkg/error.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <apti18n.h>
+ /*}}}*/
+
+// DirStream::DoItem - Process an item /*{{{*/
+// ---------------------------------------------------------------------
+/* This is a very simple extractor, it does not deal with things like
+ overwriting directories with files and so on. */
+bool pkgDirStream::DoItem(Item &Itm,int &Fd)
+{
+ switch (Itm.Type)
+ {
+ case Item::File:
+ {
+ /* Open the output file, NDELAY is used to prevent this from
+ blowing up on device special files.. */
+ int iFd = open(Itm.Name,O_NDELAY|O_WRONLY|O_CREAT|O_TRUNC|O_APPEND,
+ Itm.Mode);
+ if (iFd < 0)
+ return _error->Errno("open",_("Failed to write file %s"),
+ Itm.Name);
+
+ // fchmod deals with umask and fchown sets the ownership
+ if (fchmod(iFd,Itm.Mode) != 0)
+ {
+ close(iFd);
+ return _error->Errno("fchmod",_("Failed to write file %s"), Itm.Name);
+ }
+ if (fchown(iFd,Itm.UID,Itm.GID) != 0 && errno != EPERM)
+ {
+ close(iFd);
+ return _error->Errno("fchown",_("Failed to write file %s"), Itm.Name);
+ }
+ Fd = iFd;
+ return true;
+ }
+
+ case Item::HardLink:
+ case Item::SymbolicLink:
+ case Item::CharDevice:
+ case Item::BlockDevice:
+ case Item::Directory:
+ {
+ struct stat Buf;
+ // check if the dir is already there, if so return true
+ if (stat(Itm.Name,&Buf) == 0)
+ {
+ if(S_ISDIR(Buf.st_mode))
+ return true;
+ // something else is there already, return false
+ return false;
+ }
+ // nothing here, create the dir
+ if(mkdir(Itm.Name,Itm.Mode) < 0)
+ return false;
+ return true;
+ }
+ case Item::FIFO:
+ break;
+ }
+
+ return true;
+}
+ /*}}}*/
+// DirStream::FinishedFile - Finished processing a file /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool pkgDirStream::FinishedFile(Item &Itm,int Fd)
+{
+ if (Fd < 0)
+ return true;
+
+ /* Set the modification times. The only way it can fail is if someone
+ has futzed with our file, which is intolerable :> */
+ struct timeval times[2];
+ times[0].tv_sec = times[1].tv_sec = Itm.MTime;
+ times[0].tv_usec = times[1].tv_usec = 0;
+ if (utimes(Itm.Name, times) != 0)
+ _error->Errno("utimes", "Failed to set modification time for %s",Itm.Name);
+
+ if (close(Fd) != 0)
+ return _error->Errno("close",_("Failed to close file %s"),Itm.Name);
+ return true;
+}
+ /*}}}*/
+// DirStream::Fail - Failed processing a file /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool pkgDirStream::Fail(Item &/*Itm*/, int Fd)
+{
+ if (Fd < 0)
+ return true;
+
+ close(Fd);
+ return false;
+}
+ /*}}}*/
diff --git a/apt-inst/dirstream.h b/apt-inst/dirstream.h
new file mode 100644
index 0000000..0f0e348
--- /dev/null
+++ b/apt-inst/dirstream.h
@@ -0,0 +1,57 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ Directory Stream
+
+ When unpacking the contents of the archive are passed into a directory
+ stream class for analysis and processing. The class controls all aspects
+ of actually writing the directory stream from disk. The low level
+ archive handlers are only responsible for decoding the archive format
+ and sending events (via method calls) to the specified directory
+ stream.
+
+ When unpacking a real file the archive handler is passed back a file
+ handle to write the data to, this is to support strange
+ archives+unpacking methods. If that fd is -1 then the file data is
+ simply ignored.
+
+ The provided defaults do the 'Right Thing' for a normal unpacking
+ process (ie 'tar')
+
+ ##################################################################### */
+ /*}}}*/
+#ifndef PKGLIB_DIRSTREAM_H
+#define PKGLIB_DIRSTREAM_H
+
+#include <apt-pkg/macros.h>
+
+class pkgDirStream
+{
+ public:
+
+ // All possible information about a component
+ struct Item
+ {
+ enum Type_t {File, HardLink, SymbolicLink, CharDevice, BlockDevice,
+ Directory, FIFO} Type;
+ char *Name;
+ char *LinkTarget;
+ unsigned long Mode;
+ unsigned long UID;
+ unsigned long GID;
+ unsigned long long Size;
+ unsigned long MTime;
+ unsigned long Major;
+ unsigned long Minor;
+ };
+
+ virtual bool DoItem(Item &Itm,int &Fd);
+ virtual bool Fail(Item &Itm,int Fd);
+ virtual bool FinishedFile(Item &Itm,int Fd);
+ virtual bool Process(Item &/*Itm*/,const unsigned char * /*Data*/,
+ unsigned long long /*Size*/,unsigned long long /*Pos*/) {return true;};
+ virtual ~pkgDirStream() {};
+};
+
+#endif
diff --git a/apt-inst/dpkg-diffs.txt b/apt-inst/dpkg-diffs.txt
new file mode 100644
index 0000000..d161055
--- /dev/null
+++ b/apt-inst/dpkg-diffs.txt
@@ -0,0 +1,5 @@
+- Replacing directories with files
+ dpkg permits this with the weak condition that the directory is owned only
+ by the package. APT requires that the directory have no files that are not
+ owned by the package. Replaces are specifically not checked to prevent
+ file list corruption.
diff --git a/apt-inst/extract.cc b/apt-inst/extract.cc
new file mode 100644
index 0000000..35fa015
--- /dev/null
+++ b/apt-inst/extract.cc
@@ -0,0 +1,514 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ Archive Extraction Directory Stream
+
+ Extraction for each file is a bit of an involved process. Each object
+ undergoes an atomic backup, overwrite, erase sequence. First the
+ object is unpacked to '.dpkg.new' then the original is hardlinked to
+ '.dpkg.tmp' and finally the new object is renamed to overwrite the old
+ one. From an external perspective the file never ceased to exist.
+ After the archive has been successfully unpacked the .dpkg.tmp files
+ are erased. A failure causes all the .dpkg.tmp files to be restored.
+
+ Decisions about unpacking go like this:
+ - Store the original filename in the file listing
+ - Resolve any diversions that would effect this file, all checks
+ below apply to the diverted name, not the real one.
+ - Resolve any symlinked configuration files.
+ - If the existing file does not exist then .dpkg-tmp is checked for.
+ [Note, this is reduced to only check if a file was expected to be
+ there]
+ - If the existing link/file is not a directory then it is replaced
+ regardless
+ - If the existing link/directory is being replaced by a directory then
+ absolutely nothing happens.
+ - If the existing link/directory is being replaced by a link then
+ absolutely nothing happens.
+ - If the existing link/directory is being replaced by a non-directory
+ then this will abort if the package is not the sole owner of the
+ directory. [Note, this is changed to not happen if the directory
+ non-empty - that is, it only includes files that are part of this
+ package - prevents removing user files accidentally.]
+ - If the non-directory exists in the listing database and it
+ does not belong to the current package then an overwrite condition
+ is invoked.
+
+ As we unpack we record the file list differences in the FL cache. If
+ we need to unroll the FL cache knows which files have been unpacked
+ and can undo. When we need to erase then it knows which files have not
+ been unpacked.
+
+ ##################################################################### */
+ /*}}}*/
+// Include Files /*{{{*/
+#include <config.h>
+
+#include <apt-pkg/debversion.h>
+#include <apt-pkg/dirstream.h>
+#include <apt-pkg/error.h>
+#include <apt-pkg/extract.h>
+#include <apt-pkg/filelist.h>
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/mmap.h>
+#include <apt-pkg/pkgcache.h>
+
+#include <iostream>
+#include <string>
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <apti18n.h>
+ /*}}}*/
+using namespace std;
+
+static const char *TempExt = "dpkg-tmp";
+//static const char *NewExt = "dpkg-new";
+
+// Extract::pkgExtract - Constructor /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+pkgExtract::pkgExtract(pkgFLCache &FLCache,pkgCache::VerIterator Ver) :
+ FLCache(FLCache), Ver(Ver)
+{
+ FLPkg = FLCache.GetPkg(Ver.ParentPkg().Name(),true);
+ if (FLPkg.end() == true)
+ return;
+ Debug = true;
+}
+ /*}}}*/
+// Extract::DoItem - Handle a single item from the stream /*{{{*/
+// ---------------------------------------------------------------------
+/* This performs the setup for the extraction.. */
+bool pkgExtract::DoItem(Item &Itm, int &/*Fd*/)
+{
+ /* Strip any leading/trailing /s from the filename, then copy it to the
+ temp buffer and re-apply the leading / We use a class variable
+ to store the new filename for use by the three extraction funcs */
+ char *End = FileName+1;
+ const char *I = Itm.Name;
+ for (; *I != 0 && *I == '/'; I++);
+ *FileName = '/';
+ for (; *I != 0 && End < FileName + sizeof(FileName); I++, End++)
+ *End = *I;
+ if (End + 20 >= FileName + sizeof(FileName))
+ return _error->Error(_("The path %s is too long"),Itm.Name);
+ for (; End > FileName && End[-1] == '/'; End--);
+ *End = 0;
+ Itm.Name = FileName;
+
+ /* Lookup the file. Nde is the file [group] we are going to write to and
+ RealNde is the actual node we are manipulating. Due to diversions
+ they may be entirely different. */
+ pkgFLCache::NodeIterator Nde = FLCache.GetNode(Itm.Name,End,0,false,false);
+ pkgFLCache::NodeIterator RealNde = Nde;
+
+ // See if the file is already in the file listing
+ unsigned long FileGroup = RealNde->File;
+ for (; RealNde.end() == false && FileGroup == RealNde->File; RealNde++)
+ if (RealNde.RealPackage() == FLPkg)
+ break;
+
+ // Nope, create an entry
+ if (RealNde.end() == true)
+ {
+ RealNde = FLCache.GetNode(Itm.Name,End,FLPkg.Offset(),true,false);
+ if (RealNde.end() == true)
+ return false;
+ RealNde->Flags |= pkgFLCache::Node::NewFile;
+ }
+
+ /* Check if this entry already was unpacked. The only time this should
+ ever happen is if someone has hacked tar to support capabilities, in
+ which case this needs to be modified anyhow.. */
+ if ((RealNde->Flags & pkgFLCache::Node::Unpacked) ==
+ pkgFLCache::Node::Unpacked)
+ return _error->Error(_("Unpacking %s more than once"),Itm.Name);
+
+ if (Nde.end() == true)
+ Nde = RealNde;
+
+ /* Consider a diverted file - We are not permitted to divert directories,
+ but everything else is fair game (including conf files!) */
+ if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
+ {
+ if (Itm.Type == Item::Directory)
+ return _error->Error(_("The directory %s is diverted"),Itm.Name);
+
+ /* A package overwriting a diversion target is just the same as
+ overwriting a normally owned file and is checked for below in
+ the overwrites mechanism */
+
+ /* If this package is trying to overwrite the target of a diversion,
+ that is never, ever permitted */
+ pkgFLCache::DiverIterator Div = Nde.Diversion();
+ if (Div.DivertTo() == Nde)
+ return _error->Error(_("The package is trying to write to the "
+ "diversion target %s/%s"),Nde.DirN(),Nde.File());
+
+ // See if it is us and we are following it in the right direction
+ if (Div->OwnerPkg != FLPkg.Offset() && Div.DivertFrom() == Nde)
+ {
+ Nde = Div.DivertTo();
+ End = FileName + snprintf(FileName,sizeof(FileName)-20,"%s/%s",
+ Nde.DirN(),Nde.File());
+ if (End <= FileName)
+ return _error->Error(_("The diversion path is too long"));
+ }
+ }
+
+ // Deal with symlinks and conf files
+ if ((RealNde->Flags & pkgFLCache::Node::NewConfFile) ==
+ pkgFLCache::Node::NewConfFile)
+ {
+ string Res = flNoLink(Itm.Name);
+ if (Res.length() > sizeof(FileName))
+ return _error->Error(_("The path %s is too long"),Res.c_str());
+ if (Debug == true)
+ clog << "Followed conf file from " << FileName << " to " << Res << endl;
+ Itm.Name = strcpy(FileName,Res.c_str());
+ }
+
+ /* Get information about the existing file, and attempt to restore
+ a backup if it does not exist */
+ struct stat LExisting;
+ bool EValid = false;
+ if (lstat(Itm.Name,&LExisting) != 0)
+ {
+ // This is bad news.
+ if (errno != ENOENT)
+ return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
+
+ // See if we can recover the backup file
+ if (Nde.end() == false)
+ {
+ char Temp[sizeof(FileName)];
+ snprintf(Temp,sizeof(Temp),"%s.%s",Itm.Name,TempExt);
+ if (rename(Temp,Itm.Name) != 0 && errno != ENOENT)
+ return _error->Errno("rename",_("Failed to rename %s to %s"),
+ Temp,Itm.Name);
+ if (stat(Itm.Name,&LExisting) != 0)
+ {
+ if (errno != ENOENT)
+ return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
+ }
+ else
+ EValid = true;
+ }
+ }
+ else
+ EValid = true;
+
+ /* If the file is a link we need to stat its destination, get the
+ existing file modes */
+ struct stat Existing = LExisting;
+ if (EValid == true && S_ISLNK(Existing.st_mode))
+ {
+ if (stat(Itm.Name,&Existing) != 0)
+ {
+ if (errno != ENOENT)
+ return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
+ Existing = LExisting;
+ }
+ }
+
+ // We pretend a non-existing file looks like it is a normal file
+ if (EValid == false)
+ Existing.st_mode = S_IFREG;
+
+ /* Okay, at this point 'Existing' is the stat information for the
+ real non-link file */
+
+ /* The only way this can be a no-op is if a directory is being
+ replaced by a directory or by a link */
+ if (S_ISDIR(Existing.st_mode) != 0 &&
+ (Itm.Type == Item::Directory || Itm.Type == Item::SymbolicLink))
+ return true;
+
+ /* Non-Directory being replaced by non-directory. We check for over
+ writes here. */
+ if (Nde.end() == false)
+ {
+ if (HandleOverwrites(Nde) == false)
+ return false;
+ }
+
+ /* Directory being replaced by a non-directory - this needs to see if
+ the package is the owner and then see if the directory would be
+ empty after the package is removed [ie no user files will be
+ erased] */
+ if (S_ISDIR(Existing.st_mode) != 0)
+ {
+ if (CheckDirReplace(Itm.Name) == false)
+ return _error->Error(_("The directory %s is being replaced by a non-directory"),Itm.Name);
+ }
+
+ if (Debug == true)
+ clog << "Extract " << string(Itm.Name,End) << endl;
+/* if (Count != 0)
+ return _error->Error(_("Done"));*/
+
+ return true;
+}
+ /*}}}*/
+// Extract::Finished - Sequence finished, erase the temp files /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+APT_PURE bool pkgExtract::Finished()
+{
+ return true;
+}
+ /*}}}*/
+// Extract::Aborted - Sequence aborted, undo all our unpacking /*{{{*/
+// ---------------------------------------------------------------------
+/* This undoes everything that was done by all calls to the DoItem method
+ and restores the File Listing cache to its original form. It bases its
+ actions on the flags value for each node in the cache. */
+bool pkgExtract::Aborted()
+{
+ if (Debug == true)
+ clog << "Aborted, backing out" << endl;
+
+ pkgFLCache::NodeIterator Files = FLPkg.Files();
+ map_ptrloc *Last = &FLPkg->Files;
+
+ /* Loop over all files, restore those that have been unpacked from their
+ dpkg-tmp entries */
+ while (Files.end() == false)
+ {
+ // Locate the hash bucket for the node and locate its group head
+ pkgFLCache::NodeIterator Nde(FLCache,FLCache.HashNode(Files));
+ for (; Nde.end() == false && Files->File != Nde->File; Nde++);
+ if (Nde.end() == true)
+ return _error->Error(_("Failed to locate node in its hash bucket"));
+
+ if (snprintf(FileName,sizeof(FileName)-20,"%s/%s",
+ Nde.DirN(),Nde.File()) <= 0)
+ return _error->Error(_("The path is too long"));
+
+ // Deal with diversions
+ if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
+ {
+ pkgFLCache::DiverIterator Div = Nde.Diversion();
+
+ // See if it is us and we are following it in the right direction
+ if (Div->OwnerPkg != FLPkg.Offset() && Div.DivertFrom() == Nde)
+ {
+ Nde = Div.DivertTo();
+ if (snprintf(FileName,sizeof(FileName)-20,"%s/%s",
+ Nde.DirN(),Nde.File()) <= 0)
+ return _error->Error(_("The diversion path is too long"));
+ }
+ }
+
+ // Deal with overwrites+replaces
+ for (; Nde.end() == false && Files->File == Nde->File; Nde++)
+ {
+ if ((Nde->Flags & pkgFLCache::Node::Replaced) ==
+ pkgFLCache::Node::Replaced)
+ {
+ if (Debug == true)
+ clog << "De-replaced " << FileName << " from " << Nde.RealPackage()->Name << endl;
+ Nde->Flags &= ~pkgFLCache::Node::Replaced;
+ }
+ }
+
+ // Undo the change in the filesystem
+ if (Debug == true)
+ clog << "Backing out " << FileName;
+
+ // Remove a new node
+ if ((Files->Flags & pkgFLCache::Node::NewFile) ==
+ pkgFLCache::Node::NewFile)
+ {
+ if (Debug == true)
+ clog << " [new node]" << endl;
+ pkgFLCache::Node *Tmp = Files;
+ Files++;
+ *Last = Tmp->NextPkg;
+ Tmp->NextPkg = 0;
+
+ FLCache.DropNode(Tmp - FLCache.NodeP);
+ }
+ else
+ {
+ if (Debug == true)
+ clog << endl;
+
+ Last = &Files->NextPkg;
+ Files++;
+ }
+ }
+
+ return true;
+}
+ /*}}}*/
+// Extract::Fail - Extraction of a file Failed /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool pkgExtract::Fail(Item &Itm,int Fd)
+{
+ return pkgDirStream::Fail(Itm,Fd);
+}
+ /*}}}*/
+// Extract::FinishedFile - Finished a file /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool pkgExtract::FinishedFile(Item &Itm,int Fd)
+{
+ return pkgDirStream::FinishedFile(Itm,Fd);
+}
+ /*}}}*/
+// Extract::HandleOverwrites - See if a replaces covers this overwrite /*{{{*/
+// ---------------------------------------------------------------------
+/* Check if the file is in a package that is being replaced by this
+ package or if the file is being overwritten. Note that if the file
+ is really a directory but it has been erased from the filesystem
+ this will fail with an overwrite message. This is a limitation of the
+ dpkg file information format.
+
+ XX If a new package installs and another package replaces files in this
+ package what should we do? */
+bool pkgExtract::HandleOverwrites(pkgFLCache::NodeIterator Nde,
+ bool DiverCheck)
+{
+ pkgFLCache::NodeIterator TmpNde = Nde;
+ unsigned long DiverOwner = 0;
+ unsigned long FileGroup = Nde->File;
+ for (; Nde.end() == false && FileGroup == Nde->File; Nde++)
+ {
+ if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
+ {
+ /* Store the diversion owner if this is the forward direction
+ of the diversion */
+ if (DiverCheck == true)
+ DiverOwner = Nde.Diversion()->OwnerPkg;
+ continue;
+ }
+
+ pkgFLCache::PkgIterator FPkg(FLCache,Nde.RealPackage());
+ if (FPkg.end() == true || FPkg == FLPkg)
+ continue;
+
+ /* This tests trips when we are checking a diversion to see
+ if something has already been diverted by this diversion */
+ if (FPkg.Offset() == DiverOwner)
+ continue;
+
+ // Now see if this package matches one in a replace depends
+ pkgCache::DepIterator Dep = Ver.DependsList();
+ bool Ok = false;
+ for (; Dep.end() == false; ++Dep)
+ {
+ if (Dep->Type != pkgCache::Dep::Replaces)
+ continue;
+
+ // Does the replaces apply to this package?
+ if (strcmp(Dep.TargetPkg().Name(),FPkg.Name()) != 0)
+ continue;
+
+ /* Check the version for match. I do not think CurrentVer can be
+ 0 if we are here.. */
+ pkgCache::PkgIterator Pkg = Dep.TargetPkg();
+ if (Pkg->CurrentVer == 0)
+ {
+ _error->Warning(_("Overwrite package match with no version for %s"),Pkg.Name());
+ continue;
+ }
+
+ // Replaces is met
+ if (debVS.CheckDep(Pkg.CurrentVer().VerStr(),Dep->CompareOp,Dep.TargetVer()) == true)
+ {
+ if (Debug == true)
+ clog << "Replaced file " << Nde.DirN() << '/' << Nde.File() << " from " << Pkg.Name() << endl;
+ Nde->Flags |= pkgFLCache::Node::Replaced;
+ Ok = true;
+ break;
+ }
+ }
+
+ // Negative Hit
+ if (Ok == false)
+ return _error->Error(_("File %s/%s overwrites the one in the package %s"),
+ Nde.DirN(),Nde.File(),FPkg.Name());
+ }
+
+ /* If this is a diversion we might have to recurse to process
+ the other side of it */
+ if ((TmpNde->Flags & pkgFLCache::Node::Diversion) != 0)
+ {
+ pkgFLCache::DiverIterator Div = TmpNde.Diversion();
+ if (Div.DivertTo() == TmpNde)
+ return HandleOverwrites(Div.DivertFrom(),true);
+ }
+
+ return true;
+}
+ /*}}}*/
+// Extract::CheckDirReplace - See if this directory can be erased /*{{{*/
+// ---------------------------------------------------------------------
+/* If this directory is owned by a single package and that package is
+ replacing it with something non-directoryish then dpkg allows this.
+ We increase the requirement to be that the directory is non-empty after
+ the package is removed */
+bool pkgExtract::CheckDirReplace(string Dir,unsigned int Depth)
+{
+ // Looping?
+ if (Depth > 40)
+ return false;
+
+ if (Dir[Dir.size() - 1] != '/')
+ Dir += '/';
+
+ DIR *D = opendir(Dir.c_str());
+ if (D == 0)
+ return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
+
+ string File;
+ for (struct dirent *Dent = readdir(D); Dent != 0; Dent = readdir(D))
+ {
+ // Skip some files
+ if (strcmp(Dent->d_name,".") == 0 ||
+ strcmp(Dent->d_name,"..") == 0)
+ continue;
+
+ // Look up the node
+ File = Dir + Dent->d_name;
+ pkgFLCache::NodeIterator Nde = FLCache.GetNode(File.c_str(),
+ File.c_str() + File.length(),0,false,false);
+
+ // The file is not owned by this package
+ if (Nde.end() != false || Nde.RealPackage() != FLPkg)
+ {
+ closedir(D);
+ return false;
+ }
+
+ // See if it is a directory
+ struct stat St;
+ if (lstat(File.c_str(),&St) != 0)
+ {
+ closedir(D);
+ return _error->Errno("lstat",_("Unable to stat %s"),File.c_str());
+ }
+
+ // Recurse down directories
+ if (S_ISDIR(St.st_mode) != 0)
+ {
+ if (CheckDirReplace(File,Depth + 1) == false)
+ {
+ closedir(D);
+ return false;
+ }
+ }
+ }
+
+ // No conflicts
+ closedir(D);
+ return true;
+}
+ /*}}}*/
diff --git a/apt-inst/extract.h b/apt-inst/extract.h
new file mode 100644
index 0000000..4b4c8d7
--- /dev/null
+++ b/apt-inst/extract.h
@@ -0,0 +1,49 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ Archive Extraction Directory Stream
+
+ This Directory Stream implements extraction of an archive into the
+ filesystem. It makes the choices on what files should be unpacked and
+ replaces as well as guiding the actual unpacking.
+
+ When the unpacking sequence is completed one of the two functions,
+ Finished or Aborted must be called.
+
+ ##################################################################### */
+ /*}}}*/
+#ifndef PKGLIB_EXTRACT_H
+#define PKGLIB_EXTRACT_H
+
+#include <apt-pkg/dirstream.h>
+#include <apt-pkg/filelist.h>
+#include <apt-pkg/pkgcache.h>
+
+#include <string>
+
+class pkgExtract : public pkgDirStream
+{
+ pkgFLCache &FLCache;
+ pkgCache::VerIterator Ver;
+ pkgFLCache::PkgIterator FLPkg;
+ char FileName[1024];
+ bool Debug;
+
+ bool HandleOverwrites(pkgFLCache::NodeIterator Nde,
+ bool DiverCheck = false);
+ bool CheckDirReplace(std::string Dir,unsigned int Depth = 0);
+
+ public:
+
+ virtual bool DoItem(Item &Itm,int &Fd) APT_OVERRIDE;
+ virtual bool Fail(Item &Itm,int Fd) APT_OVERRIDE;
+ virtual bool FinishedFile(Item &Itm,int Fd) APT_OVERRIDE;
+
+ bool Finished();
+ bool Aborted();
+
+ pkgExtract(pkgFLCache &FLCache,pkgCache::VerIterator Ver);
+};
+
+#endif
diff --git a/apt-inst/filelist.cc b/apt-inst/filelist.cc
new file mode 100644
index 0000000..44b97d0
--- /dev/null
+++ b/apt-inst/filelist.cc
@@ -0,0 +1,586 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ File Listing - Manages a Cache of File -> Package names.
+
+ Diversions add some significant complexity to the system. To keep
+ storage space down in the very special case of a diverted file no
+ extra bytes are allocated in the Node structure. Instead a diversion
+ is inserted directly into the hash table and its flag bit set. Every
+ lookup for that filename will always return the diversion.
+
+ The hash buckets are stored in sorted form, with diversions having
+ the highest sort order. Identical files are assigned the same file
+ pointer, thus after a search all of the nodes owning that file can be
+ found by iterating down the bucket.
+
+ Re-updates of diversions (another extremely special case) are done by
+ marking all diversions as untouched, then loading the entire diversion
+ list again, touching each diversion and then finally going back and
+ releasing all untouched diversions. It is assumed that the diversion
+ table will always be quite small and be a very irregular case.
+
+ Diversions that are user-installed are represented by a package with
+ an empty name string.
+
+ Conf files are handled like diversions by changing the meaning of the
+ Pointer field to point to a conf file entry - again to reduce over
+ head for a special case.
+
+ ##################################################################### */
+ /*}}}*/
+// Include Files /*{{{*/
+#include <config.h>
+
+#include <apt-pkg/error.h>
+#include <apt-pkg/filelist.h>
+#include <apt-pkg/mmap.h>
+#include <apt-pkg/strutl.h>
+
+#include <iostream>
+#include <string.h>
+#include <apti18n.h>
+ /*}}}*/
+
+using namespace std;
+
+// FlCache::Header::Header - Constructor /*{{{*/
+// ---------------------------------------------------------------------
+/* Initialize the header variables. These are the defaults used when
+ creating new caches */
+pkgFLCache::Header::Header()
+{
+ Signature = 0xEA3F1295;
+
+ /* Whenever the structures change the major version should be bumped,
+ whenever the generator changes the minor version should be bumped. */
+ MajorVersion = 1;
+ MinorVersion = 0;
+ Dirty = true;
+
+ HeaderSz = sizeof(pkgFLCache::Header);
+ NodeSz = sizeof(pkgFLCache::Node);
+ DirSz = sizeof(pkgFLCache::Directory);
+ PackageSz = sizeof(pkgFLCache::Package);
+ DiversionSz = sizeof(pkgFLCache::Diversion);
+ ConfFileSz = sizeof(pkgFLCache::ConfFile);
+
+ NodeCount = 0;
+ DirCount = 0;
+ PackageCount = 0;
+ DiversionCount = 0;
+ ConfFileCount = 0;
+ HashSize = 1 << 14;
+
+ FileHash = 0;
+ DirTree = 0;
+ Packages = 0;
+ Diversions = 0;
+ UniqNodes = 0;
+ memset(Pools,0,sizeof(Pools));
+}
+ /*}}}*/
+// FLCache::Header::CheckSizes - Check if the two headers have same *sz /*{{{*/
+// ---------------------------------------------------------------------
+/* Compare to make sure we are matching versions */
+APT_PURE bool pkgFLCache::Header::CheckSizes(Header &Against) const
+{
+ if (HeaderSz == Against.HeaderSz &&
+ NodeSz == Against.NodeSz &&
+ DirSz == Against.DirSz &&
+ DiversionSz == Against.DiversionSz &&
+ PackageSz == Against.PackageSz &&
+ ConfFileSz == Against.ConfFileSz)
+ return true;
+ return false;
+}
+ /*}}}*/
+
+// FLCache::pkgFLCache - Constructor /*{{{*/
+// ---------------------------------------------------------------------
+/* If this is a new cache then a new header and hash table are instantaited
+ otherwise the existing ones are mearly attached */
+pkgFLCache::pkgFLCache(DynamicMMap &Map) : Map(Map)
+{
+ if (_error->PendingError() == true)
+ return;
+
+ LastTreeLookup = 0;
+ LastLookupSize = 0;
+
+ // Apply the typecasts
+ HeaderP = (Header *)Map.Data();
+ NodeP = (Node *)Map.Data();
+ DirP = (Directory *)Map.Data();
+ DiverP = (Diversion *)Map.Data();
+ PkgP = (Package *)Map.Data();
+ ConfP = (ConfFile *)Map.Data();
+ StrP = (char *)Map.Data();
+ AnyP = (unsigned char *)Map.Data();
+
+ // New mapping, create the basic cache structures
+ if (Map.Size() == 0)
+ {
+ Map.RawAllocate(sizeof(pkgFLCache::Header));
+ *HeaderP = pkgFLCache::Header();
+ HeaderP->FileHash = Map.RawAllocate(sizeof(pkgFLCache::Node)*HeaderP->HashSize,
+ sizeof(pkgFLCache::Node))/sizeof(pkgFLCache::Node);
+ }
+
+ FileHash = NodeP + HeaderP->FileHash;
+
+ // Setup the dynamic map manager
+ HeaderP->Dirty = true;
+ Map.Sync(0,sizeof(pkgFLCache::Header));
+ Map.UsePools(*HeaderP->Pools,sizeof(HeaderP->Pools)/sizeof(HeaderP->Pools[0]));
+}
+ /*}}}*/
+// FLCache::TreeLookup - Perform a lookup in a generic tree /*{{{*/
+// ---------------------------------------------------------------------
+/* This is a simple generic tree lookup. The first three entries of
+ the Directory structure are used as a template, but any other similar
+ structure could be used in it's place. */
+map_ptrloc pkgFLCache::TreeLookup(map_ptrloc *Base,const char *Text,
+ const char *TextEnd,unsigned long Size,
+ unsigned int *Count,bool Insert)
+{
+ pkgFLCache::Directory *Dir;
+
+ // Check our last entry cache
+ if (LastTreeLookup != 0 && LastLookupSize == Size)
+ {
+ Dir = (pkgFLCache::Directory *)(AnyP + LastTreeLookup*Size);
+ if (stringcmp(Text,TextEnd,StrP + Dir->Name) == 0)
+ return LastTreeLookup;
+ }
+
+ while (1)
+ {
+ // Allocate a new one
+ if (*Base == 0)
+ {
+ if (Insert == false)
+ return 0;
+
+ *Base = Map.Allocate(Size);
+ if (*Base == 0)
+ return 0;
+
+ (*Count)++;
+ Dir = (pkgFLCache::Directory *)(AnyP + *Base*Size);
+ Dir->Name = Map.WriteString(Text,TextEnd - Text);
+ LastTreeLookup = *Base;
+ LastLookupSize = Size;
+ return *Base;
+ }
+
+ // Compare this node
+ Dir = (pkgFLCache::Directory *)(AnyP + *Base*Size);
+ int Res = stringcmp(Text,TextEnd,StrP + Dir->Name);
+ if (Res == 0)
+ {
+ LastTreeLookup = *Base;
+ LastLookupSize = Size;
+ return *Base;
+ }
+
+ if (Res > 0)
+ Base = &Dir->Left;
+ if (Res < 0)
+ Base = &Dir->Right;
+ }
+}
+ /*}}}*/
+// FLCache::PrintTree - Print out a tree /*{{{*/
+// ---------------------------------------------------------------------
+/* This is a simple generic tree dumper, meant for debugging. */
+void pkgFLCache::PrintTree(map_ptrloc Base,unsigned long Size)
+{
+ if (Base == 0)
+ return;
+
+ pkgFLCache::Directory *Dir = (pkgFLCache::Directory *)(AnyP + Base*Size);
+ PrintTree(Dir->Left,Size);
+ cout << (StrP + Dir->Name) << endl;
+ PrintTree(Dir->Right,Size);
+}
+ /*}}}*/
+// FLCache::GetPkg - Get a package pointer /*{{{*/
+// ---------------------------------------------------------------------
+/* Locate a package by name in it's tree, this is just a wrapper for
+ TreeLookup */
+pkgFLCache::PkgIterator pkgFLCache::GetPkg(const char *Name,const char *NameEnd,
+ bool Insert)
+{
+ if (NameEnd == 0)
+ NameEnd = Name + strlen(Name);
+
+ map_ptrloc Pos = TreeLookup(&HeaderP->Packages,Name,NameEnd,
+ sizeof(pkgFLCache::Package),
+ &HeaderP->PackageCount,Insert);
+ if (Pos == 0)
+ return pkgFLCache::PkgIterator();
+ return pkgFLCache::PkgIterator(*this,PkgP + Pos);
+}
+ /*}}}*/
+// FLCache::GetNode - Get the node associated with the filename /*{{{*/
+// ---------------------------------------------------------------------
+/* Lookup a node in the hash table. If Insert is true then a new node is
+ always inserted. The hash table can have multiple instances of a
+ single name available. A search returns the first. It is important
+ that additions for the same name insert after the first entry of
+ the name group. */
+pkgFLCache::NodeIterator pkgFLCache::GetNode(const char *Name,
+ const char *NameEnd,
+ map_ptrloc Loc,
+ bool Insert,bool Divert)
+{
+ // Split the name into file and directory, hashing as it is copied
+ const char *File = Name;
+ unsigned long HashPos = 0;
+ for (const char *I = Name; I < NameEnd; I++)
+ {
+ HashPos = 1637*HashPos + *I;
+ if (*I == '/')
+ File = I;
+ }
+
+ // Search for it
+ Node *Hash = NodeP + HeaderP->FileHash + (HashPos % HeaderP->HashSize);
+ int Res = 0;
+ map_ptrloc FilePtr = 0;
+ while (Hash->Pointer != 0)
+ {
+ // Compare
+ Res = stringcmp(File+1,NameEnd,StrP + Hash->File);
+ if (Res == 0)
+ Res = stringcmp(Name,File,StrP + DirP[Hash->Dir].Name);
+
+ // Diversion?
+ if (Res == 0 && Insert == true)
+ {
+ /* Dir and File match exactly, we need to reuse the file name
+ when we link it in */
+ FilePtr = Hash->File;
+ Res = Divert - ((Hash->Flags & Node::Diversion) == Node::Diversion);
+ }
+
+ // Is a match
+ if (Res == 0)
+ {
+ if (Insert == false)
+ return NodeIterator(*this,Hash);
+
+ // Only one diversion per name!
+ if (Divert == true)
+ return NodeIterator(*this,Hash);
+ break;
+ }
+
+ // Out of sort order
+ if (Res > 0)
+ break;
+
+ if (Hash->Next != 0)
+ Hash = NodeP + Hash->Next;
+ else
+ break;
+ }
+
+ // Fail, not found
+ if (Insert == false)
+ return NodeIterator(*this);
+
+ // Find a directory node
+ map_ptrloc Dir = TreeLookup(&HeaderP->DirTree,Name,File,
+ sizeof(pkgFLCache::Directory),
+ &HeaderP->DirCount,true);
+ if (Dir == 0)
+ return NodeIterator(*this);
+
+ // Allocate a new node
+ if (Hash->Pointer != 0)
+ {
+ // Overwrite or append
+ if (Res > 0)
+ {
+ Node *Next = NodeP + Map.Allocate(sizeof(*Hash));
+ if (Next == NodeP)
+ return NodeIterator(*this);
+ *Next = *Hash;
+ Hash->Next = Next - NodeP;
+ }
+ else
+ {
+ unsigned long NewNext = Map.Allocate(sizeof(*Hash));
+ if (NewNext == 0)
+ return NodeIterator(*this);
+ NodeP[NewNext].Next = Hash->Next;
+ Hash->Next = NewNext;
+ Hash = NodeP + Hash->Next;
+ }
+ }
+
+ // Insert into the new item
+ Hash->Dir = Dir;
+ Hash->Pointer = Loc;
+ Hash->Flags = 0;
+ if (Divert == true)
+ Hash->Flags |= Node::Diversion;
+
+ if (FilePtr != 0)
+ Hash->File = FilePtr;
+ else
+ {
+ HeaderP->UniqNodes++;
+ Hash->File = Map.WriteString(File+1,NameEnd - File-1);
+ }
+
+ // Link the node to the package list
+ if (Divert == false && Loc == 0)
+ {
+ Hash->Next = PkgP[Loc].Files;
+ PkgP[Loc].Files = Hash - NodeP;
+ }
+
+ HeaderP->NodeCount++;
+ return NodeIterator(*this,Hash);
+}
+ /*}}}*/
+// FLCache::HashNode - Return the hash bucket for the node /*{{{*/
+// ---------------------------------------------------------------------
+/* This is one of two hashing functions. The other is inlined into the
+ GetNode routine. */
+APT_PURE pkgFLCache::Node *pkgFLCache::HashNode(NodeIterator const &Nde)
+{
+ // Hash the node
+ unsigned long HashPos = 0;
+ for (const char *I = Nde.DirN(); *I != 0; I++)
+ HashPos = 1637*HashPos + *I;
+ HashPos = 1637*HashPos + '/';
+ for (const char *I = Nde.File(); *I != 0; I++)
+ HashPos = 1637*HashPos + *I;
+ return NodeP + HeaderP->FileHash + (HashPos % HeaderP->HashSize);
+}
+ /*}}}*/
+// FLCache::DropNode - Drop a node from the hash table /*{{{*/
+// ---------------------------------------------------------------------
+/* This erases a node from the hash table. Note that this does not unlink
+ the node from the package linked list. */
+void pkgFLCache::DropNode(map_ptrloc N)
+{
+ if (N == 0)
+ return;
+
+ NodeIterator Nde(*this,NodeP + N);
+
+ if (Nde->NextPkg != 0)
+ _error->Warning(_("DropNode called on still linked node"));
+
+ // Locate it in the hash table
+ Node *Last = 0;
+ Node *Hash = HashNode(Nde);
+ while (Hash->Pointer != 0)
+ {
+ // Got it
+ if (Hash == Nde)
+ {
+ // Top of the bucket..
+ if (Last == 0)
+ {
+ Hash->Pointer = 0;
+ if (Hash->Next == 0)
+ return;
+ *Hash = NodeP[Hash->Next];
+ // Release Hash->Next
+ return;
+ }
+ Last->Next = Hash->Next;
+ // Release Hash
+ return;
+ }
+
+ Last = Hash;
+ if (Hash->Next != 0)
+ Hash = NodeP + Hash->Next;
+ else
+ break;
+ }
+
+ _error->Error(_("Failed to locate the hash element!"));
+}
+ /*}}}*/
+// FLCache::BeginDiverLoad - Start reading new diversions /*{{{*/
+// ---------------------------------------------------------------------
+/* Tag all the diversions as untouched */
+void pkgFLCache::BeginDiverLoad()
+{
+ for (DiverIterator I = DiverBegin(); I.end() == false; I++)
+ I->Flags = 0;
+}
+ /*}}}*/
+// FLCache::FinishDiverLoad - Finish up a new diversion load /*{{{*/
+// ---------------------------------------------------------------------
+/* This drops any untouched diversions. In effect removing any diversions
+ that where not loaded (ie missing from the diversion file) */
+void pkgFLCache::FinishDiverLoad()
+{
+ map_ptrloc *Cur = &HeaderP->Diversions;
+ while (*Cur != 0)
+ {
+ Diversion *Div = DiverP + *Cur;
+ if ((Div->Flags & Diversion::Touched) == Diversion::Touched)
+ {
+ Cur = &Div->Next;
+ continue;
+ }
+
+ // Purge!
+ DropNode(Div->DivertTo);
+ DropNode(Div->DivertFrom);
+ *Cur = Div->Next;
+ }
+}
+ /*}}}*/
+// FLCache::AddDiversion - Add a new diversion /*{{{*/
+// ---------------------------------------------------------------------
+/* Add a new diversion to the diverion tables and make sure that it is
+ unique and non-chaining. */
+bool pkgFLCache::AddDiversion(PkgIterator const &Owner,
+ const char *From,const char *To)
+{
+ /* Locate the two hash nodes we are going to manipulate. If there
+ are pre-existing diversions then they will be returned */
+ NodeIterator FromN = GetNode(From,From+strlen(From),0,true,true);
+ NodeIterator ToN = GetNode(To,To+strlen(To),0,true,true);
+ if (FromN.end() == true || ToN.end() == true)
+ return _error->Error(_("Failed to allocate diversion"));
+
+ // Should never happen
+ if ((FromN->Flags & Node::Diversion) != Node::Diversion ||
+ (ToN->Flags & Node::Diversion) != Node::Diversion)
+ return _error->Error(_("Internal error in AddDiversion"));
+
+ // Now, try to reclaim an existing diversion..
+ map_ptrloc Diver = 0;
+ if (FromN->Pointer != 0)
+ Diver = FromN->Pointer;
+
+ /* Make sure from and to point to the same diversion, if they don't
+ then we are trying to intermix diversions - very bad */
+ if (ToN->Pointer != 0 && ToN->Pointer != Diver)
+ {
+ // It could be that the other diversion is no longer in use
+ if ((DiverP[ToN->Pointer].Flags & Diversion::Touched) == Diversion::Touched)
+ return _error->Error(_("Trying to overwrite a diversion, %s -> %s and %s/%s"),
+ From,To,ToN.File(),ToN.Dir().Name());
+
+ // We can erase it.
+ Diversion *Div = DiverP + ToN->Pointer;
+ ToN->Pointer = 0;
+
+ if (Div->DivertTo == ToN.Offset())
+ Div->DivertTo = 0;
+ if (Div->DivertFrom == ToN.Offset())
+ Div->DivertFrom = 0;
+
+ // This diversion will be cleaned up by FinishDiverLoad
+ }
+
+ // Allocate a new diversion
+ if (Diver == 0)
+ {
+ Diver = Map.Allocate(sizeof(Diversion));
+ if (Diver == 0)
+ return false;
+ DiverP[Diver].Next = HeaderP->Diversions;
+ HeaderP->Diversions = Diver;
+ HeaderP->DiversionCount++;
+ }
+
+ // Can only have one diversion of the same files
+ Diversion *Div = DiverP + Diver;
+ if ((Div->Flags & Diversion::Touched) == Diversion::Touched)
+ return _error->Error(_("Double add of diversion %s -> %s"),From,To);
+
+ // Setup the From/To links
+ if (Div->DivertFrom != FromN.Offset() && Div->DivertFrom != ToN.Offset())
+ DropNode(Div->DivertFrom);
+ Div->DivertFrom = FromN.Offset();
+ if (Div->DivertTo != FromN.Offset() && Div->DivertTo != ToN.Offset())
+ DropNode(Div->DivertTo);
+ Div->DivertTo = ToN.Offset();
+
+ // Link it to the two nodes
+ FromN->Pointer = Diver;
+ ToN->Pointer = Diver;
+
+ // And the package
+ Div->OwnerPkg = Owner.Offset();
+ Div->Flags |= Diversion::Touched;
+
+ return true;
+}
+ /*}}}*/
+// FLCache::AddConfFile - Add a new configuration file /*{{{*/
+// ---------------------------------------------------------------------
+/* This simply adds a new conf file node to the hash table. This is only
+ used by the status file reader. It associates a hash with each conf
+ file entry that exists in the status file and the list file for
+ the proper package. Duplicate conf files (across packages) are left
+ up to other routines to deal with. */
+bool pkgFLCache::AddConfFile(const char *Name,const char *NameEnd,
+ PkgIterator const &Owner,
+ const unsigned char *Sum)
+{
+ NodeIterator Nde = GetNode(Name,NameEnd,0,false,false);
+ if (Nde.end() == true)
+ return true;
+
+ unsigned long File = Nde->File;
+ for (; Nde->File == File && Nde.end() == false; Nde++)
+ {
+ if (Nde.RealPackage() != Owner)
+ continue;
+
+ if ((Nde->Flags & Node::ConfFile) == Node::ConfFile)
+ return _error->Error(_("Duplicate conf file %s/%s"),Nde.DirN(),Nde.File());
+
+ // Allocate a new conf file structure
+ map_ptrloc Conf = Map.Allocate(sizeof(ConfFile));
+ if (Conf == 0)
+ return false;
+ ConfP[Conf].OwnerPkg = Owner.Offset();
+ memcpy(ConfP[Conf].MD5,Sum,sizeof(ConfP[Conf].MD5));
+
+ Nde->Pointer = Conf;
+ Nde->Flags |= Node::ConfFile;
+ return true;
+ }
+
+ /* This means the conf file has been replaced, but the entry in the
+ status file was not updated */
+ return true;
+}
+ /*}}}*/
+
+// NodeIterator::RealPackage - Return the package for this node /*{{{*/
+// ---------------------------------------------------------------------
+/* Since the package pointer is indirected in all sorts of interesting ways
+ this is used to get a pointer to the owning package */
+APT_PURE pkgFLCache::Package *pkgFLCache::NodeIterator::RealPackage() const
+{
+ if (Nde->Pointer == 0)
+ return 0;
+
+ if ((Nde->Flags & Node::ConfFile) == Node::ConfFile)
+ return Owner->PkgP + Owner->ConfP[Nde->Pointer].OwnerPkg;
+
+ // Diversions are ignored
+ if ((Nde->Flags & Node::Diversion) == Node::Diversion)
+ return 0;
+
+ return Owner->PkgP + Nde->Pointer;
+}
+ /*}}}*/
diff --git a/apt-inst/filelist.h b/apt-inst/filelist.h
new file mode 100644
index 0000000..7fe43de
--- /dev/null
+++ b/apt-inst/filelist.h
@@ -0,0 +1,312 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ File Listing - Manages a Cache of File -> Package names.
+
+ This is identical to the Package cache, except that the generator
+ (which is much simpler) is integrated directly into the main class,
+ and it has been designed to handle live updates.
+
+ The storage content of the class is maintained in a memory map and is
+ written directly to the file system. Performance is traded against
+ space to give something that performs well and remains small.
+ The average per file usage is 32 bytes which yields about a meg every
+ 36k files. Directory paths are collected into a binary tree and stored
+ only once, this offsets the cost of the hash nodes enough to keep
+ memory usage slightly less than the sum of the filenames.
+
+ The file names are stored into a fixed size chained hash table that is
+ linked to the package name and to the directory component.
+
+ Each file node has a set of associated flags that indicate the current
+ state of the file.
+
+ ##################################################################### */
+ /*}}}*/
+#ifndef PKGLIB_FILELIST_H
+#define PKGLIB_FILELIST_H
+
+#include <apt-pkg/mmap.h>
+
+#include <cstring>
+#include <string>
+
+class pkgFLCache
+{
+ public:
+ struct Header;
+ struct Node;
+ struct Directory;
+ struct Package;
+ struct Diversion;
+ struct ConfFile;
+
+ class NodeIterator;
+ class DirIterator;
+ class PkgIterator;
+ class DiverIterator;
+
+ protected:
+ std::string CacheFile;
+ DynamicMMap &Map;
+ map_ptrloc LastTreeLookup;
+ unsigned long LastLookupSize;
+
+ // Helpers for the addition algorithms
+ map_ptrloc TreeLookup(map_ptrloc *Base,const char *Text,const char *TextEnd,
+ unsigned long Size,unsigned int *Count = 0,
+ bool Insert = false);
+
+ public:
+
+ // Pointers to the arrays of items
+ Header *HeaderP;
+ Node *NodeP;
+ Directory *DirP;
+ Package *PkgP;
+ Diversion *DiverP;
+ ConfFile *ConfP;
+ char *StrP;
+ unsigned char *AnyP;
+
+ // Quick accessors
+ Node *FileHash;
+
+ // Accessors
+ Header &Head() {return *HeaderP;};
+ void PrintTree(map_ptrloc Base,unsigned long Size);
+
+ // Add/Find things
+ PkgIterator GetPkg(const char *Name,const char *End,bool Insert);
+ inline PkgIterator GetPkg(const char *Name,bool Insert);
+ NodeIterator GetNode(const char *Name,
+ const char *NameEnd,
+ map_ptrloc Loc,
+ bool Insert,bool Divert);
+ Node *HashNode(NodeIterator const &N);
+ void DropNode(map_ptrloc Node);
+
+ inline DiverIterator DiverBegin();
+
+ // Diversion control
+ void BeginDiverLoad();
+ void FinishDiverLoad();
+ bool AddDiversion(PkgIterator const &Owner,const char *From,
+ const char *To);
+ bool AddConfFile(const char *Name,const char *NameEnd,
+ PkgIterator const &Owner,const unsigned char *Sum);
+
+ pkgFLCache(DynamicMMap &Map);
+// ~pkgFLCache();
+};
+
+struct pkgFLCache::Header
+{
+ // Signature information
+ unsigned long Signature;
+ short MajorVersion;
+ short MinorVersion;
+ bool Dirty;
+
+ // Size of structure values
+ unsigned HeaderSz;
+ unsigned NodeSz;
+ unsigned DirSz;
+ unsigned PackageSz;
+ unsigned DiversionSz;
+ unsigned ConfFileSz;
+
+ // Structure Counts;
+ unsigned int NodeCount;
+ unsigned int DirCount;
+ unsigned int PackageCount;
+ unsigned int DiversionCount;
+ unsigned int ConfFileCount;
+ unsigned int HashSize;
+ unsigned long UniqNodes;
+
+ // Offsets
+ map_ptrloc FileHash;
+ map_ptrloc DirTree;
+ map_ptrloc Packages;
+ map_ptrloc Diversions;
+
+ /* Allocation pools, there should be one of these for each structure
+ excluding the header */
+ DynamicMMap::Pool Pools[5];
+
+ bool CheckSizes(Header &Against) const;
+ Header();
+};
+
+/* The bit field is used to advoid incurring an extra 4 bytes x 40000,
+ Pointer is the most infrequently used member of the structure */
+struct pkgFLCache::Node
+{
+ map_ptrloc Dir; // Dir
+ map_ptrloc File; // String
+ unsigned Pointer:24; // Package/Diversion/ConfFile
+ unsigned Flags:8; // Package
+ map_ptrloc Next; // Node
+ map_ptrloc NextPkg; // Node
+
+ enum Flags {Diversion = (1<<0),ConfFile = (1<<1),
+ NewConfFile = (1<<2),NewFile = (1<<3),
+ Unpacked = (1<<4),Replaced = (1<<5)};
+};
+
+struct pkgFLCache::Directory
+{
+ map_ptrloc Left; // Directory
+ map_ptrloc Right; // Directory
+ map_ptrloc Name; // String
+};
+
+struct pkgFLCache::Package
+{
+ map_ptrloc Left; // Package
+ map_ptrloc Right; // Package
+ map_ptrloc Name; // String
+ map_ptrloc Files; // Node
+};
+
+struct pkgFLCache::Diversion
+{
+ map_ptrloc OwnerPkg; // Package
+ map_ptrloc DivertFrom; // Node
+ map_ptrloc DivertTo; // String
+
+ map_ptrloc Next; // Diversion
+ unsigned long Flags;
+
+ enum Flags {Touched = (1<<0)};
+};
+
+struct pkgFLCache::ConfFile
+{
+ map_ptrloc OwnerPkg; // Package
+ unsigned char MD5[16];
+};
+
+class pkgFLCache::PkgIterator
+{
+ Package *Pkg;
+ pkgFLCache *Owner;
+
+ public:
+
+ inline bool end() const {return Owner == 0 || Pkg == Owner->PkgP?true:false;}
+
+ // Accessors
+ inline Package *operator ->() {return Pkg;}
+ inline Package const *operator ->() const {return Pkg;}
+ inline Package const &operator *() const {return *Pkg;}
+ inline operator Package *() {return Pkg == Owner->PkgP?0:Pkg;}
+ inline operator Package const *() const {return Pkg == Owner->PkgP?0:Pkg;}
+
+ inline unsigned long Offset() const {return Pkg - Owner->PkgP;}
+ inline const char *Name() const {return Pkg->Name == 0?0:Owner->StrP + Pkg->Name;}
+ inline pkgFLCache::NodeIterator Files() const;
+
+ PkgIterator() : Pkg(0), Owner(0) {}
+ PkgIterator(pkgFLCache &Owner,Package *Trg) : Pkg(Trg), Owner(&Owner) {}
+};
+
+class pkgFLCache::DirIterator
+{
+ Directory *Dir;
+ pkgFLCache *Owner;
+
+ public:
+
+ // Accessors
+ inline Directory *operator ->() {return Dir;}
+ inline Directory const *operator ->() const {return Dir;}
+ inline Directory const &operator *() const {return *Dir;}
+ inline operator Directory *() {return Dir == Owner->DirP?0:Dir;}
+ inline operator Directory const *() const {return Dir == Owner->DirP?0:Dir;}
+
+ inline const char *Name() const {return Dir->Name == 0?0:Owner->StrP + Dir->Name;}
+
+ DirIterator() : Dir(0), Owner(0) {}
+ DirIterator(pkgFLCache &Owner,Directory *Trg) : Dir(Trg), Owner(&Owner) {}
+};
+
+class pkgFLCache::DiverIterator
+{
+ Diversion *Diver;
+ pkgFLCache *Owner;
+
+ public:
+
+ // Iteration
+ void operator ++(int) {if (Diver != Owner->DiverP) Diver = Owner->DiverP + Diver->Next;}
+ inline void operator ++() {operator ++(0);}
+ inline bool end() const {return Owner == 0 || Diver == Owner->DiverP;}
+
+ // Accessors
+ inline Diversion *operator ->() {return Diver;}
+ inline Diversion const *operator ->() const {return Diver;}
+ inline Diversion const &operator *() const {return *Diver;}
+ inline operator Diversion *() {return Diver == Owner->DiverP?0:Diver;}
+ inline operator Diversion const *() const {return Diver == Owner->DiverP?0:Diver;}
+
+ inline PkgIterator OwnerPkg() const {return PkgIterator(*Owner,Owner->PkgP + Diver->OwnerPkg);}
+ inline NodeIterator DivertFrom() const;
+ inline NodeIterator DivertTo() const;
+
+ DiverIterator() : Diver(0), Owner(0) {};
+ DiverIterator(pkgFLCache &Owner,Diversion *Trg) : Diver(Trg), Owner(&Owner) {}
+};
+
+class pkgFLCache::NodeIterator
+{
+ Node *Nde;
+ enum {NdePkg, NdeHash} Type;
+ pkgFLCache *Owner;
+
+ public:
+
+ // Iteration
+ void operator ++(int) {if (Nde != Owner->NodeP) Nde = Owner->NodeP +
+ (Type == NdePkg?Nde->NextPkg:Nde->Next);}
+ inline void operator ++() {operator ++(0);}
+ inline bool end() const {return Owner == 0 || Nde == Owner->NodeP;}
+
+ // Accessors
+ inline Node *operator ->() {return Nde;}
+ inline Node const *operator ->() const {return Nde;}
+ inline Node const &operator *() const {return *Nde;}
+ inline operator Node *() {return Nde == Owner->NodeP?0:Nde;}
+ inline operator Node const *() const {return Nde == Owner->NodeP?0:Nde;}
+ inline unsigned long Offset() const {return Nde - Owner->NodeP;}
+ inline DirIterator Dir() const {return DirIterator(*Owner,Owner->DirP + Nde->Dir);}
+ inline DiverIterator Diversion() const {return DiverIterator(*Owner,Owner->DiverP + Nde->Pointer);}
+ inline const char *File() const {return Nde->File == 0?0:Owner->StrP + Nde->File;}
+ inline const char *DirN() const {return Owner->StrP + Owner->DirP[Nde->Dir].Name;}
+ Package *RealPackage() const;
+
+ NodeIterator() : Nde(0), Type(NdeHash), Owner(0) {};
+ NodeIterator(pkgFLCache &Owner) : Nde(Owner.NodeP), Type(NdeHash), Owner(&Owner) {}
+ NodeIterator(pkgFLCache &Owner,Node *Trg) : Nde(Trg), Type(NdeHash), Owner(&Owner) {}
+ NodeIterator(pkgFLCache &Owner,Node *Trg,Package *) : Nde(Trg), Type(NdePkg), Owner(&Owner) {}
+};
+
+/* Inlines with forward references that cannot be included directly in their
+ respsective classes */
+inline pkgFLCache::NodeIterator pkgFLCache::DiverIterator::DivertFrom() const
+ {return NodeIterator(*Owner,Owner->NodeP + Diver->DivertFrom);}
+inline pkgFLCache::NodeIterator pkgFLCache::DiverIterator::DivertTo() const
+ {return NodeIterator(*Owner,Owner->NodeP + Diver->DivertTo);}
+
+inline pkgFLCache::NodeIterator pkgFLCache::PkgIterator::Files() const
+ {return NodeIterator(*Owner,Owner->NodeP + Pkg->Files,Pkg);}
+
+inline pkgFLCache::DiverIterator pkgFLCache::DiverBegin()
+ {return DiverIterator(*this,DiverP + HeaderP->Diversions);}
+
+inline pkgFLCache::PkgIterator pkgFLCache::GetPkg(const char *Name,bool Insert)
+ {return GetPkg(Name,Name+strlen(Name),Insert);}
+
+#endif