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

Bug#2828: [PATCH] check dev/inode before removing old files on upgrade



This fixes one of the major bugs in dpkg, IMO. Referenced in #2828 and
others over time. Basically this is the symlink problem in dpkg which
forced the kludge we are now using on the /usr/doc upgrade.

Normally when dpkg unpacks files, it recognizes new files based on file
names alone in the packages .list. This breaks when a file in package "a_1"
is actually the same as then one in package "a_2" (same package, different
versions), but are referenced differently because of directory symlinks
on the system. Eg. if /usr/doc were a symlink to /usr/share/doc and package
"a_1" used /usr/doc, but "a_2" used /usr/share/doc, dpkg would install the
new doc files (/usr/share/doc/changelog.gz) but then remove the common files
when getting rid of the files from "a_1" that it thought were not in the
newer version (/usr/doc/changelog.gz, which follow to the same file).

The work around is for dpkg to check each file it removes on upgrading the
package against the files in the new version, by stat'ing them and comparing
dev/inode. If there is a match, dpkg leaves it alone since they are the
same file. This is further sped up by only having to stat the new files once
on the first loop, so for more than one old file, there is not much performance
loss (barely measurable using `time').

Enjoy,
  Ben
diff -urN dpkg-1.4.1.13.old/main/filesdb.c dpkg-1.4.1.13/main/filesdb.c
--- dpkg-1.4.1.13.old/main/filesdb.c	Thu Sep  9 21:33:14 1999
+++ dpkg-1.4.1.13/main/filesdb.c	Sun Oct 10 21:53:15 1999
@@ -515,6 +515,7 @@
       for (fnn= bins[i]; fnn; fnn= fnn->next) {
         fnn->flags= 0;
         fnn->oldhash= 0;
+	fnn->stat= 0;
       }
     break;
   case -1:
@@ -523,6 +524,7 @@
          fnn= fnn->next) {
       fnn->flags= 0;
       fnn->oldhash= 0;
+      fnn->stat= 0;
     }
     break;
   default:
diff -urN dpkg-1.4.1.13.old/main/filesdb.h dpkg-1.4.1.13/main/filesdb.h
--- dpkg-1.4.1.13.old/main/filesdb.h	Sun Oct 25 17:25:40 1998
+++ dpkg-1.4.1.13/main/filesdb.h	Sun Oct 10 21:53:15 1999
@@ -62,6 +62,7 @@
     fnnf_no_atomic_overwrite= 000020, /* >=1 instance is a dir, cannot rename over */
   } flags; /* Set to zero when a new node is created. */
   const char *oldhash; /* valid iff this namenode is in the newconffiles list */
+  struct stat *stat;
 };
  
 struct fileinlist {
diff -urN dpkg-1.4.1.13.old/main/processarc.c dpkg-1.4.1.13/main/processarc.c
--- dpkg-1.4.1.13.old/main/processarc.c	Thu Sep  9 19:56:34 1999
+++ dpkg-1.4.1.13/main/processarc.c	Sun Oct 10 21:53:15 1999
@@ -473,6 +473,9 @@
    *  - The listed thing does not exist.  We ignore it.
    *  - The listed thing is a directory or a symlink to a directory.
    *    We delete it only if it isn't listed in any other package.
+   *  - The listed thing is not a directory, but was part of the package
+   *    that was upgraded, we check to make sure the files aren't the
+   *    same ones from the old package by checking dev/inode
    *  - The listed thing is not a directory or a symlink to one (ie,
    *    it's a plain file, device, pipe, &c, or a symlink to one, or a
    *    dangling symlink).  We delete it.
@@ -569,6 +572,30 @@
     if (!rmdir(fnamevb.buf)) continue;
     if (errno == ENOENT || errno == ELOOP) continue;
     if (errno == ENOTDIR) {
+      /* Ok, it's an old file, but is it really not in the new package?
+       * We need to check to make sure, so we stat the file, then compare
+       * it to the new list. If we find a dev/inode match, we assume they
+       * are the same file, and leave it alone. NOTE: we don't check in
+       * other packages for sanity reasons.
+       */
+      struct stat oldfs, *newfs;
+      int donotrm = 0;
+      /* If we can't stat the old or new file, or it's a directory,
+       * we leave it up to the normal code
+       */
+      if (!lstat(fnamevb.buf, &oldfs) && !S_ISDIR(oldfs.st_mode)) {
+	for (cfile = newfileslist; cfile; cfile = cfile->next) {
+	  if(!cfile->namenode->stat) {
+	    newfs = nfmalloc(sizeof(struct stat));
+	    if (lstat(cfile->namenode->name, newfs)) continue;
+	    cfile->namenode->stat = newfs;
+	  } else newfs = cfile->namenode->stat;
+	  if (!S_ISDIR(newfs->st_mode) && oldfs.st_dev == newfs->st_dev &&
+	      oldfs.st_ino == newfs->st_ino)
+	    donotrm = 1;
+	}
+      }
+      if (donotrm) continue;
       if (!unlink(fnamevb.buf)) continue;
       if (errno == ENOTDIR) continue;
     }

Attachment: pgpxGyLREXdQj.pgp
Description: PGP signature


Reply to: