]> git.saurik.com Git - apt.git/blobdiff - apt-pkg/contrib/fileutl.cc
FileFd(): Drop file name extension check in ReadOnlyGzip mode
[apt.git] / apt-pkg / contrib / fileutl.cc
index 0ce0c9b9da55a4f5f48542c3191fbed63475d40c..0d2d3f356f48cd2449a8983ee4a421a87f63233b 100644 (file)
@@ -8,21 +8,26 @@
    CopyFile - Buffered copy of a single file
    GetLock - dpkg compatible lock file manipulation (fcntl)
    
-   This source is placed in the Public Domain, do with it what you will
+   Most of this source is placed in the Public Domain, do with it what 
+   you will
    It was originally written by Jason Gunthorpe <jgg@debian.org>.
+   FileFd gzip support added by Martin Pitt <martin.pitt@canonical.com>
    
+   The exception is RunScripts() it is under the GPLv2
+
    ##################################################################### */
                                                                        /*}}}*/
 // Include Files                                                       /*{{{*/
-#ifdef __GNUG__
-#pragma implementation "apt-pkg/fileutl.h"
-#endif 
 #include <apt-pkg/fileutl.h>
 #include <apt-pkg/error.h>
 #include <apt-pkg/sptr.h>
+#include <apt-pkg/configuration.h>
 
 #include <apti18n.h>
 
+#include <cstdlib>
+#include <cstring>
+
 #include <iostream>
 #include <unistd.h>
 #include <fcntl.h>
 #include <sys/types.h>
 #include <sys/time.h>
 #include <sys/wait.h>
+#include <dirent.h>
 #include <signal.h>
 #include <errno.h>
+#include <set>
+#include <algorithm>
                                                                        /*}}}*/
 
 using namespace std;
 
+// RunScripts - Run a set of scripts from a configuration subtree      /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool RunScripts(const char *Cnf)
+{
+   Configuration::Item const *Opts = _config->Tree(Cnf);
+   if (Opts == 0 || Opts->Child == 0)
+      return true;
+   Opts = Opts->Child;
+
+   // Fork for running the system calls
+   pid_t Child = ExecFork();
+   
+   // This is the child
+   if (Child == 0)
+   {
+      if (chdir("/tmp/") != 0)
+        _exit(100);
+        
+      unsigned int Count = 1;
+      for (; Opts != 0; Opts = Opts->Next, Count++)
+      {
+        if (Opts->Value.empty() == true)
+           continue;
+        
+        if (system(Opts->Value.c_str()) != 0)
+           _exit(100+Count);
+      }
+      _exit(0);
+   }      
+
+   // Wait for the child
+   int Status = 0;
+   while (waitpid(Child,&Status,0) != Child)
+   {
+      if (errno == EINTR)
+        continue;
+      return _error->Errno("waitpid","Couldn't wait for subprocess");
+   }
+
+   // Restore sig int/quit
+   signal(SIGQUIT,SIG_DFL);
+   signal(SIGINT,SIG_DFL);   
+
+   // Check for an error code.
+   if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
+   {
+      unsigned int Count = WEXITSTATUS(Status);
+      if (Count > 100)
+      {
+        Count -= 100;
+        for (; Opts != 0 && Count != 1; Opts = Opts->Next, Count--);
+        _error->Error("Problem executing scripts %s '%s'",Cnf,Opts->Value.c_str());
+      }
+      
+      return _error->Error("Sub-process returned an error code");
+   }
+   
+   return true;
+}
+                                                                       /*}}}*/
+
 // CopyFile - Buffered copy of a file                                  /*{{{*/
 // ---------------------------------------------------------------------
 /* The caller is expected to set things so that failure causes erasure */
@@ -71,7 +141,9 @@ bool CopyFile(FileFd &From,FileFd &To)
    close at some time. */
 int GetLock(string File,bool Errors)
 {
-   int FD = open(File.c_str(),O_RDWR | O_CREAT | O_TRUNC,0640);
+   // GetLock() is used in aptitude on directories with public-write access
+   // Use O_NOFOLLOW here to prevent symlink traversal attacks
+   int FD = open(File.c_str(),O_RDWR | O_CREAT | O_NOFOLLOW,0640);
    if (FD < 0)
    {
       // Read only .. cant have locking problems there.
@@ -126,6 +198,128 @@ bool FileExists(string File)
    return true;
 }
                                                                        /*}}}*/
+// GetListOfFilesInDir - returns a vector of files in the given dir    /*{{{*/
+// ---------------------------------------------------------------------
+/* If an extension is given only files with this extension are included
+   in the returned vector, otherwise every "normal" file is included. */
+std::vector<string> GetListOfFilesInDir(string const &Dir, string const &Ext,
+                                       bool const &SortList)
+{
+   return GetListOfFilesInDir(Dir, Ext, SortList, false);
+}
+std::vector<string> GetListOfFilesInDir(string const &Dir, string const &Ext,
+                                       bool const &SortList, bool const &AllowNoExt)
+{
+   std::vector<string> ext;
+   ext.reserve(2);
+   if (Ext.empty() == false)
+      ext.push_back(Ext);
+   if (AllowNoExt == true && ext.empty() == false)
+      ext.push_back("");
+   return GetListOfFilesInDir(Dir, ext, SortList);
+}
+std::vector<string> GetListOfFilesInDir(string const &Dir, std::vector<string> const &Ext,
+                                       bool const &SortList)
+{
+   // Attention debuggers: need to be set with the environment config file!
+   bool const Debug = _config->FindB("Debug::GetListOfFilesInDir", false);
+   if (Debug == true)
+   {
+      std::clog << "Accept in " << Dir << " only files with the following " << Ext.size() << " extensions:" << std::endl;
+      if (Ext.empty() == true)
+        std::clog << "\tNO extension" << std::endl;
+      else
+        for (std::vector<string>::const_iterator e = Ext.begin();
+             e != Ext.end(); ++e)
+           std::clog << '\t' << (e->empty() == true ? "NO" : *e) << " extension" << std::endl;
+   }
+
+   std::vector<string> List;
+   DIR *D = opendir(Dir.c_str());
+   if (D == 0) 
+   {
+      _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
+      return List;
+   }
+
+   for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D)) 
+   {
+      // skip "hidden" files
+      if (Ent->d_name[0] == '.')
+        continue;
+
+      // check for accepted extension:
+      // no extension given -> periods are bad as hell!
+      // extensions given -> "" extension allows no extension
+      if (Ext.empty() == false)
+      {
+        string d_ext = flExtension(Ent->d_name);
+        if (d_ext == Ent->d_name) // no extension
+        {
+           if (std::find(Ext.begin(), Ext.end(), "") == Ext.end())
+           {
+              if (Debug == true)
+                 std::clog << "Bad file: " << Ent->d_name << " → no extension" << std::endl;
+              continue;
+           }
+        }
+        else if (std::find(Ext.begin(), Ext.end(), d_ext) == Ext.end())
+        {
+           if (Debug == true)
+              std::clog << "Bad file: " << Ent->d_name << " → bad extension »" << flExtension(Ent->d_name) << "«" << std::endl;
+           continue;
+        }
+      }
+
+      // Skip bad filenames ala run-parts
+      const char *C = Ent->d_name;
+      for (; *C != 0; ++C)
+        if (isalpha(*C) == 0 && isdigit(*C) == 0
+            && *C != '_' && *C != '-') {
+           // no required extension -> dot is a bad character
+           if (*C == '.' && Ext.empty() == false)
+              continue;
+           break;
+        }
+
+      // we don't reach the end of the name -> bad character included
+      if (*C != 0)
+      {
+        if (Debug == true)
+           std::clog << "Bad file: " << Ent->d_name << " → bad character »"
+              << *C << "« in filename (period allowed: " << (Ext.empty() ? "no" : "yes") << ")" << std::endl;
+        continue;
+      }
+
+      // skip filenames which end with a period. These are never valid
+      if (*(C - 1) == '.')
+      {
+        if (Debug == true)
+           std::clog << "Bad file: " << Ent->d_name << " → Period as last character" << std::endl;
+        continue;
+      }
+
+      // Make sure it is a file and not something else
+      string const File = flCombine(Dir,Ent->d_name);
+      struct stat St;
+      if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
+      {
+        if (Debug == true)
+           std::clog << "Bad file: " << Ent->d_name << " → stat says not a good file" << std::endl;
+        continue;
+      }
+
+      if (Debug == true)
+        std::clog << "Accept file: " << Ent->d_name << " in " << Dir << std::endl;
+      List.push_back(File);
+   }
+   closedir(D);
+
+   if (SortList == true)
+      std::sort(List.begin(),List.end());
+   return List;
+}
+                                                                       /*}}}*/
 // SafeGetCWD - This is a safer getcwd that returns a dynamic string   /*{{{*/
 // ---------------------------------------------------------------------
 /* We return / on failure. */
@@ -306,7 +500,7 @@ bool WaitFd(int Fd,bool write,unsigned long timeout)
 /* This is used if you want to cleanse the environment for the forked 
    child, it fixes up the important signals and nukes all of the fds,
    otherwise acts like normal fork. */
-pid_t ExecFork(int dontCloseThisFd)
+pid_t ExecFork()
 {
    // Fork off the process
    pid_t Process = fork();
@@ -326,11 +520,27 @@ pid_t ExecFork(int dontCloseThisFd)
       signal(SIGWINCH,SIG_DFL);
       signal(SIGCONT,SIG_DFL);
       signal(SIGTSTP,SIG_DFL);
-      
+
+      set<int> KeepFDs;
+      Configuration::Item const *Opts = _config->Tree("APT::Keep-Fds");
+      if (Opts != 0 && Opts->Child != 0)
+      {
+        Opts = Opts->Child;
+        for (; Opts != 0; Opts = Opts->Next)
+        {
+           if (Opts->Value.empty() == true)
+              continue;
+           int fd = atoi(Opts->Value.c_str());
+           KeepFDs.insert(fd);
+        }
+      }
+
       // Close all of our FDs - just in case
       for (int K = 3; K != 40; K++)
-        if(K != dontCloseThisFd)
+      {
+        if(KeepFDs.find(K) == KeepFDs.end())
            fcntl(K,F_SETFD,FD_CLOEXEC);
+      }
    }
    
    return Process;
@@ -365,8 +575,13 @@ bool ExecWait(pid_t Pid,const char *Name,bool Reap)
    {
       if (Reap == true)
         return false;
-      if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV)
-        return _error->Error(_("Sub-process %s received a segmentation fault."),Name);
+      if (WIFSIGNALED(Status) != 0)
+      {
+        if( WTERMSIG(Status) == SIGSEGV)
+           return _error->Error(_("Sub-process %s received a segmentation fault."),Name);
+        else 
+           return _error->Error(_("Sub-process %s received signal %u."),Name, WTERMSIG(Status));
+      }
 
       if (WIFEXITED(Status) != 0)
         return _error->Error(_("Sub-process %s returned an error code (%u)"),Name,WEXITSTATUS(Status));
@@ -390,6 +605,17 @@ bool FileFd::Open(string FileName,OpenMode Mode, unsigned long Perms)
       case ReadOnly:
       iFd = open(FileName.c_str(),O_RDONLY);
       break;
+
+      case ReadOnlyGzip:
+      iFd = open(FileName.c_str(),O_RDONLY);
+      if (iFd > 0) {
+        gz = gzdopen (iFd, "r");
+        if (gz == NULL) {
+            close (iFd);
+            iFd = -1;
+        }
+      }
+      break;
       
       case WriteEmpty:
       {
@@ -444,7 +670,10 @@ bool FileFd::Read(void *To,unsigned long Size,unsigned long *Actual)
    
    do
    {
-      Res = read(iFd,To,Size);
+      if (gz != NULL)
+         Res = gzread(gz,To,Size);
+      else
+         Res = read(iFd,To,Size);
       if (Res < 0 && errno == EINTR)
         continue;
       if (Res < 0)
@@ -483,7 +712,10 @@ bool FileFd::Write(const void *From,unsigned long Size)
    errno = 0;
    do
    {
-      Res = write(iFd,From,Size);
+      if (gz != NULL)
+         Res = gzwrite(gz,From,Size);
+      else
+         Res = write(iFd,From,Size);
       if (Res < 0 && errno == EINTR)
         continue;
       if (Res < 0)
@@ -509,7 +741,12 @@ bool FileFd::Write(const void *From,unsigned long Size)
 /* */
 bool FileFd::Seek(unsigned long To)
 {
-   if (lseek(iFd,To,SEEK_SET) != (signed)To)
+   int res;
+   if (gz)
+      res = gzseek(gz,To,SEEK_SET);
+   else
+      res = lseek(iFd,To,SEEK_SET);
+   if (res != (signed)To)
    {
       Flags |= Fail;
       return _error->Error("Unable to seek to %lu",To);
@@ -523,7 +760,12 @@ bool FileFd::Seek(unsigned long To)
 /* */
 bool FileFd::Skip(unsigned long Over)
 {
-   if (lseek(iFd,Over,SEEK_CUR) < 0)
+   int res;
+   if (gz)
+      res = gzseek(gz,Over,SEEK_CUR);
+   else
+      res = lseek(iFd,Over,SEEK_CUR);
+   if (res < 0)
    {
       Flags |= Fail;
       return _error->Error("Unable to seek ahead %lu",Over);
@@ -537,6 +779,11 @@ bool FileFd::Skip(unsigned long Over)
 /* */
 bool FileFd::Truncate(unsigned long To)
 {
+   if (gz)
+   {
+      Flags |= Fail;
+      return _error->Error("Truncating gzipped files is not implemented (%s)", FileName.c_str());
+   }
    if (ftruncate(iFd,To) != 0)
    {
       Flags |= Fail;
@@ -551,7 +798,11 @@ bool FileFd::Truncate(unsigned long To)
 /* */
 unsigned long FileFd::Tell()
 {
-   off_t Res = lseek(iFd,0,SEEK_CUR);
+   off_t Res;
+   if (gz)
+     Res = gztell(gz);
+   else
+     Res = lseek(iFd,0,SEEK_CUR);
    if (Res == (off_t)-1)
       _error->Errno("lseek","Failed to determine the current file position");
    return Res;
@@ -562,6 +813,7 @@ unsigned long FileFd::Tell()
 /* */
 unsigned long FileFd::Size()
 {
+   //TODO: For gz, do we need the actual file size here or the uncompressed length?
    struct stat Buf;
    if (fstat(iFd,&Buf) != 0)
       return _error->Errno("fstat","Unable to determine the file size");
@@ -575,9 +827,18 @@ bool FileFd::Close()
 {
    bool Res = true;
    if ((Flags & AutoClose) == AutoClose)
-      if (iFd >= 0 && close(iFd) != 0)
-        Res &= _error->Errno("close",_("Problem closing the file"));
+   {
+      if (gz != NULL) {
+        int e = gzclose(gz);
+        // gzdopen() on empty files always fails with "buffer error" here, ignore that
+        if (e != 0 && e != Z_BUF_ERROR)
+           Res &= _error->Errno("close",_("Problem closing the gzip file"));
+      } else
+        if (iFd > 0 && close(iFd) != 0)
+           Res &= _error->Errno("close",_("Problem closing the file"));
+   }
    iFd = -1;
+   gz = NULL;
    
    if ((Flags & Fail) == Fail && (Flags & DelOnFail) == DelOnFail &&
        FileName.empty() == false)