[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