Browse Source

dpkg: Upgrade the database automatically to the new multi-arch layout

The upgrade is scheduled by explicit calls to pkg_infodb_upgrade() if
the current database format version is less than the latest supported
format or if the previous upgrade was interrupted.

The upgrade goes as follows:
- link all old files to their new names.
- set <admindir>/info/format-new to 1.
- remove all old files.
- rename <admindir>/info/format-new to <admindir>/info/format.

In case of abrupt interruption, the presence of <admindir>/info/format-new
means the upgrade is not yet completed and it needs to be retried. In case
of clean interruption with rollback, that file is removed after the old
layout has been restored.

Based-on-patch-by: Raphaël Hertzog <hertzog@debian.org>
Patch-sponsored-by: Linaro Limited

Designed-by: Guillem Jover <guillem@debian.org>
Signed-off-by: Guillem Jover <guillem@debian.org>
Guillem Jover 14 years ago
parent
commit
81fbbcec49
7 changed files with 259 additions and 0 deletions
  1. 1 0
      po/POTFILES.in
  2. 1 0
      src/Makefile.am
  3. 3 0
      src/archives.c
  4. 247 0
      src/infodb-upgrade.c
  5. 1 0
      src/infodb.h
  6. 3 0
      src/packages.c
  7. 3 0
      src/select.c

+ 1 - 0
po/POTFILES.in

@@ -53,6 +53,7 @@ src/filesdb.c
 src/help.c
 src/infodb-access.c
 src/infodb-format.c
+src/infodb-upgrade.c
 src/main.c
 src/packages.c
 src/processarc.c

+ 1 - 0
src/Makefile.am

@@ -41,6 +41,7 @@ dpkg_SOURCES = \
 	filters.c filters.h \
 	infodb-access.c \
 	infodb-format.c \
+	infodb-upgrade.c \
 	divertdb.c \
 	statdb.c \
 	help.c \

+ 3 - 0
src/archives.c

@@ -65,6 +65,7 @@
 #include "main.h"
 #include "archives.h"
 #include "filters.h"
+#include "infodb.h"
 
 static inline void
 fd_writeback_init(int fd)
@@ -1450,6 +1451,8 @@ archivefiles(const char *const *argv)
                  msdbrw_available_write);
 
   checkpath();
+  pkg_infodb_upgrade();
+
   log_message("startup archives %s", cipaction->olong);
 
   if (f_recursive) {

+ 247 - 0
src/infodb-upgrade.c

@@ -0,0 +1,247 @@
+/*
+ * dpkg - main program for package management
+ * infodb-upgrade.c - package control information database format upgrade
+ *
+ * Copyright © 1995 Ian Jackson <ian@chiark.greenend.org.uk>
+ * Copyright © 2011 Guillem Jover <guillem@debian.org>
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ *
+ * This 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 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.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/path.h>
+#include <dpkg/dir.h>
+
+#include "filesdb.h"
+#include "infodb.h"
+
+struct rename_node {
+	struct rename_node *next;
+	char *old;
+	char *new;
+};
+
+/* Global variables. */
+static struct rename_node *rename_head = NULL;
+
+static struct rename_node *
+rename_node_new(const char *old, const char *new, struct rename_node *next)
+{
+	struct rename_node *node;
+
+	node = m_malloc(sizeof(*node));
+	node->next = next;
+	node->old = m_strdup(old);
+	node->new = m_strdup(new);
+
+	return node;
+}
+
+static void
+rename_node_free(struct rename_node *node)
+{
+	free(node->old);
+	free(node->new);
+	free(node);
+}
+
+static void
+pkg_infodb_link_multiarch_files(void)
+{
+	DIR *db_dir;
+	struct dirent *db_de;
+	struct varbuf pkgname = VARBUF_INIT;
+	struct varbuf oldname = VARBUF_INIT;
+	struct varbuf newname = VARBUF_INIT;
+	size_t db_path_len;
+
+	varbuf_add_str(&oldname, pkgadmindir());
+	varbuf_add_char(&oldname, '/');
+	db_path_len = oldname.used;
+	varbuf_end_str(&oldname);
+
+	varbuf_add_buf(&newname, oldname.buf, oldname.used);
+	varbuf_end_str(&newname);
+
+	db_dir = opendir(oldname.buf);
+	if (!db_dir)
+		ohshite(_("cannot read info directory"));
+
+	push_cleanup(cu_closedir, ~0, NULL, 0, 1, (void *)db_dir);
+	while ((db_de = readdir(db_dir)) != NULL) {
+		const char *filetype, *dot;
+		struct pkginfo *pkg;
+		struct pkgset *set;
+
+		/* Ignore dotfiles, including ‘.’ and ‘..’. */
+		if (db_de->d_name[0] == '.')
+			continue;
+
+		/* Ignore anything odd. */
+		dot = strrchr(db_de->d_name, '.');
+		if (dot == NULL)
+			continue;
+
+		varbuf_reset(&pkgname);
+		varbuf_add_buf(&pkgname, db_de->d_name, dot - db_de->d_name);
+		varbuf_end_str(&pkgname);
+
+		 /* Skip files already converted. */
+		if (strchr(pkgname.buf, ':'))
+			continue;
+
+		set = pkg_db_find_set(pkgname.buf);
+		for (pkg = &set->pkg; pkg; pkg = pkg->arch_next)
+			if (pkg->status != stat_notinstalled)
+				break;
+		if (!pkg) {
+			warning(_("Info file %s/%s not associated to any package"),
+			        pkgadmindir(), db_de->d_name);
+			continue;
+		}
+
+		/* Does it need to be upgraded? */
+		if (pkg->installed.multiarch != multiarch_same)
+			continue;
+
+		/* Skip past the full stop. */
+		filetype = dot + 1;
+
+		varbuf_trunc(&oldname, db_path_len);
+		varbuf_add_str(&oldname, db_de->d_name);
+		varbuf_end_str(&oldname);
+
+		varbuf_trunc(&newname, db_path_len);
+		varbuf_add_pkgbin_name(&newname, pkg, &pkg->installed, pnaw_always);
+		varbuf_add_char(&newname, '.');
+		varbuf_add_str(&newname, filetype);
+		varbuf_end_str(&newname);
+
+		if (link(oldname.buf, newname.buf) && errno != EEXIST)
+			ohshite(_("error creating hard link `%.255s'"),
+			        newname.buf);
+		rename_head = rename_node_new(oldname.buf, newname.buf, rename_head);
+	}
+	pop_cleanup(ehflag_normaltidy); /* closedir */
+
+	varbuf_destroy(&newname);
+	varbuf_destroy(&oldname);
+}
+
+static void
+cu_abort_db_upgrade(int argc, void **argv)
+{
+	struct atomic_file *file = argv[0];
+	struct rename_node *next;
+
+	/* Restore the old files if needed and drop the newly created files. */
+	while (rename_head) {
+		next = rename_head->next;
+		if (link(rename_head->new, rename_head->old) && errno != EEXIST)
+			ohshite(_("error creating hard link `%.255s'"),
+			        rename_head->old);
+		if (unlink(rename_head->new))
+			ohshite(_("cannot remove `%.250s'"), rename_head->new);
+		rename_node_free(rename_head);
+		rename_head = next;
+	}
+	if (unlink(file->name_new) && errno != ENOENT)
+		ohshite(_("cannot remove `%.250s'"), file->name_new);
+
+	atomic_file_free(file);
+}
+
+static void
+pkg_infodb_write_format(struct atomic_file *file, int version)
+{
+	if (fprintf(file->fp, "%d\n", version) < 0)
+		ohshite(_("error while writing '%s'"), file->name_new);
+
+	atomic_file_sync(file);
+	atomic_file_close(file);
+	dir_sync_path_parent(file->name);
+
+	pkg_infodb_set_format(version);
+}
+
+static void
+pkg_infodb_unlink_monoarch_files(void)
+{
+	struct rename_node *next;
+
+	while (rename_head) {
+		next = rename_head->next;
+		if (unlink(rename_head->old))
+			ohshite(_("cannot remove `%.250s'"), rename_head->old);
+		rename_node_free(rename_head);
+		rename_head = next;
+	}
+}
+
+static void
+pkg_infodb_upgrade_to_multiarch(void)
+{
+	struct atomic_file *db_file;
+	char *db_format_file;
+
+	db_format_file = dpkg_db_get_path(INFODIR "/format");
+	db_file = atomic_file_new(db_format_file, 0);
+	atomic_file_open(db_file);
+
+	push_cleanup(cu_abort_db_upgrade, ehflag_bombout, NULL, 0, 1, db_file);
+
+	pkg_infodb_link_multiarch_files();
+	pkg_infodb_write_format(db_file, 1);
+
+	pkg_infodb_unlink_monoarch_files();
+	atomic_file_commit(db_file);
+	dir_sync_path(pkgadmindir());
+
+	pop_cleanup(ehflag_normaltidy);
+
+	atomic_file_free(db_file);
+	free(db_format_file);
+}
+
+/**
+ * Upgrade the infodb if there's the need and possibility.
+ *
+ * Currently this implies, that the modstatdb was opened for writing and:
+ * - previous upgrade has not been completed; or
+ * - current format is not the latest one.
+ */
+void
+pkg_infodb_upgrade(void)
+{
+	if (modstatdb_get_status() < msdbrw_write)
+		return;
+
+	if (pkg_infodb_get_format() < pkg_infodb_format_multiarch ||
+	    pkg_infodb_is_upgrading())
+		pkg_infodb_upgrade_to_multiarch();
+}

+ 1 - 0
src/infodb.h

@@ -34,6 +34,7 @@ enum pkg_infodb_format {
 enum pkg_infodb_format pkg_infodb_get_format(void);
 void pkg_infodb_set_format(enum pkg_infodb_format format);
 bool pkg_infodb_is_upgrading(void);
+void pkg_infodb_upgrade(void);
 
 bool pkg_infodb_has_file(struct pkginfo *pkg, struct pkgbin *pkgbin,
                          const char *name);

+ 3 - 0
src/packages.c

@@ -44,6 +44,7 @@
 #include <dpkg/options.h>
 
 #include "filesdb.h"
+#include "infodb.h"
 #include "main.h"
 
 static struct pkginfo *progress_bytrigproc;
@@ -134,6 +135,8 @@ packages(const char *const *argv)
                  fc_nonroot ? msdbrw_write :
                               msdbrw_needsuperuser);
   checkpath();
+  pkg_infodb_upgrade();
+
   log_message("startup packages %s", cipaction->olong);
 
   if (f_pending) {

+ 3 - 0
src/select.c

@@ -39,6 +39,7 @@
 #include <dpkg/options.h>
 
 #include "filesdb.h"
+#include "infodb.h"
 #include "main.h"
 
 static void getsel1package(struct pkginfo *pkg) {
@@ -116,6 +117,7 @@ setselections(const char *const *argv)
     badusage(_("--%s takes no arguments"), cipaction->olong);
 
   modstatdb_open(msdbrw_write | msdbrw_available_readonly);
+  pkg_infodb_upgrade();
 
   lno= 1;
   for (;;) {
@@ -185,6 +187,7 @@ clearselections(const char *const *argv)
     badusage(_("--%s takes no arguments"), cipaction->olong);
 
   modstatdb_open(msdbrw_write);
+  pkg_infodb_upgrade();
 
   it = pkg_db_iter_new();
   while ((pkg = pkg_db_iter_next_pkg(it))) {