[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

[PATCH 3/6] Conffile database handling functions



(note: this information is largely duplicative of the comments in conffiledb.h)

The files conffiledb.{c,h} define a set of routines for adding and removing
stored versions of the pristine conffiles as shipped in their respective
packages.

Such an implementation allows for some neat features in the future,
such as:

 * 3-way merging of conffile changes
 * showing the old->new delta for the "dist" conffiles
 * dynamic registering of conffiles (à la ucf)
 * a general dpkg/conffile cmdline interface for all of this

The layout pattern for the conffile database is:

	<admindir>/conffiles/<base>/<pkg>/<hash>

Where:

 * <admindir> is the standard dpkg administrative dir (i.e. /var/lib/dpkg).
 * <base> is a short string corresponding to which type of base reference
          is stored underneath (see source code comments for more info).
 * <pkg> is the conffile database directory for a package.
 * <hash> is the md5 hash of the installed location of a package's conffile.
          A hash is used to keep the directory structure flat and balanced.

It's assumed that this database is not meant for direct user consumption.
However, a "dpkg-conffile" cmdline interface will be provided that will
allow the user to perform various forms of inspection with regards to
installed conffiles vs the versions in this database.

conffiledb
---
 lib/dpkg/dpkg.h  |    1 +
 src/Makefile.am  |    1 +
 src/conffiledb.c |  218 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/conffiledb.h |  155 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 375 insertions(+), 0 deletions(-)
 create mode 100644 src/conffiledb.c
 create mode 100644 src/conffiledb.h

diff --git a/lib/dpkg/dpkg.h b/lib/dpkg/dpkg.h
index afe650f..9b06c73 100644
--- a/lib/dpkg/dpkg.h
+++ b/lib/dpkg/dpkg.h
@@ -74,6 +74,7 @@ DPKG_BEGIN_DECLS
 #define STATOVERRIDEFILE  "statoverride"
 #define UPDATESDIR        "updates/"
 #define INFODIR           "info/"
+#define CONFFILEDBDIRROOT "conffiles/" /**< @ref conffilesdb root location */
 #define TRIGGERSDIR       "triggers/"
 #define TRIGGERSFILEFILE  "File"
 #define TRIGGERSDEFERREDFILE "Unincorp"
diff --git a/src/Makefile.am b/src/Makefile.am
index a29b629..24583de 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -20,6 +20,7 @@ bin_PROGRAMS = \
 dpkg_SOURCES = \
 	archives.c archives.h \
 	cleanup.c \
+	conffiledb.c conffiledb.h \
 	configure.c \
 	depcon.c \
 	enquiry.c \
diff --git a/src/conffiledb.c b/src/conffiledb.c
new file mode 100644
index 0000000..46cbfaf
--- /dev/null
+++ b/src/conffiledb.c
@@ -0,0 +1,218 @@
+#include "conffiledb.h"
+
+#include <config.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/i18n.h>
+#include <dpkg/buffer.h>
+#include <dpkg/path.h>
+#include <dpkg/dpkg-db.h>
+#include "main.h"
+
+const char *conffiledb_base_dirs[] = { "current", "new", "resolved", "tmp" };
+
+char* 
+conffiledb_path(const char *pkg, const char *path, conffiledb_base base)
+{
+	char *full_path = NULL, *hash = NULL;
+	const char *basedir = NULL;
+	size_t basedir_sz = 0, conffiledb_sz = 0, full_path_sz = 0;
+
+	/* sanity check for a valid base dir */
+	if (base >= conffiledb_base_COUNT)
+		ohshit("conffiledb_path called with unsupported base %x", base);
+
+	basedir = conffiledb_base_dirs[base];
+
+	/* the root path is <admindir>/<#CONFFILEDBDIRROOT/><basedir>/
+	 * (note that the '/' after #CONFFILEDBDIRROOT is implicitly included 
+	 * in the #define'd string). The null byte is also accounted for
+	 * at this point. */
+	basedir_sz = strlen(admindir) + 1 + strlen(CONFFILEDBDIRROOT) +
+	             strlen(basedir) + 2;
+	full_path_sz = conffiledb_sz = basedir_sz;
+	
+	/* we will add "<pkg>/" to the string later if <pkg> is not null */
+	if (pkg != NULL) 
+		full_path_sz = conffiledb_sz += strlen(pkg) + 1;
+
+	/* and if a conffile is being requested (not just the db root)... */
+	if (path != NULL)
+		full_path_sz += CONFFILEDB_MD5SUM_LEN;
+
+	/* this is the path to the conffile db for the given base/pkg */
+	full_path = m_malloc(full_path_sz);
+	snprintf(full_path, basedir_sz, "%s/%s%s/", admindir, 
+	         CONFFILEDBDIRROOT, basedir);
+	
+	/* append "<pkg>/" if needed */
+	if (pkg != NULL)
+		sprintf(full_path + basedir_sz - 1, "%s/", pkg);
+
+	/* append the pathname's hash if relevant */
+	if (path != NULL) {
+		hash = full_path + conffiledb_sz - 1;
+		buffer_md5(path, hash, strlen(path));
+	}
+
+	debug(dbg_conffdetail, "confffiledb_path(%s, %s, %x) = %s\n", pkg, 
+	      path, base, full_path);
+
+	return full_path;
+}
+
+/** Ensure that a particular conffiledb dir exists.
+ *
+ * @param pkg The package owning the conffiledb dir.
+ * @param base Which conffiledb basedir to use.
+ */
+static void
+conffiledb_ensure_db(const char *pkg, conffiledb_base base)
+{
+	struct stat s; 
+	short i = 0;
+	char *dbdir = NULL;
+	char *separators[3];
+
+	dbdir = conffiledb_path(pkg, NULL, base);
+	/* temporarily truncate the string to cheaply traverse up to the
+	 * the parent and its parent. store the locations so we can cheaply
+	 * get back to them later. */
+	for (i = 0; i < 3; i++) {
+		separators[i] = rindex(dbdir, '/');
+		*separators[i] = '\0';
+	}
+
+	/* now ensure each directory exists while reconstructing the path */
+	for (i = 2; i >= 0; i--) {
+		if (stat(dbdir, &s)) {
+			debug(dbg_conffdetail, 
+			      "conffiledb_ensure_db: creating %s\n", dbdir);
+			if (errno != ENOENT) 
+				ohshite("conffiledb_ensure_db: stat(%s)", 
+				        dbdir);
+			if (mkdir(dbdir, S_IRWXU)) 
+				ohshite("conffiledb_ensure_db: mkdir(%s)", 
+				        dbdir);
+		}
+		*separators[i] = '/';
+	}
+
+	free(dbdir);
+}
+
+void
+conffiledb_register_new_conffile(const char *pkg, const char *path, int fd, 
+                                 size_t sz)
+{
+	int cfgfd;
+	/* get the path to where the registered file goes */
+	char *p = conffiledb_path(pkg, path, conffiledb_base_new);
+	char fnamebuf[256];
+
+	conffiledb_ensure_db(pkg, conffiledb_base_new);
+	debug(dbg_conff, "conffile_reg_fd: %s, %s, %s\n", pkg, path, p);
+	/* make a mode 600 copy of file to p */
+	cfgfd = open(p, (O_CREAT|O_EXCL|O_WRONLY), (S_IRUSR|S_IWUSR));
+	if (cfgfd < 0) 
+		ohshite(_("unable to create `%.255s'"),p);
+	fd_fd_copy(fd, cfgfd, sz, _("backend dpkg-deb during `%.255s'"),
+	           path_quote_filename(fnamebuf, p, 256));
+	if (close(cfgfd))
+		ohshite("can't close %s", p);
+
+	free(p);
+}
+
+int
+conffiledb_get_conffile(const char *pkg, const char *path, conffiledb_base base)
+{
+	int fd = -1;
+	char *p = conffiledb_path(pkg, path, base);
+
+	fd = open(p, O_RDONLY);
+	if (fd == -1)
+		ohshite("error opening conffile registered at %s", p);
+
+	free(p);
+	return fd;
+}
+
+void
+conffiledb_commit(const char *pkg, conffiledb_base from_base, 
+                  conffiledb_base to_base)
+{
+	/* update the conffiles "db" dir.  this consists of the following steps:
+	 * - nuke the tmp dir if it exists
+	 * - rename to_base to the tmp dir
+	 * - rename from_base to to_base
+	 * - nuke the tmp dir
+	 */
+	char *cfdb_to = NULL, *cfdb_from = NULL, *cfdb_tmp = NULL;
+	debug(dbg_conff, "conffiledb_commit(%s, %d, %d)", pkg, from_base, 
+	      to_base);
+
+	cfdb_from = conffiledb_path(pkg, NULL, from_base);
+	cfdb_to = conffiledb_path(pkg, NULL, to_base);
+	cfdb_tmp = conffiledb_path(pkg, NULL, conffiledb_base_tmp);
+
+	/* Make sure all leading components of the tmp conffiledb exist */
+	conffiledb_ensure_db(pkg, conffiledb_base_tmp);
+	/* Then nuke the tmp conffiledb if it exists */
+	conffiledb_remove(pkg, conffiledb_base_tmp);
+	/* Make sure all leading components of the target conffiledb exist */
+	conffiledb_ensure_db(pkg, to_base);
+	/* Rename the target conffiledb to the tmp one */
+	if (rename(cfdb_to, cfdb_tmp))
+		ohshite("conffiledb_commit: rename(%s,%s)", cfdb_to, cfdb_tmp);
+	/* Make sure all leading components of the source conffiledb exist */
+	conffiledb_ensure_db(pkg, from_base);
+	/* Rename from_base to to_base */
+	if (rename(cfdb_from, cfdb_to))
+		ohshite("conffiledb_commit: rename(%s,%s)", cfdb_from, cfdb_to);
+	/* Nuke the tmp version if it exists */
+	conffiledb_remove(pkg, conffiledb_base_tmp);
+
+	free(cfdb_from);
+	free(cfdb_to);
+	free(cfdb_tmp);
+}
+
+void
+conffiledb_remove(const char *pkg, conffiledb_base base)
+{
+	char *cfdb = conffiledb_path(pkg, NULL, base);
+
+	debug(dbg_conff, "conffiledb_remove(%s): removing %s\n", pkg, cfdb);
+	ensure_pathname_nonexisting(cfdb);
+
+	free(cfdb);
+}
+
+int
+conffiledb_diff (const char *pkg, const char *path, conffiledb_base base)
+{
+	int status;
+	char *src = conffiledb_path(pkg, path, base);
+	char *cmd = NULL;
+	const char *opts = "-u";
+	/* DIFF + ' ' + <opts> + ' ' + <srcfile> + ' ' + <dstfile> + '\0' */
+	size_t cmd_sz = strlen(DIFF) + 1 + strlen(opts) + 1 + strlen(src) +
+	                1 + strlen(path) + 1;
+
+	cmd = m_malloc(cmd_sz);
+	sprintf(cmd, "%s %s %s %s", DIFF, opts, src, path);
+	status = system(cmd);
+
+	free(src);
+	free(cmd);
+	return WEXITSTATUS(status);
+}
diff --git a/src/conffiledb.h b/src/conffiledb.h
new file mode 100644
index 0000000..9278cf1
--- /dev/null
+++ b/src/conffiledb.h
@@ -0,0 +1,155 @@
+/**
+ * @file conffiledb.h
+ *
+ * Functions for tracking referencable versions of packages' conffiles.
+ *
+ * This library allows for tracking the pristine versions (and in some specific
+ * cases a few other versions) of packages' conffiles.  This allows for a
+ * number of desirable features, such as conffile merging and more
+ * featureful reporting of the differences between conffiles on package
+ * upgrade even when the local admin may have modified the installed
+ * version of the file.
+ *
+ * The internal database of conffiles follows the convention:
+ *
+ *	#CONFFILEDBDIRROOT/\<base\>/\<pkg\>/\<md5\>
+ *
+ * where:
+ * - @b \<base\> is a short string which corresponds to the base reference
+ *   type in question. See the documentation for conffiledb_base for the 
+ *   enumerated values and conffiledb_base_dirs for the corresponding
+ *   string values.
+ * - @b \<pkg\> is the conffile database directory for an individual package.
+ * - @b \<md5\> is the md5 hash of the location of a package's conffile.  A 
+ *   hash is used to keep a flat and balanced directory structure, and has
+ *   the added benefit of a simpler implementation.
+ *
+ * Some further terminology to keep things clear later on:
+ *
+ * - @b "conffiledb root" refers to the top-most directory containing
+ *   all conffile databases, and is defined by #CONFFILEDBDIRROOT.
+ * - @b "conffiledb basedir" refers to the subdirectory directly
+ *   underneath the conffiledb root directory, which divides the the
+ *   different conffiledbs by different "reference types" (i.e. the
+ *   original conffile provided by the currently installed package vs.
+ *   a newly unpacked version). The available basedirs are defined in
+ *   conffiledb_base_dirs, which is indexed on the respective enumerated
+ *   values from conffiledb_base.
+ * - @anchor conffiledb @b "conffile database" or @b "conffiledb" refers
+ *   to a directory specific to a conffiledb_base + package tuple.  Such a
+ *   directory will contain copies of the conffiles named after the hash
+ *   of their respective installed locations.
+ * - @b "conffiledb file" or @b "conffiledb entry" refers to a file whose 
+ *   contents correspond to a particular conffiledb_base/package/conffile 
+ *   triplet.
+ */
+#ifndef CONFFILES_H
+#define CONFFILES_H
+
+#include <sys/types.h>
+
+/** The different base reference types underneath the conffile db. */
+typedef enum {
+	conffiledb_base_current, /**< The currently installed version */
+	conffiledb_base_new, /**< A newly unpacked version */
+	conffiledb_base_resolved, /**< The most recently resolved conflict */
+	conffiledb_base_tmp, /**< A temporary version for internal use */
+	conffiledb_base_COUNT /**< The count of possible values for this type */
+} conffiledb_base;
+
+/** 
+ * The directory names of the conffiledb base dirs, indexed by the respective
+ * conffiledb_base value.
+ */
+extern const char *conffiledb_base_dirs[conffiledb_base_COUNT];
+
+/** Length of the MD5 string used in conffile path hashing (NULL not counted) */
+#define CONFFILEDB_MD5SUM_LEN 32
+
+/** Return the canonical path for a conffiledb entry or other location.
+ *
+ * This is a helper function to resolve the full path to a specific location 
+ * within the conffile database. The primary use is to find the location
+ * of a conffiledb file, but it can also be used to find the location
+ * of the @ref conffiledb for a particular conffiledb_base + package,
+ * as well as the parent conffiledb basedir corresponding to a particular 
+ * conffiledb_base.
+ *
+ * @param pkg The package name. If NULL, the returned value will be
+ *        the conffiledb basedir corresponding to base.
+ * @param path The absolute path to the conffile. If NULL, the returned
+ *        value will be the conffiledb directory which should contain all
+ *        conffiledb files for the combination of pkg and base.
+ * @param base Which conffiledb basedir is requested.
+ * @return An allocated string with the path (your job to free it).
+ */
+char* conffiledb_path(const char *pkg, const char *path, conffiledb_base base);
+
+/** Register a new conffiledb file for a package.
+ *
+ * Create a conffiledb entry in the conffiledb dir for the::conffiledb_base_new
+ * version of a particular package. The conffile will be later processed in the
+ * package configuration stage, after which there should be a call to
+ * conffiledb_commit_new before configuration can be considered successful.
+ *
+ * The use of a filedescriptor is due to the fact that the new conffile is
+ * intercepted very early during the unpack phase, before it exists on disk.
+ * Therefore this interface is designed to hook cleanly into what's available 
+ * at that point, which is a filedescriptor generated from a struct TarInfo
+ * (see tarobject in archives.c).
+ *
+ * @param pkg The package owning the conffile.
+ * @param path The installed location of the conffile.
+ * @param fd A file descriptor holding the contents of the conffile.
+ * @param sz The size of the conffile.
+ */
+void conffiledb_register_new_conffile(const char *pkg, const char *path, int fd,
+                                      size_t sz);
+
+/** Get a readable filehandle to the contents of a conffiledb entry.
+ *
+ * @param pkg The package owning the conffile.
+ * @param path The installed location of the conffile.
+ * @param base Which conffiledb basedir to use.
+ * @return A read-only file descriptor for the registered conffile.
+ */
+int conffiledb_get_conffile(const char *pkg, const char *path, 
+                            conffiledb_base base);
+
+/** Remove a conffiledb directory for a package.
+ *
+ * This is designed to be used in two use cases: 
+ * - package removal and cleanup
+ * - after one conffiledb directory has been replaced by another.
+ *
+ * @param pkg The package owning the conffile db to be removed.
+ * @param base Which conffiledb basedir to use with respect to pkg.
+ */
+void conffiledb_remove(const char *pkg, conffiledb_base base);
+
+/** Commit one conffiledb as another, replacing any existing one.
+ *
+ * This function takes the conffiledb defined by pkg and from_base and
+ * commits it as being defined by pkg and to_base. Any existing conffiledb
+ * for pkg / to_base is first renamed as conffiledb_base_tmp and then
+ * removed after the successful "commit" (to keep things as atomic as 
+ * possible).
+ *
+ * @param pkg The package owning the conffile database
+ * @param from_base The current conffiledb basedir which should be renamed.
+ * @param to_base The conffiledb basedir which should be replaced by the
+ *        version in from_base.
+ */
+void conffiledb_commit(const char *pkg, conffiledb_base from_base, 
+                       conffiledb_base to_base);
+
+/** Show the diff between a conffiledb entry and a corresponding conffile.
+ * 
+ * @param pkg The package that owns the conffile.
+ * @param path The pathname to the installed version of the conffile.
+ * @param base Which conffiledb basedir to use.
+ * @return The exit status of the underlying call to diff(1).
+ */
+int conffiledb_diff(const char *pkg, const char *path, conffiledb_base base);
+
+#endif /* CONFFILES_H */
-- 
1.6.4.3


Reply to: