From 854ad168370000ca46f4ba091c2d5bb05274b6ef Mon Sep 17 00:00:00 2001 From: Frank Lichtenheld Date: Fri, 10 Feb 2006 16:22:50 +0000 Subject: 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. --- 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 ++ 7 files changed, 193 insertions(+), 74 deletions(-) (limited to 'src') diff --git a/src/archives.c b/src/archives.c index 78daddc4f..7b1e96957 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 6141b6933..60b3aae38 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 3abaa205d..912dea12d 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 2514ea92a..a8b8c2008 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 44a4fd631..8e9731b51 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 56536e2d2..55b4683b5 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 be7c71c8b..86b993929 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')", -- cgit v1.2.3