Procházet zdrojové kódy

dpkg: fix metadata installation by not mixing rename() in a readdir() loop

dpkg's process_archive() was doing the improper assumption that a
readdir() loop would not return the same filename twice even when the
scanned directory has files renamed into it (coming from tmp.ci).

The net result of having the same filename returned twice is that the
the second time the updated file to install is no longer there and
thus dpkg removed the current metadata file believing that it was
obsolete. btrfs triggers this bug consistently.

All other readdir() occurrences have been reviewed as well for similar
problems. But they are all safe, they mainly unlink() files rather
than adding new files into the scanned directory.

Thanks to Carey Underwood and Chris Mason for their help in diagnosing
this problem.

Acked-by: Guillem Jover <guillem@debian.org>
Raphaël Hertzog před 16 roky
rodič
revize
18b12083b5
2 změnil soubory, kde provedl 35 přidání a 7 odebrání
  1. 3 0
      debian/changelog
  2. 32 7
      src/processarc.c

+ 3 - 0
debian/changelog

@@ -14,6 +14,9 @@ dpkg (1.15.6.2) UNRELEASED; urgency=low
     do not include that file in the generated source package.
   * Improve explanation of --all option in dpkg-parsechangelog(1). Thanks to
     Jari Aalto. Closes: #575706
+  * Fix dpkg to not lose package metadata on filesystems where readdir()
+    returns new files added after the opendir() call, btrfs in particular
+    triggered the problematic behaviour. Closes: #575891
 
   [ Updated dpkg translations ]
   * German (Sven Joachim).

+ 32 - 7
src/processarc.c

@@ -52,6 +52,12 @@
 #include "main.h"
 #include "archives.h"
 
+struct rename_list {
+  struct rename_list *next;
+  char *src;
+  char *dst;
+};
+
 static const char *
 summarize_filename(const char *filename)
 {
@@ -118,6 +124,7 @@ void process_archive(const char *filename) {
   struct dirent *de;
   struct stat stab, oldfs;
   struct pkg_deconf_list *deconpil, *deconpiltemp;
+  struct rename_list *rename_head = NULL, *rename_node = NULL;
   
   cleanup_pkg_failed= cleanup_conflictor_failed= 0;
   admindirlen= strlen(admindir);
@@ -850,19 +857,37 @@ void process_archive(const char *filename) {
     varbufaddstr(&infofnvb,de->d_name);
     varbufaddc(&infofnvb,0);
     strcpy(cidirrest,p);
-    if (!rename(cidir,infofnvb.buf)) {
+    /* We keep files to rename in a list as doing the rename immediately
+     * might influence the current readdir(), the just renamed file might
+     * be returned a second time as it's actually a new file from the
+     * point of view of the filesystem. */
+    rename_node = m_malloc(sizeof(*rename_node));
+    rename_node->next = rename_head;
+    rename_node->src = m_strdup(cidir);
+    rename_node->dst = m_strdup(infofnvb.buf);
+    rename_head = rename_node;
+  }
+  pop_cleanup(ehflag_normaltidy); /* closedir */
+
+  while ((rename_node = rename_head)) {
+    if (!rename(rename_node->src, rename_node->dst)) {
       debug(dbg_scripts, "process_archive info installed %s as %s",
-            cidir, infofnvb.buf);
+            rename_node->src, rename_node->dst);
     } else if (errno == ENOENT) {
       /* Right, no new version. */
-      if (unlink(infofnvb.buf))
-        ohshite(_("unable to remove obsolete info file `%.250s'"),infofnvb.buf);
-      debug(dbg_scripts, "process_archive info unlinked %s",infofnvb.buf);
+      if (unlink(rename_node->dst))
+        ohshite(_("unable to remove obsolete info file `%.250s'"),
+                rename_node->dst);
+      debug(dbg_scripts, "process_archive info unlinked %s", rename_node->dst);
     } else {
-      ohshite(_("unable to install (supposed) new info file `%.250s'"),cidir);
+      ohshite(_("unable to install (supposed) new info file `%.250s'"),
+              rename_node->src);
     }
+    rename_head = rename_node->next;
+    free(rename_node->src);
+    free(rename_node->dst);
+    free(rename_node);
   }
-  pop_cleanup(ehflag_normaltidy); /* closedir */
   
   *cidirrest = '\0'; /* the directory itself */
   dsd= opendir(cidir);