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