]> git.saurik.com Git - apt.git/blobdiff - apt-pkg/tagfile.cc
test: Avoid use of /proc/self/fd
[apt.git] / apt-pkg / tagfile.cc
index 1c41f1265c206c0a9c77605ead7f94bfb3325298..3a3a3a04a5895044cc27f793d461ba07bc790446 100644 (file)
    ##################################################################### */
                                                                        /*}}}*/
 // Include Files                                                       /*{{{*/
+#include<config.h>
+
 #include <apt-pkg/tagfile.h>
 #include <apt-pkg/error.h>
 #include <apt-pkg/strutl.h>
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/string_view.h>
+
+#include <list>
 
-#include <apti18n.h>
-    
 #include <string>
 #include <stdio.h>
 #include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <apti18n.h>
                                                                        /*}}}*/
 
 using std::string;
+using APT::StringView;
 
-// TagFile::pkgTagFile - Constructor                                   /*{{{*/
-// ---------------------------------------------------------------------
-/* */
-pkgTagFile::pkgTagFile(FileFd *pFd,unsigned long Size) :
-     Fd(*pFd),
-     Size(Size)
+class APT_HIDDEN pkgTagFilePrivate                                     /*{{{*/
 {
-   if (Fd.IsOpen() == false)
+public:
+   void Reset(FileFd * const pFd, unsigned long long const pSize, pkgTagFile::Flags const pFlags)
    {
-      Buffer = 0;
-      Start = End = Buffer = 0;
-      Done = true;
+      if (Buffer != NULL)
+        free(Buffer);
+      Buffer = NULL;
+      Fd = pFd;
+      Flags = pFlags;
+      Start = NULL;
+      End = NULL;
+      Done = false;
       iOffset = 0;
-      return;
+      Size = pSize;
+      isCommentedLine = false;
+      chunks.clear();
    }
-   
-   Buffer = new char[Size];
-   Start = End = Buffer;
-   Done = false;
-   iOffset = 0;
-   Fill();
+
+   pkgTagFilePrivate(FileFd * const pFd, unsigned long long const Size, pkgTagFile::Flags const pFlags) : Buffer(NULL)
+   {
+      Reset(pFd, Size, pFlags);
+   }
+   FileFd * Fd;
+   pkgTagFile::Flags Flags;
+   char *Buffer;
+   char *Start;
+   char *End;
+   bool Done;
+   unsigned long long iOffset;
+   unsigned long long Size;
+   bool isCommentedLine;
+   struct FileChunk
+   {
+      bool const good;
+      size_t length;
+      FileChunk(bool const pgood, size_t const plength) : good(pgood), length(plength) {}
+   };
+   std::list<FileChunk> chunks;
+
+   ~pkgTagFilePrivate()
+   {
+      if (Buffer != NULL)
+        free(Buffer);
+   }
+};
+                                                                       /*}}}*/
+class APT_HIDDEN pkgTagSectionPrivate                                  /*{{{*/
+{
+public:
+   pkgTagSectionPrivate()
+   {
+   }
+   struct TagData {
+      unsigned int StartTag;
+      unsigned int EndTag;
+      unsigned int StartValue;
+      unsigned int NextInBucket;
+
+      explicit TagData(unsigned int const StartTag) : StartTag(StartTag), EndTag(0), StartValue(0), NextInBucket(0) {}
+   };
+   std::vector<TagData> Tags;
+};
+                                                                       /*}}}*/
+
+static unsigned long AlphaHash(const char *Text, size_t Length)                /*{{{*/
+{
+   /* This very simple hash function for the last 8 letters gives
+      very good performance on the debian package files */
+   if (Length > 8)
+   {
+    Text += (Length - 8);
+    Length = 8;
+   }
+   unsigned long Res = 0;
+   for (size_t i = 0; i < Length; ++i)
+      Res = ((unsigned long)(Text[i]) & 0xDF) ^ (Res << 1);
+   return Res & 0xFF;
+}
+                                                                       /*}}}*/
+
+// TagFile::pkgTagFile - Constructor                                   /*{{{*/
+pkgTagFile::pkgTagFile(FileFd * const pFd,pkgTagFile::Flags const pFlags, unsigned long long const Size)
+   : d(new pkgTagFilePrivate(pFd, Size + 4, pFlags))
+{
+   Init(pFd, pFlags, Size);
+}
+pkgTagFile::pkgTagFile(FileFd * const pFd,unsigned long long const Size)
+   : pkgTagFile(pFd, pkgTagFile::STRICT, Size)
+{
+}
+void pkgTagFile::Init(FileFd * const pFd, pkgTagFile::Flags const pFlags, unsigned long long Size)
+{
+   /* The size is increased by 4 because if we start with the Size of the
+      filename we need to try to read 1 char more to see an EOF faster, 1
+      char the end-pointer can be on and maybe 2 newlines need to be added
+      to the end of the file -> 4 extra chars */
+   Size += 4;
+   d->Reset(pFd, Size, pFlags);
+
+   if (d->Fd->IsOpen() == false)
+      d->Start = d->End = d->Buffer = 0;
+   else
+      d->Buffer = (char*)malloc(sizeof(char) * Size);
+
+   if (d->Buffer == NULL)
+      d->Done = true;
+   else
+      d->Done = false;
+
+   d->Start = d->End = d->Buffer;
+   d->iOffset = 0;
+   if (d->Done == false)
+      Fill();
+}
+void pkgTagFile::Init(FileFd * const pFd,unsigned long long Size)
+{
+   Init(pFd, pkgTagFile::STRICT, Size);
 }
                                                                        /*}}}*/
 // TagFile::~pkgTagFile - Destructor                                   /*{{{*/
-// ---------------------------------------------------------------------
-/* */
 pkgTagFile::~pkgTagFile()
 {
-   delete [] Buffer;
+   delete d;
+}
+                                                                       /*}}}*/
+// TagFile::Offset - Return the current offset in the buffer           /*{{{*/
+APT_PURE unsigned long pkgTagFile::Offset()
+{
+   return d->iOffset;
 }
                                                                        /*}}}*/
 // TagFile::Resize - Resize the internal buffer                                /*{{{*/
@@ -62,23 +172,26 @@ pkgTagFile::~pkgTagFile()
  */
 bool pkgTagFile::Resize()
 {
-   char *tmp;
-   unsigned long EndSize = End - Start;
-
    // fail is the buffer grows too big
-   if(Size > 1024*1024+1)
+   if(d->Size > 1024*1024+1)
       return false;
 
+   return Resize(d->Size * 2);
+}
+bool pkgTagFile::Resize(unsigned long long const newSize)
+{
+   unsigned long long const EndSize = d->End - d->Start;
+
    // get new buffer and use it
-   tmp = new char[2*Size];
-   memcpy(tmp, Buffer, Size);
-   Size = Size*2;
-   delete [] Buffer;
-   Buffer = tmp;
+   char* const newBuffer = static_cast<char*>(realloc(d->Buffer, sizeof(char) * newSize));
+   if (newBuffer == NULL)
+      return false;
+   d->Buffer = newBuffer;
+   d->Size = newSize;
 
    // update the start/end pointers to the new buffer
-   Start = Buffer;
-   End = Start + EndSize;
+   d->Start = d->Buffer;
+   d->End = d->Start + EndSize;
    return true;
 }
                                                                        /*}}}*/
@@ -90,20 +203,52 @@ bool pkgTagFile::Resize()
  */
 bool pkgTagFile::Step(pkgTagSection &Tag)
 {
-   while (Tag.Scan(Start,End - Start) == false)
+   if(Tag.Scan(d->Start,d->End - d->Start) == false)
    {
-      if (Fill() == false)
-        return false;
-      
-      if(Tag.Scan(Start,End - Start))
-        break;
+      do
+      {
+        if (Fill() == false)
+           return false;
+
+        if(Tag.Scan(d->Start,d->End - d->Start, false))
+           break;
+
+        if (Resize() == false)
+           return _error->Error(_("Unable to parse package file %s (%d)"),
+                 d->Fd->Name().c_str(), 1);
+
+      } while (Tag.Scan(d->Start,d->End - d->Start, false) == false);
+   }
+
+   size_t tagSize = Tag.size();
+   d->Start += tagSize;
 
-      if (Resize() == false)
-        return _error->Error(_("Unable to parse package file %s (1)"),
-                                Fd.Name().c_str());
+   if ((d->Flags & pkgTagFile::SUPPORT_COMMENTS) == 0)
+      d->iOffset += tagSize;
+   else
+   {
+      auto first = d->chunks.begin();
+      for (; first != d->chunks.end(); ++first)
+      {
+        if (first->good == false)
+           d->iOffset += first->length;
+        else
+        {
+           if (tagSize < first->length)
+           {
+              first->length -= tagSize;
+              d->iOffset += tagSize;
+              break;
+           }
+           else
+           {
+              tagSize -= first->length;
+              d->iOffset += first->length;
+           }
+        }
+      }
+      d->chunks.erase(d->chunks.begin(), first);
    }
-   Start += Tag.size();
-   iOffset += Tag.size();
 
    Tag.Trim();
    return true;
@@ -113,43 +258,166 @@ bool pkgTagFile::Step(pkgTagSection &Tag)
 // ---------------------------------------------------------------------
 /* This takes the bit at the end of the buffer and puts it at the start
    then fills the rest from the file */
+static bool FillBuffer(pkgTagFilePrivate * const d)
+{
+   unsigned long long Actual = 0;
+   // See if only a bit of the file is left
+   unsigned long long const dataSize = d->Size - ((d->End - d->Buffer) + 1);
+   if (d->Fd->Read(d->End, dataSize, &Actual) == false)
+      return false;
+   if (Actual != dataSize)
+      d->Done = true;
+   d->End += Actual;
+   return true;
+}
+static void RemoveCommentsFromBuffer(pkgTagFilePrivate * const d)
+{
+   // look for valid comments in the buffer
+   char * good_start = nullptr, * bad_start = nullptr;
+   char * current = d->Start;
+   if (d->isCommentedLine == false)
+   {
+      if (d->Start == d->Buffer)
+      {
+        // the start of the buffer is a newline as a record can't start
+        // in the middle of a line by definition.
+        if (*d->Start == '#')
+        {
+           d->isCommentedLine = true;
+           ++current;
+           if (current > d->End)
+              d->chunks.emplace_back(false, 1);
+        }
+      }
+      if (d->isCommentedLine == false)
+        good_start = d->Start;
+      else
+        bad_start = d->Start;
+   }
+   else
+      bad_start = d->Start;
+
+   std::vector<std::pair<char*, size_t>> good_parts;
+   while (current <= d->End)
+   {
+      size_t const restLength = (d->End - current) + 1;
+      if (d->isCommentedLine == false)
+      {
+        current = static_cast<char*>(memchr(current, '#', restLength));
+        if (current == nullptr)
+        {
+           size_t const goodLength = d->End - good_start;
+           d->chunks.emplace_back(true, goodLength);
+           if (good_start != d->Start)
+              good_parts.push_back(std::make_pair(good_start, goodLength));
+           break;
+        }
+        bad_start = current;
+        --current;
+        // ensure that this is really a comment and not a '#' in the middle of a line
+        if (*current == '\n')
+        {
+           size_t const goodLength = (current - good_start) + 1;
+           d->chunks.emplace_back(true, goodLength);
+           good_parts.push_back(std::make_pair(good_start, goodLength));
+           good_start = nullptr;
+           d->isCommentedLine = true;
+        }
+        current += 2;
+      }
+      else // the current line is a comment
+      {
+        current = static_cast<char*>(memchr(current, '\n', restLength));
+        if (current == nullptr)
+        {
+           d->chunks.emplace_back(false, (d->End - bad_start));
+           break;
+        }
+        ++current;
+        // is the next line a comment, too?
+        if (current > d->End || *current != '#')
+        {
+           d->chunks.emplace_back(false, (current - bad_start));
+           good_start = current;
+           bad_start = nullptr;
+           d->isCommentedLine = false;
+        }
+        ++current;
+      }
+   }
+
+   if (good_parts.empty() == false)
+   {
+      // we found comments, so move later parts over them
+      current = d->Start;
+      for (auto const &good: good_parts)
+      {
+        memmove(current, good.first, good.second);
+        current += good.second;
+      }
+      d->End = current;
+   }
+
+   if (d->isCommentedLine == true)
+   {
+      // deal with a buffer containing only comments
+      // or an (unfinished) comment at the end
+      if (good_parts.empty() == true)
+        d->End = d->Start;
+      else
+        d->Start = d->End;
+   }
+   else
+   {
+      // the buffer was all comment, but ended with the buffer
+      if (good_parts.empty() == true && good_start >= d->End)
+        d->End = d->Start;
+      else
+        d->Start = d->End;
+   }
+}
 bool pkgTagFile::Fill()
 {
-   unsigned long EndSize = End - Start;
-   unsigned long Actual = 0;
-   
-   memmove(Buffer,Start,EndSize);
-   Start = Buffer;
-   End = Buffer + EndSize;
-   
-   if (Done == false)
+   unsigned long long const EndSize = d->End - d->Start;
+   if (EndSize != 0)
    {
-      // See if only a bit of the file is left
-      if (Fd.Read(End,Size - (End - Buffer),&Actual) == false)
+      memmove(d->Buffer,d->Start,EndSize);
+      d->Start = d->End = d->Buffer + EndSize;
+   }
+   else
+      d->Start = d->End = d->Buffer;
+
+   unsigned long long Actual = 0;
+   while (d->Done == false && d->Size > (Actual + 1))
+   {
+      if (FillBuffer(d) == false)
         return false;
-      if (Actual != Size - (End - Buffer))
-        Done = true;
-      End += Actual;
+      if ((d->Flags & pkgTagFile::SUPPORT_COMMENTS) != 0)
+        RemoveCommentsFromBuffer(d);
+      Actual = d->End - d->Buffer;
    }
-   
-   if (Done == true)
+   d->Start = d->Buffer;
+
+   if (d->Done == true)
    {
       if (EndSize <= 3 && Actual == 0)
         return false;
-      if (Size - (End - Buffer) < 4)
+      if (d->Size - (d->End - d->Buffer) < 4)
         return true;
-      
+
       // Append a double new line if one does not exist
       unsigned int LineCount = 0;
-      for (const char *E = End - 1; E - End < 6 && (*E == '\n' || *E == '\r'); E--)
+      for (const char *E = d->End - 1; E - d->End < 6 && (*E == '\n' || *E == '\r'); E--)
         if (*E == '\n')
-           LineCount++;
-      for (; LineCount < 2; LineCount++)
-        *End++ = '\n';
-      
-      return true;
+           ++LineCount;
+      if (LineCount < 2)
+      {
+        if (static_cast<unsigned long long>(d->End - d->Buffer) >= d->Size)
+           Resize(d->Size + 3);
+        for (; LineCount < 2; ++LineCount)
+           *d->End++ = '\n';
+      }
    }
-   
    return true;
 }
                                                                        /*}}}*/
@@ -157,85 +425,161 @@ bool pkgTagFile::Fill()
 // ---------------------------------------------------------------------
 /* This jumps to a pre-recorded file location and reads the record
    that is there */
-bool pkgTagFile::Jump(pkgTagSection &Tag,unsigned long Offset)
+bool pkgTagFile::Jump(pkgTagSection &Tag,unsigned long long Offset)
 {
+   if ((d->Flags & pkgTagFile::SUPPORT_COMMENTS) == 0 &&
    // We are within a buffer space of the next hit..
-   if (Offset >= iOffset && iOffset + (End - Start) > Offset)
+        Offset >= d->iOffset && d->iOffset + (d->End - d->Start) > Offset)
    {
-      unsigned long Dist = Offset - iOffset;
-      Start += Dist;
-      iOffset += Dist;
-      return Step(Tag);
+      unsigned long long Dist = Offset - d->iOffset;
+      d->Start += Dist;
+      d->iOffset += Dist;
+      // if we have seen the end, don't ask for more
+      if (d->Done == true)
+        return Tag.Scan(d->Start, d->End - d->Start);
+      else
+        return Step(Tag);
    }
 
    // Reposition and reload..
-   iOffset = Offset;
-   Done = false;
-   if (Fd.Seek(Offset) == false)
+   d->iOffset = Offset;
+   d->Done = false;
+   if (d->Fd->Seek(Offset) == false)
       return false;
-   End = Start = Buffer;
-   
+   d->End = d->Start = d->Buffer;
+   d->isCommentedLine = false;
+   d->chunks.clear();
+
    if (Fill() == false)
       return false;
 
-   if (Tag.Scan(Start,End - Start) == true)
+   if (Tag.Scan(d->Start, d->End - d->Start) == true)
       return true;
    
    // This appends a double new line (for the real eof handling)
    if (Fill() == false)
       return false;
    
-   if (Tag.Scan(Start,End - Start) == false)
-      return _error->Error(_("Unable to parse package file %s (2)"),Fd.Name().c_str());
+   if (Tag.Scan(d->Start, d->End - d->Start, false) == false)
+      return _error->Error(_("Unable to parse package file %s (%d)"),d->Fd->Name().c_str(), 2);
    
    return true;
 }
                                                                        /*}}}*/
-// TagSection::Scan - Scan for the end of the header information       /*{{{*/
+// pkgTagSection::pkgTagSection - Constructor                          /*{{{*/
 // ---------------------------------------------------------------------
-/* This looks for the first double new line in the data stream. It also
-   indexes the tags in the section. This very simple hash function for the
-   last 8 letters gives very good performance on the debian package files */
-inline static unsigned long AlphaHash(const char *Text, const char *End = 0)
+/* */
+APT_IGNORE_DEPRECATED_PUSH
+pkgTagSection::pkgTagSection()
+   : Section(0), d(new pkgTagSectionPrivate()), Stop(0)
 {
-   unsigned long Res = 0;
-   for (; Text != End && *Text != ':' && *Text != 0; Text++)
-      Res = ((unsigned long)(*Text) & 0xDF) ^ (Res << 1);
-   return Res & 0xFF;
+   memset(&AlphaIndexes, 0, sizeof(AlphaIndexes));
 }
-
-bool pkgTagSection::Scan(const char *Start,unsigned long MaxLength)
+APT_IGNORE_DEPRECATED_POP
+                                                                       /*}}}*/
+// TagSection::Scan - Scan for the end of the header information       /*{{{*/
+bool pkgTagSection::Scan(const char *Start,unsigned long MaxLength, bool const Restart)
 {
+   Section = Start;
    const char *End = Start + MaxLength;
-   Stop = Section = Start;
-   memset(AlphaIndexes,0,sizeof(AlphaIndexes));
+
+   if (Restart == false && d->Tags.empty() == false)
+   {
+      Stop = Section + d->Tags.back().StartTag;
+      if (End <= Stop)
+        return false;
+      Stop = (const char *)memchr(Stop,'\n',End - Stop);
+      if (Stop == NULL)
+        return false;
+      ++Stop;
+   }
+   else
+   {
+      Stop = Section;
+      if (d->Tags.empty() == false)
+      {
+        memset(&AlphaIndexes, 0, sizeof(AlphaIndexes));
+        d->Tags.clear();
+      }
+      d->Tags.reserve(0x100);
+   }
+   unsigned int TagCount = d->Tags.size();
 
    if (Stop == 0)
       return false;
 
-   TagCount = 0;
-   while (TagCount+1 < sizeof(Indexes)/sizeof(Indexes[0]) && Stop < End)
+   pkgTagSectionPrivate::TagData lastTagData(0);
+   lastTagData.EndTag = 0;
+   unsigned long lastTagHash = 0;
+   while (Stop < End)
    {
-       TrimRecord(true,End);
+      TrimRecord(true,End);
+
+      // this can happen when TrimRecord trims away the entire Record
+      // (e.g. because it just contains comments)
+      if(Stop == End)
+         return true;
 
       // Start a new index and add it to the hash
-      if (isspace(Stop[0]) == 0)
+      if (isspace_ascii(Stop[0]) == 0)
       {
-        Indexes[TagCount++] = Stop - Section;
-        AlphaIndexes[AlphaHash(Stop,End)] = TagCount;
+        // store the last found tag
+        if (lastTagData.EndTag != 0)
+        {
+           if (AlphaIndexes[lastTagHash] != 0)
+              lastTagData.NextInBucket = AlphaIndexes[lastTagHash];
+           APT_IGNORE_DEPRECATED_PUSH
+           AlphaIndexes[lastTagHash] = TagCount;
+           APT_IGNORE_DEPRECATED_POP
+           d->Tags.push_back(lastTagData);
+        }
+
+        APT_IGNORE_DEPRECATED(++TagCount;)
+        lastTagData = pkgTagSectionPrivate::TagData(Stop - Section);
+        // find the colon separating tag and value
+        char const * Colon = (char const *) memchr(Stop, ':', End - Stop);
+        if (Colon == NULL)
+           return false;
+        // find the end of the tag (which might or might not be the colon)
+        char const * EndTag = Colon;
+        --EndTag;
+        for (; EndTag > Stop && isspace_ascii(*EndTag) != 0; --EndTag)
+           ;
+        ++EndTag;
+        lastTagData.EndTag = EndTag - Section;
+        lastTagHash = AlphaHash(Stop, EndTag - Stop);
+        // find the beginning of the value
+        Stop = Colon + 1;
+        for (; Stop < End && isspace_ascii(*Stop) != 0; ++Stop)
+           if (*Stop == '\n' && Stop[1] != ' ')
+              break;
+        if (Stop >= End)
+           return false;
+        lastTagData.StartValue = Stop - Section;
       }
 
       Stop = (const char *)memchr(Stop,'\n',End - Stop);
-      
+
       if (Stop == 0)
         return false;
 
-      for (; Stop+1 < End && Stop[1] == '\r'; Stop++);
+      for (; Stop+1 < End && Stop[1] == '\r'; Stop++)
+         /* nothing */
+         ;
 
       // Double newline marks the end of the record
       if (Stop+1 < End && Stop[1] == '\n')
       {
-        Indexes[TagCount] = Stop - Section;
+        if (lastTagData.EndTag != 0)
+        {
+           if (AlphaIndexes[lastTagHash] != 0)
+              lastTagData.NextInBucket = AlphaIndexes[lastTagHash];
+           APT_IGNORE_DEPRECATED(AlphaIndexes[lastTagHash] = TagCount;)
+           d->Tags.push_back(lastTagData);
+        }
+
+        pkgTagSectionPrivate::TagData const td(Stop - Section);
+        d->Tags.push_back(td);
         TrimRecord(false,End);
         return true;
       }
@@ -264,96 +608,91 @@ void pkgTagSection::Trim()
    for (; Stop > Section + 2 && (Stop[-2] == '\n' || Stop[-2] == '\r'); Stop--);
 }
                                                                        /*}}}*/
+// TagSection::Exists - return True if a tag exists                    /*{{{*/
+bool pkgTagSection::Exists(StringView Tag) const
+{
+   unsigned int tmp;
+   return Find(Tag, tmp);
+}
+                                                                       /*}}}*/
 // TagSection::Find - Locate a tag                                     /*{{{*/
 // ---------------------------------------------------------------------
 /* This searches the section for a tag that matches the given string. */
-bool pkgTagSection::Find(const char *Tag,unsigned &Pos) const
+bool pkgTagSection::Find(StringView TagView,unsigned int &Pos) const
 {
-   unsigned int Length = strlen(Tag);
-   unsigned int I = AlphaIndexes[AlphaHash(Tag)];
-   if (I == 0)
+   const char * const Tag = TagView.data();
+   size_t const Length = TagView.length();
+   unsigned int Bucket = AlphaIndexes[AlphaHash(Tag, Length)];
+   if (Bucket == 0)
       return false;
-   I--;
-   
-   for (unsigned int Counter = 0; Counter != TagCount; Counter++, 
-       I = (I+1)%TagCount)
+
+   for (; Bucket != 0; Bucket = d->Tags[Bucket - 1].NextInBucket)
    {
-      const char *St;
-      St = Section + Indexes[I];
-      if (strncasecmp(Tag,St,Length) != 0)
+      if ((d->Tags[Bucket - 1].EndTag - d->Tags[Bucket - 1].StartTag) != Length)
         continue;
 
-      // Make sure the colon is in the right place
-      const char *C = St + Length;
-      for (; isspace(*C) != 0; C++);
-      if (*C != ':')
+      char const * const St = Section + d->Tags[Bucket - 1].StartTag;
+      if (strncasecmp(Tag,St,Length) != 0)
         continue;
-      Pos = I;
+
+      Pos = Bucket - 1;
       return true;
    }
 
    Pos = 0;
    return false;
 }
-                                                                       /*}}}*/
-// TagSection::Find - Locate a tag                                     /*{{{*/
-// ---------------------------------------------------------------------
-/* This searches the section for a tag that matches the given string. */
-bool pkgTagSection::Find(const char *Tag,const char *&Start,
+
+bool pkgTagSection::Find(StringView Tag,const char *&Start,
                         const char *&End) const
 {
-   unsigned int Length = strlen(Tag);
-   unsigned int I = AlphaIndexes[AlphaHash(Tag)];
-   if (I == 0)
+   unsigned int Pos;
+   if (Find(Tag, Pos) == false)
       return false;
-   I--;
-   
-   for (unsigned int Counter = 0; Counter != TagCount; Counter++, 
-       I = (I+1)%TagCount)
-   {
-      const char *St;
-      St = Section + Indexes[I];
-      if (strncasecmp(Tag,St,Length) != 0)
-        continue;
-      
-      // Make sure the colon is in the right place
-      const char *C = St + Length;
-      for (; isspace(*C) != 0; C++);
-      if (*C != ':')
-        continue;
 
-      // Strip off the gunk from the start end
-      Start = C;
-      End = Section + Indexes[I+1];
-      if (Start >= End)
-        return _error->Error("Internal parsing error");
-      
-      for (; (isspace(*Start) != 0 || *Start == ':') && Start < End; Start++);
-      for (; isspace(End[-1]) != 0 && End > Start; End--);
-      
-      return true;
-   }
-   
-   Start = End = 0;
-   return false;
+   Start = Section + d->Tags[Pos].StartValue;
+   // Strip off the gunk from the end
+   End = Section + d->Tags[Pos + 1].StartTag;
+   if (unlikely(Start > End))
+      return _error->Error("Internal parsing error");
+
+   for (; isspace_ascii(End[-1]) != 0 && End > Start; --End);
+
+   return true;
 }
                                                                        /*}}}*/
 // TagSection::FindS - Find a string                                   /*{{{*/
-// ---------------------------------------------------------------------
-/* */
-string pkgTagSection::FindS(const char *Tag) const
+StringView pkgTagSection::Find(StringView Tag) const
 {
    const char *Start;
    const char *End;
    if (Find(Tag,Start,End) == false)
-      return string();
-   return string(Start,End);      
+      return StringView();
+   return StringView(Start, End - Start);
+}
+                                                                       /*}}}*/
+// TagSection::FindRawS - Find a string                                        /*{{{*/
+StringView pkgTagSection::FindRaw(StringView Tag) const
+{
+   unsigned int Pos;
+   if (Find(Tag, Pos) == false)
+      return "";
+
+   char const *Start = (char const *) memchr(Section + d->Tags[Pos].EndTag, ':', d->Tags[Pos].StartValue - d->Tags[Pos].EndTag);
+   ++Start;
+   char const *End = Section + d->Tags[Pos + 1].StartTag;
+   if (unlikely(Start > End))
+      return "";
+
+   for (; isspace_ascii(End[-1]) != 0 && End > Start; --End);
+
+   return StringView(Start, End - Start);
 }
                                                                        /*}}}*/
 // TagSection::FindI - Find an integer                                 /*{{{*/
 // ---------------------------------------------------------------------
 /* */
-signed int pkgTagSection::FindI(const char *Tag,signed long Default) const
+signed int pkgTagSection::FindI(StringView Tag,signed long Default) const
 {
    const char *Start;
    const char *Stop;
@@ -366,27 +705,100 @@ signed int pkgTagSection::FindI(const char *Tag,signed long Default) const
       return Default;
    strncpy(S,Start,Stop-Start);
    S[Stop - Start] = 0;
-   
+
+   errno = 0;
    char *End;
    signed long Result = strtol(S,&End,10);
+   if (errno == ERANGE ||
+       Result < std::numeric_limits<int>::min() || Result > std::numeric_limits<int>::max()) {
+      errno = ERANGE;
+      _error->Error(_("Cannot convert %s to integer: out of range"), S);
+   }
+   if (S == End)
+      return Default;
+   return Result;
+}
+                                                                       /*}}}*/
+// TagSection::FindULL - Find an unsigned long long integer            /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+unsigned long long pkgTagSection::FindULL(StringView Tag, unsigned long long const &Default) const
+{
+   const char *Start;
+   const char *Stop;
+   if (Find(Tag,Start,Stop) == false)
+      return Default;
+
+   // Copy it into a temp buffer so we can use strtoull
+   char S[100];
+   if ((unsigned)(Stop - Start) >= sizeof(S))
+      return Default;
+   strncpy(S,Start,Stop-Start);
+   S[Stop - Start] = 0;
+   
+   char *End;
+   unsigned long long Result = strtoull(S,&End,10);
    if (S == End)
       return Default;
    return Result;
 }
                                                                        /*}}}*/
+// TagSection::FindB - Find boolean value                              /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool pkgTagSection::FindB(StringView Tag, bool Default) const
+{
+   const char *Start, *Stop;
+   if (Find(Tag, Start, Stop) == false)
+      return Default;
+   return StringToBool(string(Start, Stop));
+}
+                                                                       /*}}}*/
 // TagSection::FindFlag - Locate a yes/no type flag                    /*{{{*/
 // ---------------------------------------------------------------------
 /* The bits marked in Flag are masked on/off in Flags */
-bool pkgTagSection::FindFlag(const char *Tag,unsigned long &Flags,
+bool pkgTagSection::FindFlag(StringView Tag, uint8_t &Flags,
+                            uint8_t const Flag) const
+{
+   const char *Start;
+   const char *Stop;
+   if (Find(Tag,Start,Stop) == false)
+      return true;
+   return FindFlag(Flags, Flag, Start, Stop);
+}
+bool pkgTagSection::FindFlag(uint8_t &Flags, uint8_t const Flag,
+                                       char const* const Start, char const* const Stop)
+{
+   switch (StringToBool(string(Start, Stop)))
+   {
+      case 0:
+      Flags &= ~Flag;
+      return true;
+
+      case 1:
+      Flags |= Flag;
+      return true;
+
+      default:
+      _error->Warning("Unknown flag value: %s",string(Start,Stop).c_str());
+      return true;
+   }
+   return true;
+}
+bool pkgTagSection::FindFlag(StringView Tag,unsigned long &Flags,
                             unsigned long Flag) const
 {
    const char *Start;
    const char *Stop;
    if (Find(Tag,Start,Stop) == false)
       return true;
-   
-   switch (StringToBool(string(Start,Stop)))
-   {     
+   return FindFlag(Flags, Flag, Start, Stop);
+}
+bool pkgTagSection::FindFlag(unsigned long &Flags, unsigned long Flag,
+                                       char const* Start, char const* Stop)
+{
+   switch (StringToBool(string(Start, Stop)))
+   {
       case 0:
       Flags &= ~Flag;
       return true;
@@ -402,70 +814,162 @@ bool pkgTagSection::FindFlag(const char *Tag,unsigned long &Flags,
    return true;
 }
                                                                        /*}}}*/
+void pkgTagSection::Get(const char *&Start,const char *&Stop,unsigned int I) const/*{{{*/
+{
+   Start = Section + d->Tags[I].StartTag;
+   Stop = Section + d->Tags[I+1].StartTag;
+}
+                                                                       /*}}}*/
+APT_PURE unsigned int pkgTagSection::Count() const {                   /*{{{*/
+   if (d->Tags.empty() == true)
+      return 0;
+   // the last element is just marking the end and isn't a real one
+   return d->Tags.size() - 1;
+}
+                                                                       /*}}}*/
+// TagSection::Write - Ordered (re)writing of fields                   /*{{{*/
+pkgTagSection::Tag pkgTagSection::Tag::Remove(std::string const &Name)
+{
+   return Tag(REMOVE, Name, "");
+}
+pkgTagSection::Tag pkgTagSection::Tag::Rename(std::string const &OldName, std::string const &NewName)
+{
+   return Tag(RENAME, OldName, NewName);
+}
+pkgTagSection::Tag pkgTagSection::Tag::Rewrite(std::string const &Name, std::string const &Data)
+{
+   if (Data.empty() == true)
+      return Tag(REMOVE, Name, "");
+   else
+      return Tag(REWRITE, Name, Data);
+}
+static bool WriteTag(FileFd &File, std::string Tag, StringView Value)
+{
+   if (Value.empty() || isspace_ascii(Value[0]) != 0)
+      Tag.append(":");
+   else
+      Tag.append(": ");
+   Tag.append(Value.data(), Value.length());
+   Tag.append("\n");
+   return File.Write(Tag.c_str(), Tag.length());
+}
+static bool RewriteTags(FileFd &File, pkgTagSection const * const This, char const * const Tag,
+      std::vector<pkgTagSection::Tag>::const_iterator &R,
+      std::vector<pkgTagSection::Tag>::const_iterator const &REnd)
+{
+   size_t const TagLen = strlen(Tag);
+   for (; R != REnd; ++R)
+   {
+      std::string data;
+      if (R->Name.length() == TagLen && strncasecmp(R->Name.c_str(), Tag, R->Name.length()) == 0)
+      {
+        if (R->Action != pkgTagSection::Tag::REWRITE)
+           break;
+        data = R->Data;
+      }
+      else if(R->Action == pkgTagSection::Tag::RENAME && R->Data.length() == TagLen &&
+           strncasecmp(R->Data.c_str(), Tag, R->Data.length()) == 0)
+        data = This->FindRaw(R->Name.c_str()).to_string();
+      else
+        continue;
+
+      return WriteTag(File, Tag, data);
+   }
+   return true;
+}
+bool pkgTagSection::Write(FileFd &File, char const * const * const Order, std::vector<Tag> const &Rewrite) const
+{
+   // first pass: Write everything we have an order for
+   if (Order != NULL)
+   {
+      for (unsigned int I = 0; Order[I] != 0; ++I)
+      {
+        std::vector<Tag>::const_iterator R = Rewrite.begin();
+        if (RewriteTags(File, this, Order[I], R, Rewrite.end()) == false)
+           return false;
+        if (R != Rewrite.end())
+           continue;
+
+        if (Exists(Order[I]) == false)
+           continue;
+
+        if (WriteTag(File, Order[I], FindRaw(Order[I])) == false)
+           return false;
+      }
+   }
+   // second pass: See if we have tags which aren't ordered
+   if (d->Tags.empty() == false)
+   {
+      for (std::vector<pkgTagSectionPrivate::TagData>::const_iterator T = d->Tags.begin(); T != d->Tags.end() - 1; ++T)
+      {
+        char const * const fieldname = Section + T->StartTag;
+        size_t fieldnamelen = T->EndTag - T->StartTag;
+        if (Order != NULL)
+        {
+           unsigned int I = 0;
+           for (; Order[I] != 0; ++I)
+           {
+              if (fieldnamelen == strlen(Order[I]) && strncasecmp(fieldname, Order[I], fieldnamelen) == 0)
+                 break;
+           }
+           if (Order[I] != 0)
+              continue;
+        }
+
+        std::string const name(fieldname, fieldnamelen);
+        std::vector<Tag>::const_iterator R = Rewrite.begin();
+        if (RewriteTags(File, this, name.c_str(), R, Rewrite.end()) == false)
+           return false;
+        if (R != Rewrite.end())
+           continue;
+
+        if (WriteTag(File, name, FindRaw(name)) == false)
+           return false;
+      }
+   }
+   // last pass: see if there are any rewrites remaining we haven't done yet
+   for (std::vector<Tag>::const_iterator R = Rewrite.begin(); R != Rewrite.end(); ++R)
+   {
+      if (R->Action == Tag::REMOVE)
+        continue;
+      std::string const name = ((R->Action == Tag::RENAME) ? R->Data : R->Name);
+      if (Exists(name.c_str()))
+        continue;
+      if (Order != NULL)
+      {
+        unsigned int I = 0;
+        for (; Order[I] != 0; ++I)
+        {
+           if (strncasecmp(name.c_str(), Order[I], name.length()) == 0 && name.length() == strlen(Order[I]))
+              break;
+        }
+        if (Order[I] != 0)
+           continue;
+      }
+
+      if (WriteTag(File, name, ((R->Action == Tag::RENAME) ? FindRaw(R->Name) : R->Data)) == false)
+        return false;
+   }
+   return true;
+}
+                                                                       /*}}}*/
+
+void pkgUserTagSection::TrimRecord(bool /*BeforeRecord*/, const char* &End)/*{{{*/
+{
+   for (; Stop < End && (Stop[0] == '\n' || Stop[0] == '\r' || Stop[0] == '#'); Stop++)
+      if (Stop[0] == '#')
+        Stop = (const char*) memchr(Stop,'\n',End-Stop);
+}
+                                                                       /*}}}*/
+
+#include "tagfile-order.c"
+
 // TFRewrite - Rewrite a control record                                        /*{{{*/
 // ---------------------------------------------------------------------
 /* This writes the control record to stdout rewriting it as necessary. The
    override map item specificies the rewriting rules to follow. This also
    takes the time to sort the feild list. */
-
-/* The order of this list is taken from dpkg source lib/parse.c the fieldinfos
-   array. */
-static const char *iTFRewritePackageOrder[] = {
-                          "Package",
-                          "Essential",
-                          "Status",
-                          "Priority",
-                          "Section",
-                          "Installed-Size",
-                          "Maintainer",
-                          "Original-Maintainer",
-                          "Architecture",
-                          "Source",
-                          "Version",
-                           "Revision",         // Obsolete
-                           "Config-Version",   // Obsolete
-                          "Replaces",
-                          "Provides",
-                          "Depends",
-                          "Pre-Depends",
-                          "Recommends",
-                          "Suggests",
-                          "Conflicts",
-                          "Breaks",
-                          "Conffiles",
-                          "Filename",
-                          "Size",
-                          "MD5Sum",
-                          "SHA1",
-                          "SHA256",
-                           "MSDOS-Filename",   // Obsolete
-                          "Description",
-                          0};
-static const char *iTFRewriteSourceOrder[] = {"Package",
-                                      "Source",
-                                      "Binary",
-                                      "Version",
-                                      "Priority",
-                                      "Section",
-                                      "Maintainer",
-                                     "Original-Maintainer",
-                                      "Build-Depends",
-                                      "Build-Depends-Indep",
-                                      "Build-Conflicts",
-                                      "Build-Conflicts-Indep",
-                                      "Architecture",
-                                      "Standards-Version",
-                                      "Format",
-                                      "Directory",
-                                      "Files",
-                                      0};   
-
-/* Two levels of initialization are used because gcc will set the symbol
-   size of an array to the length of the array, causing dynamic relinking 
-   errors. Doing this makes the symbol size constant */
-const char **TFRewritePackageOrder = iTFRewritePackageOrder;
-const char **TFRewriteSourceOrder = iTFRewriteSourceOrder;
-   
+APT_IGNORE_DEPRECATED_PUSH
 bool TFRewrite(FILE *Output,pkgTagSection const &Tags,const char *Order[],
               TFRewriteData *Rewrite)
 {
@@ -481,52 +985,54 @@ bool TFRewrite(FILE *Output,pkgTagSection const &Tags,const char *Order[],
    }
    
    // Write all all of the tags, in order.
-   for (unsigned int I = 0; Order[I] != 0; I++)
+   if (Order != NULL)
    {
-      bool Rewritten = false;
-      
-      // See if this is a field that needs to be rewritten
-      for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
+      for (unsigned int I = 0; Order[I] != 0; I++)
       {
-        if (strcasecmp(Rewrite[J].Tag,Order[I]) == 0)
-        {
-           Visited[J] |= 2;
-           if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0)
-           {
-              if (isspace(Rewrite[J].Rewrite[0]))
-                 fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
-              else
-                 fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
-           }
-           
-           Rewritten = true;
-           break;
-        }
-      }      
+         bool Rewritten = false;
+         
+         // See if this is a field that needs to be rewritten
+         for (unsigned int J = 0; Rewrite != 0 && Rewrite[J].Tag != 0; J++)
+         {
+            if (strcasecmp(Rewrite[J].Tag,Order[I]) == 0)
+            {
+               Visited[J] |= 2;
+               if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0)
+               {
+                  if (isspace_ascii(Rewrite[J].Rewrite[0]))
+                     fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
+                  else
+                     fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
+               }
+               Rewritten = true;
+               break;
+            }
+         }
            
-      // See if it is in the fragment
-      unsigned Pos;
-      if (Tags.Find(Order[I],Pos) == false)
-        continue;
-      Visited[Pos] |= 1;
+         // See if it is in the fragment
+         unsigned Pos;
+         if (Tags.Find(StringView(Order[I]),Pos) == false)
+            continue;
+         Visited[Pos] |= 1;
 
-      if (Rewritten == true)
-        continue;
+         if (Rewritten == true)
+            continue;
       
-      /* Write out this element, taking a moment to rewrite the tag
-         in case of changes of case. */
-      const char *Start;
-      const char *Stop;
-      Tags.Get(Start,Stop,Pos);
+         /* Write out this element, taking a moment to rewrite the tag
+            in case of changes of case. */
+         const char *Start;
+         const char *Stop;
+         Tags.Get(Start,Stop,Pos);
       
-      if (fputs(Order[I],Output) < 0)
-        return _error->Errno("fputs","IO Error to output");
-      Start += strlen(Order[I]);
-      if (fwrite(Start,Stop - Start,1,Output) != 1)
-        return _error->Errno("fwrite","IO Error to output");
-      if (Stop[-1] != '\n')
-        fprintf(Output,"\n");
-   }   
+         if (fputs(Order[I],Output) < 0)
+            return _error->Errno("fputs","IO Error to output");
+         Start += strlen(Order[I]);
+         if (fwrite(Start,Stop - Start,1,Output) != 1)
+            return _error->Errno("fwrite","IO Error to output");
+         if (Stop[-1] != '\n')
+            fprintf(Output,"\n");
+      }
+   }
 
    // Now write all the old tags that were missed.
    for (unsigned int I = 0; I != Tags.Count(); I++)
@@ -549,7 +1055,7 @@ bool TFRewrite(FILE *Output,pkgTagSection const &Tags,const char *Order[],
            Visited[J] |= 2;
            if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0)
            {
-              if (isspace(Rewrite[J].Rewrite[0]))
+              if (isspace_ascii(Rewrite[J].Rewrite[0]))
                  fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
               else
                  fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
@@ -578,7 +1084,7 @@ bool TFRewrite(FILE *Output,pkgTagSection const &Tags,const char *Order[],
       
       if (Rewrite[J].Rewrite != 0 && Rewrite[J].Rewrite[0] != 0)
       {
-        if (isspace(Rewrite[J].Rewrite[0]))
+        if (isspace_ascii(Rewrite[J].Rewrite[0]))
            fprintf(Output,"%s:%s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
         else
            fprintf(Output,"%s: %s\n",Rewrite[J].NewTag,Rewrite[J].Rewrite);
@@ -587,4 +1093,7 @@ bool TFRewrite(FILE *Output,pkgTagSection const &Tags,const char *Order[],
       
    return true;
 }
+APT_IGNORE_DEPRECATED_POP
                                                                        /*}}}*/
+
+pkgTagSection::~pkgTagSection() { delete d; }