[Aptitude-svn-commit] r4312 - in branches/aptitude-0.3/aptitude: . src/generic/util tests

Daniel Burrows dburrows at costa.debian.org
Tue Sep 27 19:36:52 UTC 2005


Author: dburrows
Date: Tue Sep 27 19:36:49 2005
New Revision: 4312

Added:
   branches/aptitude-0.3/aptitude/src/generic/util/temp.cc
   branches/aptitude-0.3/aptitude/src/generic/util/temp.h
   branches/aptitude-0.3/aptitude/tests/test_temp.cc
Modified:
   branches/aptitude-0.3/aptitude/ChangeLog
   branches/aptitude-0.3/aptitude/src/generic/util/Makefile.am
   branches/aptitude-0.3/aptitude/tests/Makefile.am
Log:
Add more reasonable wrapper code for handling temporary files.

Modified: branches/aptitude-0.3/aptitude/ChangeLog
==============================================================================
--- branches/aptitude-0.3/aptitude/ChangeLog	(original)
+++ branches/aptitude-0.3/aptitude/ChangeLog	Tue Sep 27 19:36:49 2005
@@ -1,5 +1,10 @@
 2005-09-27  Daniel Burrows  <dburrows at debian.org>
 
+	* src/generic/util/Makefile.am, src/generic/util/temp.cc, src/generic/util/temp.h, tests/Makefile.am, tests/test_temp.cc:
+
+	  Add C++-based support code for securely creating and cleaning up
+	  temporary files and directories.
+
 	* src/vscreen/config/colors.cc:
 
 	  Don't blow up if no colors are available (e.g., if the terminal

Modified: branches/aptitude-0.3/aptitude/src/generic/util/Makefile.am
==============================================================================
--- branches/aptitude-0.3/aptitude/src/generic/util/Makefile.am	(original)
+++ branches/aptitude-0.3/aptitude/src/generic/util/Makefile.am	Tue Sep 27 19:36:49 2005
@@ -13,6 +13,8 @@
 	mut_fun.h \
 	setset.h \
 	strhash.h \
+	temp.cc \
+	temp.h \
 	threads.cc \
 	threads.h \
 	undo.cc \

Added: branches/aptitude-0.3/aptitude/src/generic/util/temp.cc
==============================================================================
--- (empty file)
+++ branches/aptitude-0.3/aptitude/src/generic/util/temp.cc	Tue Sep 27 19:36:49 2005
@@ -0,0 +1,159 @@
+// temp.cc
+//
+//   Copyright (C) 2005 Daniel Burrows
+//
+//   This program is free software; you can redistribute it and/or
+//   modify it under the terms of the GNU General Public License as
+//   published by the Free Software Foundation; either version 2 of
+//   the License, or (at your option) any later version.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//   General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; see the file COPYING.  If not, write to
+//   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+//   Boston, MA 02111-1307, USA.
+
+#include "temp.h"
+
+#include "util.h"
+
+#include <aptitude.h>
+
+#include <stdlib.h>
+
+namespace temp
+{
+  std::string TemporaryCreationFailure::errmsg() const
+  {
+    return msg;
+  }
+
+  void dir::impl::init_dir(const std::string &_prefix)
+  {
+    // Need to modify it
+    std::string prefix(_prefix);
+
+    if(prefix.size() > 0 && prefix[0] != '/')
+      {
+	const char *tmpdir = getenv("TMPDIR");
+
+	if(tmpdir == NULL)
+	  tmpdir = getenv("TMP");
+
+	if(tmpdir == NULL)
+	  tmpdir = "/tmp";
+
+	prefix = std::string(tmpdir) + "/" + prefix.c_str();
+      }
+
+
+    size_t bufsize = prefix.size() + 6 + 1;
+    char *tmpl = new char[bufsize];
+    strcpy(tmpl, prefix.c_str());
+    strcat(tmpl, "XXXXXX");
+
+
+    if(mkdtemp(tmpl) == NULL)
+      {
+	std::string err = sstrerror(errno);
+	std::string errmsg = ssprintf(_("Unable to create temporary directory from template \"%s\": %s"),
+				      tmpl, err.c_str());
+
+	delete[] tmpl;
+
+	throw TemporaryCreationFailure(errmsg);
+      }
+
+    dirname.assign(tmpl);
+    delete[] tmpl;
+  }
+
+  dir::impl::impl(const std::string &prefix)
+    : refcount(1)
+  {
+    init_dir(prefix);
+  }
+
+  dir::impl::impl(const std::string &prefix, const dir &_parent)
+    : parent(_parent), refcount(1)
+  {
+    if(prefix.size() > 0 && prefix[0] == '/')
+      throw TemporaryCreationFailure("Invalid attempt to create an absolute rooted temporary directory.");
+
+    init_dir(parent.get_name() + '/' + prefix);
+  }
+
+  dir::impl::~impl()
+  {
+    rmdir(dirname.c_str());
+  }
+
+
+  name::impl::impl(const dir &_parent, const std::string &_filename)
+    : parent(_parent), refcount(1)
+  {
+    // Warn early about bad filenames.
+    if(_filename.find('/') != _filename.npos)
+      throw TemporaryCreationFailure(ssprintf("Invalid temporary filename (contains directory separator): \"%s\"",
+					      _filename.c_str()));
+
+    if(!_parent.valid())
+      throw TemporaryCreationFailure("NULL parent directory passed to temp::name constructor");
+
+    size_t parentsize = parent.get_name().size();
+    size_t filenamesize = _filename.size();
+    size_t bufsize = parentsize + 1 + filenamesize + 6 + 1;
+    char *tmpl = new char[bufsize];
+
+    strncpy(tmpl, parent.get_name().c_str(), bufsize);
+    strncat(tmpl, "/", bufsize - parentsize);
+    strncat(tmpl, _filename.c_str(), bufsize - parentsize - 1);
+    strncat(tmpl, "XXXXXX", bufsize - parentsize - 1 - filenamesize);
+
+    errno = 0;
+
+    // This use of mktemp is safe under the assumption that 'dir' is
+    // safe (because it was created using mkdtemp, which unlike mktemp
+    // is safe) and that the user running the program didn't screw with
+    // the permissions of the temporary directory.
+    if(mktemp(tmpl) == NULL)
+      {
+	std::string err = sstrerror(errno);
+
+	delete[] tmpl;
+	throw TemporaryCreationFailure(ssprintf(_("Unable to create temporary directory from template \"%s\": %s"),
+						(_parent.get_name() + "/" + _filename).c_str(),
+						err.c_str()));
+      }
+
+    if(tmpl[0] == '\0')
+      {
+	std::string err;
+	if(errno == 0)
+	  err = _("Unknown error");
+	else
+	  err = sstrerror(errno);
+
+	delete[] tmpl;
+	throw TemporaryCreationFailure(ssprintf(_("Unable to create temporary directory from template \"%s\": %s"),
+						(_parent.get_name() + "/" + _filename).c_str(),
+						err.c_str()));
+      }
+
+    filename.assign(tmpl);
+    delete[] tmpl;
+  }
+
+  // We don't know if it's a filename or a directory, so try blowing
+  // both away (ignoring errors).
+  name::impl::~impl()
+  {
+    rmdir(filename.c_str());
+    unlink(filename.c_str());
+  }
+}
+

Added: branches/aptitude-0.3/aptitude/src/generic/util/temp.h
==============================================================================
--- (empty file)
+++ branches/aptitude-0.3/aptitude/src/generic/util/temp.h	Tue Sep 27 19:36:49 2005
@@ -0,0 +1,354 @@
+// temp.h                                 -*-c++-*-
+//
+//   Copyright (C) 2005 Daniel Burrows
+//
+//   This program is free software; you can redistribute it and/or
+//   modify it under the terms of the GNU General Public License as
+//   published by the Free Software Foundation; either version 2 of
+//   the License, or (at your option) any later version.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//   General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; see the file COPYING.  If not, write to
+//   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+//   Boston, MA 02111-1307, USA.
+//
+// Code to support safely creating files in a temporary directory and
+// deleting the directory when finished.
+
+#ifndef TEMP_H
+#define TEMP_H
+
+#include <string>
+
+#include "exception.h"
+#include "threads.h"
+
+namespace temp
+{
+  /** An exception thrown when a temporary object cannot be created. */
+  class TemporaryCreationFailure : public Exception
+  {
+    std::string msg;
+  public:
+    TemporaryCreationFailure(const std::string &_msg)
+      : msg(_msg)
+    {
+    }
+
+    std::string errmsg() const;
+  };
+
+  /** This object represents a directory in which temporary files can
+   *  be created.  While you can extract the file name, it is
+   *  recommended that you instead create temporary file(name)
+   *  objects.
+   */
+  class dir
+  {
+    class impl;;
+
+    impl *real_dir;
+  public:
+    /** Create a temporary directory object with no backing directory. */
+    dir()
+      : real_dir(NULL)
+    {
+    }
+
+    /** Create a new temporary directory whose name begins with the
+     *  text in prefix.
+     *
+     *  \throws TemporaryCreationFailure
+     */
+    dir(const std::string &prefix);
+
+    /** Create a new temporary directory that is a subdirectory of an
+     *  existing directory and whose name begins with prefix.
+     *
+     *  \throws TemporaryCreationFailure
+     */
+    dir(const std::string &prefix, const dir &parent);
+    /** Create a new reference to an existing temporary directory. */
+    dir(const dir &other);
+    /** Copy a reference to an existing temporary directory. */
+    dir &operator=(const dir &other);
+
+    /** \return \b true if the directory is a valid reference. */
+    bool valid() const;
+
+    /** This method may be invoked only on valid directories.
+     *
+     *  \return the name of this directory.  It is recommended that
+     *  you use this only for informational purposes; files should be
+     *  created via (e.g.) temp::name.
+     */
+    std::string get_name() const;
+
+    ~dir();
+  };
+
+  class dir::impl
+  {
+    /** The name of this directory. */
+    std::string dirname;
+
+    /** The parent of this directory, if any. */
+    dir parent;
+
+    threads::mutex m;
+
+    int refcount;
+
+    /** Set up a temporary directory with the given prefix.
+     *
+     *  Contains common code for the constructors.
+     */
+    void init_dir(const std::string &prefix);
+
+  public:
+    /** Create a new temporary directory whose name begins with the
+     *  text in prefix.  For instance, passing "aptitude" might
+     *  create "/tmp/aptitude45j2hs" or somesuch.
+     *
+     *  The initial refcount is 1.
+     *
+     *  \param prefix the prefix of this directory's name.  If
+     *  prefix begins with a '/', then it is considered an absolute
+     *  path; otherwise, it is considered a relative path within the
+     *  system temporary directory.
+     *
+     *  \throws TemporaryCreationFailure
+     */
+    impl(const std::string &prefix);
+
+    /** Create a new temporary directory within another temporary
+     *  directory.
+     *
+     *  \param prefix the prefix of the name of this directory
+     *                within the parent directory; must not begin
+     *                with a '/'
+     *  \param parent the parent directory to create this directory
+     *                within
+     *
+     *  \throws TemporaryCreationFailure
+     */
+    impl(const std::string &prefix, const dir &parent);
+
+    /** Attempt to remove the temporary directory. */
+    ~impl();
+
+    std::string get_name() const
+    {
+      return dirname;
+    }
+
+    /** Increment the reference count of this impl. */
+    void incref()
+    {
+      threads::mutex::lock l(m);
+      ++refcount;
+    }
+
+    /** Decrement the reference count of this impl. */
+    void decref()
+    {
+      threads::mutex::lock l(m);
+
+      assert(refcount > 0);
+      --refcount;
+
+      if(refcount == 0)
+	delete this;
+    }
+  };
+
+  inline dir::dir(const std::string &prefix)
+    : real_dir(new impl(prefix))
+  {
+  }
+
+  inline dir::dir(const std::string &prefix, const dir &parent)
+    : real_dir(new impl(prefix, parent))
+  {
+  }
+
+  inline dir::dir(const dir &other)
+    : real_dir(other.real_dir)
+  {
+    if(real_dir != NULL)
+      real_dir->incref();
+  }
+
+  inline dir &dir::operator=(const dir &other)
+  {
+    if(other.real_dir != NULL)
+      other.real_dir->incref();
+
+    if(real_dir != NULL)
+      real_dir->decref();
+
+    real_dir = other.real_dir;
+
+    return *this;
+  }
+
+  inline bool dir::valid() const
+  {
+    return real_dir != NULL;
+  }
+
+  inline std::string dir::get_name() const
+  {
+    return real_dir->get_name();
+  }
+
+  inline dir::~dir()
+  {
+    if(real_dir != NULL)
+      real_dir->decref();
+  }
+
+  /** A temporary name -- at the moment of its creation it is
+   *  guaranteed to be unique, but it is up to you to ensure that it
+   *  is created uniquely.
+   */
+  class name
+  {
+    class impl;
+
+    impl *real_name;
+
+  public:
+    /** Create a new temporary filename in the given directory.
+     *
+     *  \param d the directory in which to create the filename
+     *  \param prefix the prefix of the new filename
+     *
+     *  \throws TemporaryCreationFailure if no temporary name can be
+     *  reserved.
+     */
+    name(const dir &d, const std::string &prefix);
+
+    /** Create an empty temporary name. */
+    name();
+
+    /** Create a new reference to an existing name. */
+    name(const name &other);
+
+    ~name();
+
+    /** Copy a reference to an existing temporary name. */
+    name &operator=(const name &other);
+
+
+    bool valid() const;
+    std::string get_name() const;
+  };
+
+  class name::impl
+  {
+    /** The name of this temporary object. */
+    std::string filename;
+
+    /** The directory in which this temporary should be created. */
+    dir parent;
+
+    /** The mutex of this name's reference count. */
+    threads::mutex m;
+
+    /** The reference count of this name. */
+    int refcount;
+  public:
+    /** Create a new temporary filename in the given directory.
+     *
+     *  \param dir the temporary directory in which to create the file
+     *  \param filename the prefix of the temporary filename
+     *
+     *  \throws TemporaryCreationFailure if no temporary name can be
+     *  reserved.
+     */
+    impl(const dir &parent,
+	 const std::string &filename);
+
+    /** Remove the filename associated with this temporary. */
+    ~impl();
+
+    /** \return the temporary's name. */
+    std::string get_name() const
+    {
+      return filename;
+    }
+
+    /** Increment the reference count of this impl. */
+    void incref()
+    {
+      threads::mutex::lock l(m);
+      ++refcount;
+    }
+
+    /** Decrement the reference count of this impl. */
+    void decref()
+    {
+      threads::mutex::lock l(m);
+
+      assert(refcount > 0);
+      --refcount;
+
+      if(refcount == 0)
+	delete this;
+    }
+  };
+
+  inline name::name(const dir &d, const std::string &prefix)
+    : real_name(new impl(d, prefix))
+  {
+  }
+
+  inline name::name()
+    : real_name(NULL)
+  {
+  }
+
+  inline name::name(const name &other)
+    : real_name(other.real_name)
+  {
+    if(real_name != NULL)
+      real_name->incref();
+  }
+
+  inline name::~name()
+  {
+    if(real_name != NULL)
+      real_name->decref();
+  }
+
+  inline name &name::operator=(const name &other)
+  {
+    if(other.real_name != NULL)
+      other.real_name->incref();
+
+    if(real_name != NULL)
+      real_name->decref();
+
+    real_name = other.real_name;
+
+    return *this;
+  }
+
+  inline bool name::valid() const
+  {
+    return real_name != NULL;
+  }
+
+  inline std::string name::get_name() const
+  {
+    return real_name->get_name();
+  }
+};
+
+#endif // TEMP_H

Modified: branches/aptitude-0.3/aptitude/tests/Makefile.am
==============================================================================
--- branches/aptitude-0.3/aptitude/tests/Makefile.am	(original)
+++ branches/aptitude-0.3/aptitude/tests/Makefile.am	Tue Sep 27 19:36:49 2005
@@ -23,5 +23,6 @@
 	test_resolver.cc \
 	test_setset.cc \
 	test_tags.cc \
+	test_temp.cc \
 	test_threads.cc \
 	test_wtree.cc

Added: branches/aptitude-0.3/aptitude/tests/test_temp.cc
==============================================================================
--- (empty file)
+++ branches/aptitude-0.3/aptitude/tests/test_temp.cc	Tue Sep 27 19:36:49 2005
@@ -0,0 +1,144 @@
+// test_temp.cc
+//
+//   Copyright (C) 2005 Daniel Burrows
+//
+//   This program is free software; you can redistribute it and/or
+//   modify it under the terms of the GNU General Public License as
+//   published by the Free Software Foundation; either version 2 of
+//   the License, or (at your option) any later version.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//   General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; see the file COPYING.  If not, write to
+//   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+//   Boston, MA 02111-1307, USA.
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <generic/util/temp.h>
+#include <generic/util/util.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define ASSERT_STAT(s, buf) \
+  do \
+  { \
+    if(stat((s), (buf)) != 0) \
+       CPPUNIT_FAIL(ssprintf("Can't stat %s: %s", (s), sstrerror(errno).c_str())); \
+  } while(0)
+						    
+
+class TempTest : public CppUnit::TestFixture
+{
+  CPPUNIT_TEST_SUITE(TempTest);
+
+  CPPUNIT_TEST(testTempDir);
+  CPPUNIT_TEST(testTempName);
+
+  CPPUNIT_TEST_SUITE_END();
+
+public:
+  void testTempDir()
+  {
+    std::string d1name, d2name;
+
+    {
+      temp::dir d1("tmp");
+      temp::dir d2("tmp", d1);
+
+      d1name = d1.get_name();
+      d2name = d2.get_name();
+
+      int result = access(d1name.c_str(), F_OK);
+      if(result != 0)
+	CPPUNIT_FAIL(ssprintf("Unable to access %s: %s",
+			      d1name.c_str(), sstrerror(errno).c_str()));
+
+      result = access(d2.get_name().c_str(), F_OK);
+      if(result != 0)
+	CPPUNIT_FAIL(ssprintf("Unable to access %s: %s",
+			      d2name.c_str(), sstrerror(errno).c_str()));
+
+      char *d1namecopy = strdup(d1name.c_str());
+      std::string base1 = basename(d1namecopy);
+      free(d1namecopy);
+      d1namecopy = NULL;
+
+      CPPUNIT_ASSERT_EQUAL(std::string("tmp"), std::string(base1, 0, base1.size()-6));
+
+      char *d2namecopy = strdup(d2name.c_str());
+      std::string base2 = basename(d2namecopy);
+      free(d2namecopy);
+      d2namecopy = NULL;
+
+      CPPUNIT_ASSERT_EQUAL(std::string("tmp"), std::string(base2, 0, base2.size()-6));
+
+      struct stat stbuf;
+
+      ASSERT_STAT(d1.get_name().c_str(), &stbuf);
+      CPPUNIT_ASSERT(S_ISDIR(stbuf.st_mode));
+
+      ASSERT_STAT(d2.get_name().c_str(), &stbuf);
+      CPPUNIT_ASSERT(S_ISDIR(stbuf.st_mode));
+    }
+
+    int result = access(d1name.c_str(), F_OK);
+    CPPUNIT_ASSERT(result != 0);
+    CPPUNIT_ASSERT_EQUAL(ENOENT, errno);
+
+    result = access(d2name.c_str(), F_OK);
+    CPPUNIT_ASSERT(result != 0);
+    CPPUNIT_ASSERT_EQUAL(ENOENT, errno);
+  }
+
+  void testTempName()
+  {
+    std::string dname;
+    std::string fname;
+
+    {
+      temp::dir d("tmp");
+
+      temp::name f(d, "tmpf");
+
+      dname = d.get_name();
+      fname = f.get_name();
+
+      char *fnamecopy = strdup(fname.c_str());
+      std::string base = basename(fnamecopy);
+      free(fnamecopy);
+      fnamecopy = NULL;
+
+      CPPUNIT_ASSERT_EQUAL(std::string("tmpf"), std::string(base, 0, base.size()-6));
+
+      CPPUNIT_ASSERT(access(f.get_name().c_str(), F_OK) != 0);
+      CPPUNIT_ASSERT_EQUAL(ENOENT, errno);
+
+      // Create it.
+      int fd = open(fname.c_str(), O_EXCL | O_CREAT | O_WRONLY, 0700);
+      if(fd == -1)
+	CPPUNIT_FAIL(ssprintf("Can't create \"%s\": %s",
+			      fname.c_str(),
+			      sstrerror(errno).c_str()));
+
+      CPPUNIT_ASSERT_EQUAL(0, access(f.get_name().c_str(), F_OK));
+
+      close(fd);
+    }
+
+    CPPUNIT_ASSERT(access(fname.c_str(), F_OK) != 0);
+    CPPUNIT_ASSERT_EQUAL(ENOENT, errno);
+
+    CPPUNIT_ASSERT(access(dname.c_str(), F_OK) != 0);
+    CPPUNIT_ASSERT_EQUAL(ENOENT, errno);
+  }
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TempTest);



More information about the Aptitude-svn-commit mailing list