From 854ad168370000ca46f4ba091c2d5bb05274b6ef Mon Sep 17 00:00:00 2001 From: Frank Lichtenheld Date: Fri, 10 Feb 2006 16:22:50 +0000 Subject: [PATCH] Improve processing of disappearing conffiles (Ian Jackson). This is part of the fix for #108587. * lib/dpkg-db.h (conffile): Add `obsolete' field. * lib/dump.c (w_conffiles): Write "obsolete" at the end of conffile entry if obsolete is set. * lib/fields.c (f_conffiles): Parse entries for obsolete conffiles correctly. * src/filesdb.h (filenamenode.flags): Add new flag for obsolete conffiles. * src/remove.c (removal_bulk_remove_configfiles): Handle obsolete conffiles. * src/archives.c (newconff_append): New function to append a filenamenode to a fileinlist. (addfiletolist): New function to add a filenamenode to a tarcontext. (tarobject): Use new addfiletolist function. Handle case where a new package takes over an obsolete conffile from another package. * src/archives.h: Add declaration of the addfiletolist function. * src/processarc.c (process_archive): Use new newconff_append function from archives.c. Detect obsoleted conffiles and mark them as such. * src/help.c (chmodsafe_unlink): Make it possible to differentiate between failed chmod and failed unlink by adding a new `failed' argument which will be set to the name of the failed command. (chmodsafe_unlink_statted): New function that can be called if we already have a stat result for the file/directory to be removed. (ensure_pathname_nonexisting): Give better error messages by utilizing the changes to chmodsafe_unlink. * src/main.h: Reflect changes in archives.c and help.c (add declarations for newconff_append and chmodsafe_unlink_statted and change the one of chmodsafe_unlink). (conffopt): Add new isold flag. --- ChangeLog | 37 ++++++++++++ debian/changelog | 2 + lib/dpkg-db.h | 1 + lib/dump.c | 1 + lib/fields.c | 48 ++++++++++++--- src/archives.c | 56 +++++++++++++++-- src/archives.h | 3 + src/filesdb.h | 1 + src/help.c | 36 +++++++---- src/main.h | 13 ++-- src/processarc.c | 153 ++++++++++++++++++++++++++++++----------------- src/remove.c | 5 ++ 12 files changed, 273 insertions(+), 83 deletions(-) diff --git a/ChangeLog b/ChangeLog index c9a34d4d..efe517dd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,43 @@ a warning that was produced when trying to backup a non-existant file. + * lib/dpkg-db.h (conffile): Add `obsolete' field. + * lib/dump.c (w_conffiles): Write "obsolete" at the + end of conffile entry if obsolete is set. + * lib/fields.c (f_conffiles): Parse entries for + obsolete conffiles correctly. + * src/filesdb.h (filenamenode.flags): Add new + flag for obsolete conffiles. + * src/remove.c (removal_bulk_remove_configfiles): + Handle obsolete conffiles. + * src/archives.c (newconff_append): New function + to append a filenamenode to a fileinlist. + (addfiletolist): New function to add a filenamenode + to a tarcontext. + (tarobject): Use new addfiletolist function. + Handle case where a new package takes over + an obsolete conffile from another package. + * src/archives.h: Add declaration of the + addfiletolist function. + * src/processarc.c (process_archive): Use new + newconff_append function from archives.c. + Detect obsoleted conffiles and mark them as such. + * src/help.c (chmodsafe_unlink): Make it possible + to differentiate between failed chmod and failed + unlink by adding a new `failed' argument which + will be set to the name of the failed command. + (chmodsafe_unlink_statted): New function that + can be called if we already have a stat result for + the file/directory to be removed. + (ensure_pathname_nonexisting): Give better error + messages by utilizing the changes to + chmodsafe_unlink. + * src/main.h: Reflect changes in archives.c + and help.c (add declarations for newconff_append + and chmodsafe_unlink_statted and change the + one of chmodsafe_unlink). + (conffopt): Add new isold flag. + 2006-02-10 James R. Van Zandt * man/C/dpkg.1: Document the default log file. The behaviour in case diff --git a/debian/changelog b/debian/changelog index 1069813a..435b0453 100644 --- a/debian/changelog +++ b/debian/changelog @@ -44,6 +44,8 @@ dpkg (1.13.14~) UNRELEASED; urgency=low variables. * On package configuration, differentiate between modified and deleted configuration files (Ian Jackson). Closes: #351361 + * Improve processing of disappearing conffiles (Ian Jackson). + This is part of the fix for #108587. * Let dpkg-source -x touch all patched files to have the same timestamp to mitigate time-skew problems (Denis Barbier). Closes: #105750 diff --git a/lib/dpkg-db.h b/lib/dpkg-db.h index 6c512095..763c6956 100644 --- a/lib/dpkg-db.h +++ b/lib/dpkg-db.h @@ -84,6 +84,7 @@ struct conffile { struct conffile *next; const char *name; const char *hash; + int obsolete; }; struct filedetails { diff --git a/lib/dump.c b/lib/dump.c index 2b12ffd7..9e8bb0de 100644 --- a/lib/dump.c +++ b/lib/dump.c @@ -233,6 +233,7 @@ void w_conffiles(struct varbuf *vb, if (i!=pifp->conffiles) varbufaddc(vb,'\n'); varbufaddc(vb,' '); varbufaddstr(vb,i->name); varbufaddc(vb,' '); varbufaddstr(vb,i->hash); + if (i->obsolete) varbufaddstr(vb," obsolete"); } if (flags&fw_printheader) varbufaddc(vb,'\n'); diff --git a/lib/fields.c b/lib/fields.c index 9d79c14a..907ea0b9 100644 --- a/lib/fields.c +++ b/lib/fields.c @@ -224,13 +224,38 @@ void f_configversion(struct pkginfo *pigp, struct pkginfoperfile *pifp, "in Config-Version string `%.250s': %.250s"),value,emsg); } +void conffvalue_lastword(const char *value, const char *from, + const char *endent, + const char **word_start_r, int *word_len_r, + const char **new_from_r, + const char *filename, int lno, + FILE *warnto, int *warncount, struct pkginfo *pigp) { + /* the code in f_conffiles ensures that value[-1]==' ', which is helpful */ + const char *lastspc; + + if (from <= value+1) goto malformed; + for (lastspc= from-1; *lastspc != ' '; lastspc--); + if (lastspc <= value+1 || lastspc >= endent-1) goto malformed; + + *new_from_r= lastspc; + *word_start_r= lastspc + 1; + *word_len_r= (int)(from - *word_start_r); + return; + +malformed: + parseerr(NULL,filename,lno, warnto,warncount,pigp,0, + _("value for `conffiles' has malformatted line `%.*s'"), + (int)(endent-value > 250 ? 250 : endent-value), value); +} + void f_conffiles(struct pkginfo *pigp, struct pkginfoperfile *pifp, enum parsedbflags flags, const char *filename, int lno, FILE *warnto, int *warncount, const char *value, const struct fieldinfo *fip) { + static const char obsolete_str[]= "obsolete"; struct conffile **lastp, *newlink; - const char *endent, *endfn; - int c, namelen, hashlen; + const char *endent, *endfn, *hashstart; + int c, namelen, hashlen, obsolete; char *newptr; lastp= &pifp->conffiles; @@ -240,11 +265,15 @@ void f_conffiles(struct pkginfo *pigp, struct pkginfoperfile *pifp, if (c != ' ') parseerr(NULL,filename,lno, warnto,warncount,pigp,0, _("value for" " `conffiles' has line starting with non-space `%c'"), c); for (endent= value; (c= *endent)!=0 && c != '\n'; endent++); - for (endfn= endent; *endfn != ' '; endfn--); - if (endfn <= value+1 || endfn >= endent-1) - parseerr(NULL,filename,lno, warnto,warncount,pigp,0, - _("value for `conffiles' has malformatted line `%.*s'"), - (int)(endent-value > 250 ? 250 : endent-value), value); + conffvalue_lastword(value, endent, endent, + &hashstart, &hashlen, &endfn, + filename,lno,warnto,warncount,pigp); + obsolete= (hashlen == sizeof(obsolete_str)-1 && + !memcmp(hashstart, obsolete_str, hashlen)); + if (obsolete) + conffvalue_lastword(value, endfn, endent, + &hashstart, &hashlen, &endfn, + filename,lno,warnto,warncount,pigp); newlink= nfmalloc(sizeof(struct conffile)); value= skip_slash_dotslash(value); namelen= (int)(endfn-value); @@ -255,9 +284,10 @@ void f_conffiles(struct pkginfo *pigp, struct pkginfoperfile *pifp, memcpy(newptr+1,value,namelen); newptr[namelen+1]= 0; newlink->name= newptr; - hashlen= (int)(endent-endfn)-1; newptr= nfmalloc(hashlen+1); - memcpy(newptr,endfn+1,hashlen); newptr[hashlen]= 0; + newptr= nfmalloc(hashlen+1); + memcpy(newptr,hashstart,hashlen); newptr[hashlen]= 0; newlink->hash= newptr; + newlink->obsolete= obsolete; newlink->next =NULL; *lastp= newlink; lastp= &newlink->next; diff --git a/src/archives.c b/src/archives.c index 78daddc4..7b1e9695 100644 --- a/src/archives.c +++ b/src/archives.c @@ -315,14 +315,25 @@ int unlinkorrmdir(const char *filename) { errno= e; return r; } +struct fileinlist *addfiletolist(struct tarcontext *tc, + struct filenamenode *namenode) { + struct fileinlist *nifd; + + nifd= obstack_alloc(&tar_obs, sizeof(struct fileinlist)); + nifd->namenode= namenode; + nifd->next= 0; *tc->newfilesp= nifd; tc->newfilesp= &nifd->next; + return nifd; +} + int tarobject(struct TarInfo *ti) { static struct varbuf conffderefn, hardlinkfn, symlinkfn; const char *usename; - + + struct conffile *conff; struct tarcontext *tc= (struct tarcontext*)ti->UserData; int statr, fd, i, existingdirectory, keepexisting; size_t r; - struct stat stab, stabd; + struct stat stab, stabtmp; char databuf[TARBLKSZ]; struct fileinlist *nifd, **oldnifd; struct pkginfo *divpkg, *otherpkg; @@ -336,9 +347,7 @@ int tarobject(struct TarInfo *ti) { * been stripped by TarExtractor (lib/tarfn.c). */ oldnifd= tc->newfilesp; - nifd= obstack_alloc(&tar_obs, sizeof(struct fileinlist)); - nifd->namenode= findnamenode(ti->Name, 0); - nifd->next= 0; *tc->newfilesp= nifd; tc->newfilesp= &nifd->next; + nifd= addfiletolist(tc, findnamenode(ti->Name, 0)); nifd->namenode->flags |= fnnf_new_inarchive; debug(dbg_eachfile, @@ -417,7 +426,7 @@ int tarobject(struct TarInfo *ti) { break; case Directory: /* If it's already an existing directory, do nothing. */ - if (!stat(fnamevb.buf,&stabd) && S_ISDIR(stabd.st_mode)) { + if (!stat(fnamevb.buf,&stabtmp) && S_ISDIR(stabtmp.st_mode)) { debug(dbg_eachfiledetail,"tarobject Directory exists"); existingdirectory= 1; } @@ -463,6 +472,29 @@ int tarobject(struct TarInfo *ti) { if (otherpkg->status == stat_configfiles) continue; /* Perhaps we're removing a conflicting package ? */ if (otherpkg->clientdata->istobe == itb_remove) continue; + + /* Is the file an obsolete conffile in the other package + * and a conffile in the new package ? */ + if ((nifd->namenode->flags & fnnf_new_conff) && + !statr && S_ISREG(stab.st_mode)) { + for (conff= otherpkg->installed.conffiles; + conff; + conff= conff->next) { + if (!conff->obsolete) + continue; + if (stat(conff->name, &stabtmp)) + if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP) + continue; + if (stabtmp.st_dev == stab.st_dev && + stabtmp.st_ino == stab.st_ino) + break; + } + if (conff) + debug(dbg_eachfiledetail,"tarobject other's obsolete conffile"); + /* processarc.c will have copied its hash already. */ + continue; + } + if (does_replace(tc->pkg,&tc->pkg->available,otherpkg)) { printf(_("Replacing files in old package %s ...\n"),otherpkg->name); otherpkg->clientdata->replacingfilesandsaid= 1; @@ -1103,5 +1135,17 @@ int wanttoinstall(struct pkginfo *pkg, const struct versionrevision *ver, int sa } } +struct fileinlist *newconff_append(struct fileinlist ***newconffileslastp_io, + struct filenamenode *namenode) { + struct fileinlist *newconff; + + newconff= m_malloc(sizeof(struct fileinlist)); + newconff->next= 0; + newconff->namenode= namenode; + **newconffileslastp_io= newconff; + *newconffileslastp_io= &newconff->next; + return newconff; +} + /* vi: ts=8 sw=2 */ diff --git a/src/archives.h b/src/archives.h index 6141b693..60b3aae3 100644 --- a/src/archives.h +++ b/src/archives.h @@ -66,6 +66,9 @@ int filesavespackage(struct fileinlist*, struct pkginfo*, void check_conflict(struct dependency *dep, struct pkginfo *pkg, const char *pfilename); +struct fileinlist *addfiletolist(struct tarcontext *tc, + struct filenamenode *namenode); + extern int cleanup_pkg_failed, cleanup_conflictor_failed; #endif /* ARCHIVES_H */ diff --git a/src/filesdb.h b/src/filesdb.h index 3abaa205..912dea12 100644 --- a/src/filesdb.h +++ b/src/filesdb.h @@ -65,6 +65,7 @@ struct filenamenode { fnnf_new_conff= 000001, /* in the newconffiles list */ fnnf_new_inarchive= 000002, /* in the new filesystem archive */ fnnf_old_conff= 000004, /* in the old package's conffiles list */ + fnnf_obs_conff= 000100, /* obsolete conffile */ fnnf_elide_other_lists= 000010, /* must remove from other packages' lists */ fnnf_no_atomic_overwrite= 000020, /* >=1 instance is a dir, cannot rename over */ fnnf_placed_on_disk= 000040, /* new file has been placed on the disk */ diff --git a/src/help.c b/src/help.c index 2514ea92..a8b8c200 100644 --- a/src/help.c +++ b/src/help.c @@ -430,23 +430,35 @@ void oldconffsetflags(const struct conffile *searchconff) { while (searchconff) { namenode= findnamenode(searchconff->name, 0); /* XXX */ namenode->flags |= fnnf_old_conff; + if (!namenode->oldhash) + namenode->oldhash= searchconff->hash; debug(dbg_conffdetail, "oldconffsetflags `%s' namenode %p flags %o", searchconff->name, namenode, namenode->flags); searchconff= searchconff->next; } } -int chmodsafe_unlink(const char *pathname) { +int chmodsafe_unlink(const char *pathname, const char **failed) { + /* Sets *failed to `chmod' or `unlink' if those calls fail (which is + * always unexpected). If stat fails it leaves *failed alone. */ struct stat stab; if (lstat(pathname,&stab)) return -1; - if (S_ISREG(stab.st_mode) ? (stab.st_mode & 07000) : - !(S_ISLNK(stab.st_mode) || S_ISDIR(stab.st_mode) || - S_ISFIFO(stab.st_mode) || S_ISSOCK(stab.st_mode))) { + *failed= N_("unlink"); + return chmodsafe_unlink_statted(pathname, &stab, failed); +} + +int chmodsafe_unlink_statted(const char *pathname, const struct stat *stab, + const char **failed) { + /* Sets *failed to `chmod'' if that call fails (which is always + * unexpected). If unlink fails it leaves *failed alone. */ + if (S_ISREG(stab->st_mode) ? (stab->st_mode & 07000) : + !(S_ISLNK(stab->st_mode) || S_ISDIR(stab->st_mode) || + S_ISFIFO(stab->st_mode) || S_ISSOCK(stab->st_mode))) { /* We chmod it if it is 1. a sticky or set-id file, or 2. an unrecognised - * object (ie, not a file, link, directory, fifo or socket + * object (ie, not a file, link, directory, fifo or socket) */ - if (chmod(pathname,0600)) return -1; + if (chmod(pathname,0600)) { *failed= N_("chmod"); return -1; } } if (unlink(pathname)) return -1; return 0; @@ -454,7 +466,7 @@ int chmodsafe_unlink(const char *pathname) { void ensure_pathname_nonexisting(const char *pathname) { int c1; - const char *u; + const char *u, *failed; u= skip_slash_dotslash(pathname); assert(*u); @@ -462,15 +474,19 @@ void ensure_pathname_nonexisting(const char *pathname) { debug(dbg_eachfile,"ensure_pathname_nonexisting `%s'",pathname); if (!rmdir(pathname)) return; /* Deleted it OK, it was a directory. */ if (errno == ENOENT || errno == ELOOP) return; + failed= N_("delete"); if (errno == ENOTDIR) { /* Either it's a file, or one of the path components is. If one * of the path components is this will fail again ... */ - if (!chmodsafe_unlink(pathname)) return; /* OK, it was */ + if (!chmodsafe_unlink(pathname, &failed)) return; /* OK, it was */ if (errno == ENOTDIR) return; } - if (errno != ENOTEMPTY) /* Huh ? */ - ohshite(_("failed to rmdir/unlink `%.255s'"),pathname); + if (errno != ENOTEMPTY) { /* Huh ? */ + char mbuf[250]; + snprintf(mbuf, sizeof(mbuf), N_("failed to %s `%%.255s'"), failed); + ohshite(_(mbuf),pathname); + } c1= m_fork(); if (!c1) { execlp(RM,"rm","-rf","--",pathname,(char*)0); diff --git a/src/main.h b/src/main.h index 44a4fd63..8e9731b5 100644 --- a/src/main.h +++ b/src/main.h @@ -61,9 +61,10 @@ enum conffopt { cfof_prompt = 001, cfof_keep = 002, cfof_install = 004, - cfof_backup = 0100, - cfof_newconff = 0200, - cfof_isnew = 0400, + cfof_backup = 00100, + cfof_newconff = 00200, + cfof_isnew = 00400, + cfof_isold = 01000, cfom_main = 007, cfo_keep = cfof_keep, cfo_prompt_keep = cfof_keep | cfof_prompt, @@ -103,6 +104,8 @@ void filesdbinit(void); void archivefiles(const char *const *argv); void process_archive(const char *filename); int wanttoinstall(struct pkginfo *pkg, const struct versionrevision *ver, int saywhy); +struct fileinlist *newconff_append(struct fileinlist ***newconffileslastp_io, + struct filenamenode *namenode); /* from update.c */ @@ -179,7 +182,9 @@ void ensure_package_clientdata(struct pkginfo *pkg); const char *pkgadminfile(struct pkginfo *pkg, const char *whichfile); void oldconffsetflags(const struct conffile *searchconff); void ensure_pathname_nonexisting(const char *pathname); -int chmodsafe_unlink(const char *pathname); /* chmod 600, then unlink */ +int chmodsafe_unlink(const char *pathname, const char **failed); +int chmodsafe_unlink_statted(const char *pathname, const struct stat *stab, + const char **failed); void checkpath(void); struct filenamenode *namenodetouse(struct filenamenode*, struct pkginfo*); diff --git a/src/processarc.c b/src/processarc.c index 56536e2d..55b4683b 100644 --- a/src/processarc.c +++ b/src/processarc.c @@ -68,7 +68,7 @@ void process_archive(const char *filename) { struct pkginfo *pkg, *otherpkg, *divpkg; char *cidir, *cidirrest, *p; char *pfilenamebuf, conffilenamebuf[MAXCONFFILENAME]; - const char *pfilename, *newinfofilename; + const char *pfilename, *newinfofilename, *failed; struct fileinlist *newconff, **newconffileslastp; struct fileinlist *cfile; struct reversefilelistiter rlistit; @@ -81,7 +81,7 @@ void process_archive(const char *filename) { DIR *dsd; struct filenamenode *namenode; struct dirent *de; - struct stat stab; + struct stat stab, oldfs; struct packageinlist *deconpil, *deconpiltemp; cleanup_pkg_failed= cleanup_conflictor_failed= 0; @@ -313,12 +313,10 @@ void process_archive(const char *filename) { while (p > conffilenamebuf && isspace(p[-1])) --p; if (p == conffilenamebuf) continue; *p= 0; - newconff= m_malloc(sizeof(struct fileinlist)); - newconff->next= 0; - newconff->namenode= findnamenode(conffilenamebuf, 0); - *newconffileslastp= newconff; - newconffileslastp= &newconff->next; - newconff->namenode->oldhash= NEWCONFFILEFLAG; + namenode= findnamenode(conffilenamebuf, 0); + namenode->oldhash= NEWCONFFILEFLAG; + newconff= newconff_append(&newconffileslastp, namenode); + /* Let's see if any packages have this file. If they do we * check to see if they listed it as a conffile, and if they did * we copy the hash across. Since (for plain file conffiles, @@ -356,6 +354,7 @@ void process_archive(const char *filename) { xit_conff_hashcopy_srch: if (searchconff) { newconff->namenode->oldhash= searchconff->hash; + /* we don't copy `obsolete'; it's not obsolete in the new package */ } else { debug(dbg_conff,"process_archive conffile `%s' no package, no hash", newconff->namenode->name); @@ -523,8 +522,10 @@ void process_archive(const char *filename) { * package isn't one we're already processing, and the package's * list becomes empty as a result, we `vanish' the package. This * means that we run its postrm with the `disappear' argument, and - * put the package in the `not-installed' state. Its conffiles are - * ignored and forgotten about. + * put the package in the `not-installed' state. If it had any + * conffiles, their hashes and ownership will have been transferred + * already, so we just ignore those and forget about them from the + * point of view of the disappearing package. * * NOTE THAT THE OLD POSTRM IS RUN AFTER THE NEW PREINST, since the * files get replaced `as we go'. @@ -592,8 +593,7 @@ void process_archive(const char *filename) { */ reversefilelist_init(&rlistit,pkg->clientdata->files); while ((namenode= reversefilelist_next(&rlistit))) { - if ((namenode->flags & fnnf_old_conff) || - (namenode->flags & fnnf_new_conff) || + if ((namenode->flags & fnnf_new_conff) || (namenode->flags & fnnf_new_inarchive)) continue; if (!stat(namenode->name,&stab) && S_ISDIR(stab.st_mode)) { @@ -604,10 +604,30 @@ void process_archive(const char *filename) { fnamevb.used= fnameidlu; varbufaddstr(&fnamevb, namenodetouse(namenode,pkg)->name); varbufaddc(&fnamevb,0); - if (!rmdir(fnamevb.buf)) continue; - if (errno == ENOENT || errno == ELOOP) continue; - if (errno == ENOTDIR) { + + if (lstat(fnamevb.buf, &oldfs)) { + if (!(errno == ENOENT || errno == ELOOP || errno == ENOTDIR)) + fprintf(stderr, + _("dpkg: warning - could not stat old file `%.250s'" + " so not deleting it: %s"), + fnamevb.buf, strerror(errno)); + continue; + } + if (S_ISDIR(oldfs.st_mode)) { + if (rmdir(fnamevb.buf)) { + fprintf(stderr, + _("dpkg: warning - unable to delete old directory" + " `%.250s': %s\n"), namenode->name, strerror(errno)); + } else if ((namenode->flags & fnnf_old_conff)) { + fprintf(stderr, + _("dpkg: warning - old conffile `%.250s' was an empty" + " directory (and has now been deleted)\n"), + namenode->name); + } + } else { /* Ok, it's an old file, but is it really not in the new package? + * It might be known by a different name because of symlinks. + * * 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 @@ -618,54 +638,76 @@ void process_archive(const char *filename) { * the process a little leaner. We are only worried about new ones * since ones that stayed the same don't really apply here. */ - struct stat oldfs; - int donotrm = 0; + struct fileinlist *sameas= 0; /* If we can't stat the old or new file, or it's a directory, * we leave it up to the normal code */ debug(dbg_eachfile, "process_archive: checking %s for same files on " - "upgrade/downgrade", fnamevb.buf); - if (!lstat(fnamevb.buf, &oldfs) && !S_ISDIR(oldfs.st_mode)) { - for (cfile = newfileslist; cfile; cfile = cfile->next) { - if (!cfile->namenode->filestat) { - cfile->namenode->filestat = (struct stat *) nfmalloc(sizeof(struct stat)); - if (lstat(cfile->namenode->name, cfile->namenode->filestat)) { - cfile->namenode->filestat= 0; - continue; - } - } - if (S_ISDIR(cfile->namenode->filestat->st_mode)) + "upgrade/downgrade", fnamevb.buf); + + for (cfile= newfileslist; cfile; cfile= cfile->next) { + if (!cfile->namenode->filestat) { + cfile->namenode->filestat= nfmalloc(sizeof(struct stat)); + if (lstat(cfile->namenode->name, cfile->namenode->filestat)) { + if (!(errno == ENOENT || errno == ELOOP || errno == ENOTDIR)) + ohshite(_("unable to stat other new file `%.250s'"), + cfile->namenode->name); + memset(cfile->namenode->filestat, 0, + sizeof(cfile->namenode->filestat)); continue; - if (oldfs.st_dev == cfile->namenode->filestat->st_dev && - oldfs.st_ino == cfile->namenode->filestat->st_ino) { - donotrm = 1; - debug(dbg_eachfile, "process_archive: not removing %s, since it matches %s", - fnamevb.buf, cfile->namenode->name); } } - } else - debug(dbg_eachfile, "process_archive: could not stat %s, skipping", fnamevb.buf); - if (donotrm) continue; - { - /* - * If file to remove is a device or s[gu]id, change its mode - * so that a malicious user cannot use it even if it's linked - * to another file. - */ - struct stat stat_buf; - if (lstat(fnamevb.buf,&stat_buf)==0) { - if (S_ISCHR(stat_buf.st_mode) || S_ISBLK(stat_buf.st_mode)) - chmod(fnamevb.buf, 0); - if (stat_buf.st_mode & (S_ISUID|S_ISGID)) - chmod(fnamevb.buf, stat_buf.st_mode & ~(S_ISUID|S_ISGID)); + if (!cfile->namenode->filestat->st_mode) continue; + if (oldfs.st_dev == cfile->namenode->filestat->st_dev && + oldfs.st_ino == cfile->namenode->filestat->st_ino) { + if (sameas) + fprintf(stderr, _("dpkg: warning - old file `%.250s' is the same" + " as several new files! (both `%.250s' and `%.250s'"), + fnamevb.buf, + sameas->namenode->name, cfile->namenode->name); + sameas= cfile; + debug(dbg_eachfile, "process_archive: not removing %s," + " since it matches %s", fnamevb.buf, cfile->namenode->name); } } - if (!unlink(fnamevb.buf)) continue; - if (errno == ENOTDIR) continue; - } - fprintf(stderr, - _("dpkg: warning - unable to delete old file `%.250s': %s\n"), - namenode->name, strerror(errno)); + + if ((namenode->flags & fnnf_old_conff)) { + if (sameas) { + if (sameas->namenode->flags & fnnf_new_conff) { + if (!strcmp(sameas->namenode->oldhash, NEWCONFFILEFLAG)) { + sameas->namenode->oldhash= namenode->oldhash; + debug(dbg_eachfile, "process_archive: old conff %s" + " is same as new conff %s, copying hash", + namenode->name, sameas->namenode->name); + } else { + debug(dbg_eachfile, "process_archive: old conff %s" + " is same as new conff %s but latter already has hash", + namenode->name, sameas->namenode->name); + } + } + } else { + debug(dbg_eachfile, "process_archive: old conff %s" + " is disappearing", namenode->name); + namenode->flags |= fnnf_obs_conff; + newconff_append(&newconffileslastp, namenode); + addfiletolist(&tc, namenode); + } + continue; + } + + if (sameas) + continue; + + failed= N_("delete"); + if (chmodsafe_unlink_statted(fnamevb.buf, &oldfs, &failed)) { + char mbuf[250]; + snprintf(mbuf, sizeof(mbuf), + N_("dpkg: warning - unable to %s old file `%%.250s': %%s\n"), + failed); + fprintf(stderr, _(mbuf), namenode->name, strerror(errno)); + } + + } /* !S_ISDIR */ } /* OK, now we can write the updated files-in-this package list, @@ -827,6 +869,7 @@ void process_archive(const char *filename) { newiconff->next= 0; newiconff->name= nfstrsave(cfile->namenode->name); newiconff->hash= nfstrsave(cfile->namenode->oldhash); + newiconff->obsolete= !!(cfile->namenode->flags & fnnf_obs_conff); *iconffileslastp= newiconff; iconffileslastp= &newiconff->next; } @@ -1033,6 +1076,8 @@ void process_archive(const char *filename) { * * Note that we don't ever delete things that were in the old * package as a conffile and don't appear at all in the new. + * They stay recorded as obsolete conffiles and will eventually + * (if not taken over by another package) be forgotten. */ for (cfile= newfileslist; cfile; cfile= cfile->next) { if (cfile->namenode->flags & fnnf_new_conff) continue; diff --git a/src/remove.c b/src/remove.c index be7c71c8..86b99392 100644 --- a/src/remove.c +++ b/src/remove.c @@ -429,6 +429,11 @@ static void removal_bulk_remove_configfiles(struct pkginfo *pkg) { for (conff= pkg->installed.conffiles; conff; conff= conff->next) { static struct varbuf fnvb, removevb; + if (conff->obsolete) { + debug(dbg_conffdetail, "removal_bulk conffile obsolete %s", + conff->name); + continue; + } varbufreset(&fnvb); r= conffderef(pkg, &fnvb, conff->name); debug(dbg_conffdetail, "removal_bulk conffile `%s' (= `%s')", -- 2.39.5