From ea314d2f45c40a006c0104157013ab4b857f665f Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 20:35:28 +0200 Subject: Adding upstream version 1.22.4. Signed-off-by: Daniel Baumann --- src/main/archives.c | 1711 +++++++++++++++++++++++++++++++++++++++++++++++ src/main/archives.h | 99 +++ src/main/cleanup.c | 260 ++++++++ src/main/configure.c | 825 +++++++++++++++++++++++ src/main/depcon.c | 705 ++++++++++++++++++++ src/main/enquiry.c | 906 +++++++++++++++++++++++++ src/main/errors.c | 137 ++++ src/main/file-match.c | 49 ++ src/main/file-match.h | 35 + src/main/filters.c | 133 ++++ src/main/filters.h | 37 ++ src/main/help.c | 325 +++++++++ src/main/main.c | 781 ++++++++++++++++++++++ src/main/main.h | 304 +++++++++ src/main/packages.c | 760 +++++++++++++++++++++ src/main/perpkgstate.c | 43 ++ src/main/remove.c | 686 +++++++++++++++++++ src/main/script.c | 399 +++++++++++ src/main/select.c | 244 +++++++ src/main/trigproc.c | 576 ++++++++++++++++ src/main/unpack.c | 1734 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main/update.c | 124 ++++ src/main/verify.c | 243 +++++++ 23 files changed, 11116 insertions(+) create mode 100644 src/main/archives.c create mode 100644 src/main/archives.h create mode 100644 src/main/cleanup.c create mode 100644 src/main/configure.c create mode 100644 src/main/depcon.c create mode 100644 src/main/enquiry.c create mode 100644 src/main/errors.c create mode 100644 src/main/file-match.c create mode 100644 src/main/file-match.h create mode 100644 src/main/filters.c create mode 100644 src/main/filters.h create mode 100644 src/main/help.c create mode 100644 src/main/main.c create mode 100644 src/main/main.h create mode 100644 src/main/packages.c create mode 100644 src/main/perpkgstate.c create mode 100644 src/main/remove.c create mode 100644 src/main/script.c create mode 100644 src/main/select.c create mode 100644 src/main/trigproc.c create mode 100644 src/main/unpack.c create mode 100644 src/main/update.c create mode 100644 src/main/verify.c (limited to 'src/main') diff --git a/src/main/archives.c b/src/main/archives.c new file mode 100644 index 0000000..015bb23 --- /dev/null +++ b/src/main/archives.c @@ -0,0 +1,1711 @@ +/* + * dpkg - main program for package management + * archives.c - actions that process archive files, mainly unpack + * + * Copyright © 1994,1995 Ian Jackson + * Copyright © 2000 Wichert Akkerman + * Copyright © 2007-2015 Guillem Jover + * Copyright © 2011 Linaro Limited + * Copyright © 2011 Raphaël Hertzog + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define obstack_chunk_alloc m_malloc +#define obstack_chunk_free free + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "archives.h" +#include "filters.h" + +static inline void +fd_writeback_init(int fd) +{ + /* Ignore the return code as it should be considered equivalent to an + * asynchronous hint for the kernel, we are doing an fsync() later on + * anyway. */ +#if defined(SYNC_FILE_RANGE_WRITE) + sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WRITE); +#elif defined(HAVE_POSIX_FADVISE) + posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED); +#endif +} + +static struct obstack tar_pool; +static bool tar_pool_init = false; + +/** + * Allocate memory from the tar memory pool. + */ +static void * +tar_pool_alloc(size_t size) +{ + if (!tar_pool_init) { + obstack_init(&tar_pool); + tar_pool_init = true; + } + + /* cppcheck-suppress[nullPointerArithmetic]: + * False positive, imported module. */ + return obstack_alloc(&tar_pool, size); +} + +/** + * Free memory from the tar memory pool. + */ +static void +tar_pool_free(void *ptr) +{ + obstack_free(&tar_pool, ptr); +} + +/** + * Release the tar memory pool. + */ +static void +tar_pool_release(void) +{ + if (tar_pool_init) { + /* cppcheck-suppress[nullPointerArithmetic,pointerLessThanZero]: + * False positive, imported module. */ + obstack_free(&tar_pool, NULL); + tar_pool_init = false; + } +} + +struct fsys_namenode_list * +tar_fsys_namenode_queue_push(struct fsys_namenode_queue *queue, + struct fsys_namenode *namenode) +{ + struct fsys_namenode_list *node; + + node = tar_pool_alloc(sizeof(*node)); + node->namenode = namenode; + node->next = NULL; + + *queue->tail = node; + queue->tail = &node->next; + + return node; +} + +static void +tar_fsys_namenode_queue_pop(struct fsys_namenode_queue *queue, + struct fsys_namenode_list **tail_prev, + struct fsys_namenode_list *node) +{ + tar_pool_free(node); + queue->tail = tail_prev; + *tail_prev = NULL; +} + +/** + * Check if a file or directory will save a package from disappearance. + * + * A package can only be saved by a file or directory which is part + * only of itself - it must be neither part of the new package being + * installed nor part of any 3rd package (this is important so that + * shared directories don't stop packages from disappearing). + */ +bool +filesavespackage(struct fsys_namenode_list *file, + struct pkginfo *pkgtobesaved, + struct pkginfo *pkgbeinginstalled) +{ + struct fsys_node_pkgs_iter *iter; + struct pkginfo *thirdpkg; + + debug(dbg_eachfiledetail, "filesavespackage file '%s' package %s", + file->namenode->name, pkg_name(pkgtobesaved, pnaw_always)); + + /* If the file is a contended one and it's overridden by either + * the package we're considering disappearing or the package + * we're installing then they're not actually the same file, so + * we can't disappear the package - it is saved by this file. */ + if (file->namenode->divert && file->namenode->divert->useinstead) { + struct pkgset *divpkgset; + + divpkgset = file->namenode->divert->pkgset; + if (divpkgset == pkgtobesaved->set || divpkgset == pkgbeinginstalled->set) { + debug(dbg_eachfiledetail,"filesavespackage ... diverted -- save!"); + return true; + } + } + /* Is the file in the package being installed? If so then it can't save. */ + if (file->namenode->flags & FNNF_NEW_INARCHIVE) { + debug(dbg_eachfiledetail,"filesavespackage ... in new archive -- no save"); + return false; + } + /* Look for a 3rd package which can take over the file (in case + * it's a directory which is shared by many packages. */ + iter = fsys_node_pkgs_iter_new(file->namenode); + while ((thirdpkg = fsys_node_pkgs_iter_next(iter))) { + debug(dbg_eachfiledetail, "filesavespackage ... also in %s", + pkg_name(thirdpkg, pnaw_always)); + + /* Is this not the package being installed or the one being + * checked for disappearance? */ + if (thirdpkg == pkgbeinginstalled || thirdpkg == pkgtobesaved) + continue; + + /* A Multi-Arch: same package can share files and their presence in a + * third package of the same set is not a sign that we can get rid of + * it. */ + if (pkgtobesaved->installed.multiarch == PKG_MULTIARCH_SAME && + thirdpkg->set == pkgtobesaved->set) + continue; + + debug(dbg_eachfiledetail,"filesavespackage ... is 3rd package"); + + /* If !files_list_valid then we have already disappeared this one, + * so we should not try to make it take over this shared directory. */ + if (!thirdpkg->files_list_valid) { + debug(dbg_eachfiledetail, "process_archive ... already disappeared!"); + continue; + } + + /* We've found a package that can take this file. */ + debug(dbg_eachfiledetail, "filesavespackage ... taken -- no save"); + fsys_node_pkgs_iter_free(iter); + return false; + } + fsys_node_pkgs_iter_free(iter); + + debug(dbg_eachfiledetail, "filesavespackage ... not taken -- save !"); + return true; +} + +static void +md5hash_prev_conffile(struct pkginfo *pkg, char *oldhash, const char *oldname, + struct fsys_namenode *namenode) +{ + struct pkginfo *otherpkg; + struct conffile *conff; + + debug(dbg_conffdetail, "tarobject looking for shared conffile %s", + namenode->name); + + for (otherpkg = &pkg->set->pkg; otherpkg; otherpkg = otherpkg->arch_next) { + if (otherpkg == pkg) + continue; + /* If we are reinstalling, even if the other package is only unpacked, + * we can always make use of the Conffiles hash value from an initial + * installation, if that happened at all. */ + if (otherpkg->status <= PKG_STAT_UNPACKED && + dpkg_version_compare(&otherpkg->installed.version, + &otherpkg->configversion) != 0) + continue; + for (conff = otherpkg->installed.conffiles; conff; conff = conff->next) { + if (conff->obsolete || conff->remove_on_upgrade) + continue; + if (strcmp(conff->name, namenode->name) == 0) + break; + } + if (conff) { + strcpy(oldhash, conff->hash); + debug(dbg_conffdetail, + "tarobject found shared conffile, from pkg %s (%s); digest=%s", + pkg_name(otherpkg, pnaw_always), + pkg_status_name(otherpkg), oldhash); + break; + } + } + + /* If no package was found with a valid Conffiles field, we make the + * risky assumption that the hash of the current .dpkg-new file is + * the one of the previously unpacked package. */ + if (otherpkg == NULL) { + md5hash(pkg, oldhash, oldname); + debug(dbg_conffdetail, + "tarobject found shared conffile, from disk; digest=%s", oldhash); + } +} + +void cu_pathname(int argc, void **argv) { + path_remove_tree((char*)(argv[0])); +} + +int +tarfileread(struct tar_archive *tar, char *buf, int len) +{ + struct tarcontext *tc = (struct tarcontext *)tar->ctx; + int n; + + n = fd_read(tc->backendpipe, buf, len); + if (n < 0) + ohshite(_("error reading from dpkg-deb pipe")); + return n; +} + +static void +tarobject_skip_padding(struct tarcontext *tc, struct tar_entry *te) +{ + struct dpkg_error err; + size_t remainder; + + remainder = te->size % TARBLKSZ; + if (remainder == 0) + return; + + if (fd_skip(tc->backendpipe, TARBLKSZ - remainder, &err) < 0) + ohshit(_("cannot skip padding for file '%.255s': %s"), te->name, err.str); +} + +static void +tarobject_skip_entry(struct tarcontext *tc, struct tar_entry *ti) +{ + /* We need to advance the tar file to the next object, so read the + * file data and set it to oblivion. */ + if (ti->type == TAR_FILETYPE_FILE) { + struct dpkg_error err; + + if (fd_skip(tc->backendpipe, ti->size, &err) < 0) + ohshit(_("cannot skip file '%.255s' (replaced or excluded?) from pipe: %s"), + ti->name, err.str); + tarobject_skip_padding(tc, ti); + } +} + +struct varbuf_state fname_state; +struct varbuf_state fnametmp_state; +struct varbuf_state fnamenew_state; +struct varbuf fnamevb; +struct varbuf fnametmpvb; +struct varbuf fnamenewvb; +struct pkg_deconf_list *deconfigure = NULL; + +static time_t currenttime; + +static int +does_replace(struct pkginfo *new_pkg, struct pkgbin *new_pkgbin, + struct pkginfo *old_pkg, struct pkgbin *old_pkgbin) +{ + struct dependency *dep; + + debug(dbg_depcon,"does_replace new=%s old=%s (%s)", + pkgbin_name(new_pkg, new_pkgbin, pnaw_always), + pkgbin_name(old_pkg, old_pkgbin, pnaw_always), + versiondescribe_c(&old_pkgbin->version, vdew_always)); + for (dep = new_pkgbin->depends; dep; dep = dep->next) { + if (dep->type != dep_replaces || dep->list->ed != old_pkg->set) + continue; + debug(dbg_depcondetail,"does_replace ... found old, version %s", + versiondescribe_c(&dep->list->version,vdew_always)); + if (!versionsatisfied(old_pkgbin, dep->list)) + continue; + /* The test below can only trigger if dep_replaces start having + * arch qualifiers different from “any”. */ + if (!archsatisfied(old_pkgbin, dep->list)) + continue; + debug(dbg_depcon,"does_replace ... yes"); + return true; + } + debug(dbg_depcon,"does_replace ... no"); + return false; +} + +static void +tarobject_extract(struct tarcontext *tc, struct tar_entry *te, + const char *path, struct file_stat *st, + struct fsys_namenode *namenode) +{ + static struct varbuf hardlinkfn; + static int fd; + + struct dpkg_error err; + struct fsys_namenode *linknode; + char *newhash; + int rc; + + switch (te->type) { + case TAR_FILETYPE_FILE: + /* We create the file with mode 0 to make sure nobody can do anything with + * it until we apply the proper mode, which might be a statoverride. */ + fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0); + if (fd < 0) + ohshite(_("unable to create '%.255s' (while processing '%.255s')"), + path, te->name); + push_cleanup(cu_closefd, ehflag_bombout, 1, &fd); + debug(dbg_eachfiledetail, "tarobject file open size=%jd", + (intmax_t)te->size); + + /* We try to tell the filesystem how much disk space we are going to + * need to let it reduce fragmentation and possibly improve performance, + * as we do know the size beforehand. */ + fd_allocate_size(fd, 0, te->size); + + newhash = nfmalloc(MD5HASHLEN + 1); + if (fd_fd_copy_and_md5(tc->backendpipe, fd, newhash, te->size, &err) < 0) + ohshit(_("cannot copy extracted data for '%.255s' to '%.255s': %s"), + te->name, fnamenewvb.buf, err.str); + namenode->newhash = newhash; + debug(dbg_eachfiledetail, "tarobject file digest=%s", namenode->newhash); + + tarobject_skip_padding(tc, te); + + fd_writeback_init(fd); + + if (namenode->statoverride) + debug(dbg_eachfile, "tarobject ... stat override, uid=%d, gid=%d, mode=%04o", + namenode->statoverride->uid, + namenode->statoverride->gid, + namenode->statoverride->mode); + rc = fchown(fd, st->uid, st->gid); + if (forcible_nonroot_error(rc)) + ohshite(_("error setting ownership of '%.255s'"), te->name); + rc = fchmod(fd, st->mode & ~S_IFMT); + if (forcible_nonroot_error(rc)) + ohshite(_("error setting permissions of '%.255s'"), te->name); + + /* Postpone the fsync, to try to avoid massive I/O degradation. */ + if (!in_force(FORCE_UNSAFE_IO)) + namenode->flags |= FNNF_DEFERRED_FSYNC; + + pop_cleanup(ehflag_normaltidy); /* fd = open(path) */ + if (close(fd)) + ohshite(_("error closing/writing '%.255s'"), te->name); + break; + case TAR_FILETYPE_FIFO: + if (mkfifo(path, 0)) + ohshite(_("error creating pipe '%.255s'"), te->name); + debug(dbg_eachfiledetail, "tarobject fifo"); + break; + case TAR_FILETYPE_CHARDEV: + if (mknod(path, S_IFCHR, te->dev)) + ohshite(_("error creating device '%.255s'"), te->name); + debug(dbg_eachfiledetail, "tarobject chardev"); + break; + case TAR_FILETYPE_BLOCKDEV: + if (mknod(path, S_IFBLK, te->dev)) + ohshite(_("error creating device '%.255s'"), te->name); + debug(dbg_eachfiledetail, "tarobject blockdev"); + break; + case TAR_FILETYPE_HARDLINK: + varbuf_set_str(&hardlinkfn, dpkg_fsys_get_dir()); + linknode = fsys_hash_find_node(te->linkname, FHFF_NONE); + varbuf_add_str(&hardlinkfn, + namenodetouse(linknode, tc->pkg, &tc->pkg->available)->name); + if (linknode->flags & (FNNF_DEFERRED_RENAME | FNNF_NEW_CONFF)) + varbuf_add_str(&hardlinkfn, DPKGNEWEXT); + varbuf_end_str(&hardlinkfn); + if (link(hardlinkfn.buf, path)) + ohshite(_("error creating hard link '%.255s'"), te->name); + namenode->newhash = linknode->newhash; + debug(dbg_eachfiledetail, "tarobject hardlink digest=%s", namenode->newhash); + break; + case TAR_FILETYPE_SYMLINK: + /* We've already checked for an existing directory. */ + if (symlink(te->linkname, path)) + ohshite(_("error creating symbolic link '%.255s'"), te->name); + debug(dbg_eachfiledetail, "tarobject symlink creating"); + break; + case TAR_FILETYPE_DIR: + /* We've already checked for an existing directory. */ + if (mkdir(path, 0)) + ohshite(_("error creating directory '%.255s'"), te->name); + debug(dbg_eachfiledetail, "tarobject directory creating"); + break; + default: + internerr("unknown tar type '%d', but already checked", te->type); + } +} + +static void +tarobject_hash(struct tarcontext *tc, struct tar_entry *te, + struct fsys_namenode *namenode) +{ + if (te->type == TAR_FILETYPE_FILE) { + struct dpkg_error err; + char *newhash; + + newhash = nfmalloc(MD5HASHLEN + 1); + if (fd_md5(tc->backendpipe, newhash, te->size, &err) < 0) + ohshit(_("cannot compute MD5 digest for file '%.255s' in tar archive: %s"), + te->name, err.str); + tarobject_skip_padding(tc, te); + + namenode->newhash = newhash; + debug(dbg_eachfiledetail, "tarobject file digest=%s", namenode->newhash); + } else if (te->type == TAR_FILETYPE_HARDLINK) { + struct fsys_namenode *linknode; + + linknode = fsys_hash_find_node(te->linkname, FHFF_NONE); + namenode->newhash = linknode->newhash; + debug(dbg_eachfiledetail, "tarobject hardlink digest=%s", namenode->newhash); + } +} + +static void +tarobject_set_mtime(struct tar_entry *te, const char *path) +{ + struct timeval tv[2]; + + tv[0].tv_sec = currenttime; + tv[0].tv_usec = 0; + tv[1].tv_sec = te->mtime; + tv[1].tv_usec = 0; + + if (te->type == TAR_FILETYPE_SYMLINK) { +#ifdef HAVE_LUTIMES + if (lutimes(path, tv) && errno != ENOSYS) + ohshite(_("error setting timestamps of '%.255s'"), path); +#endif + } else { + if (utimes(path, tv)) + ohshite(_("error setting timestamps of '%.255s'"), path); + } +} + +static void +tarobject_set_perms(struct tar_entry *te, const char *path, struct file_stat *st) +{ + int rc; + + if (te->type == TAR_FILETYPE_FILE) + return; /* Already handled using the file descriptor. */ + + if (te->type == TAR_FILETYPE_SYMLINK) { + rc = lchown(path, st->uid, st->gid); + if (forcible_nonroot_error(rc)) + ohshite(_("error setting ownership of symlink '%.255s'"), path); + } else { + rc = chown(path, st->uid, st->gid); + if (forcible_nonroot_error(rc)) + ohshite(_("error setting ownership of '%.255s'"), path); + rc = chmod(path, st->mode & ~S_IFMT); + if (forcible_nonroot_error(rc)) + ohshite(_("error setting permissions of '%.255s'"), path); + } +} + +static void +tarobject_set_se_context(const char *matchpath, const char *path, mode_t mode) +{ + dpkg_selabel_set_context(matchpath, path, mode); +} + +static void +tarobject_matches(struct tarcontext *tc, + const char *fn_old, struct stat *stab, char *oldhash, + const char *fn_new, struct tar_entry *te, + struct fsys_namenode *namenode) +{ + char *linkname; + ssize_t linksize; + + debug(dbg_eachfiledetail, "tarobject matches on-disk object?"); + + switch (te->type) { + case TAR_FILETYPE_DIR: + /* Nothing to check for a new directory. */ + return; + case TAR_FILETYPE_SYMLINK: + /* Symlinks to existing dirs have already been dealt with, only + * remain real symlinks where we can compare the target. */ + if (!S_ISLNK(stab->st_mode)) + break; + linkname = m_malloc(stab->st_size + 1); + linksize = readlink(fn_old, linkname, stab->st_size + 1); + if (linksize < 0) + ohshite(_("unable to read link '%.255s'"), fn_old); + else if (linksize > stab->st_size) + ohshit(_("symbolic link '%.250s' size has changed from %jd to %zd"), + fn_old, (intmax_t)stab->st_size, linksize); + else if (linksize < stab->st_size) + warning(_("symbolic link '%.250s' size has changed from %jd to %zd"), + fn_old, (intmax_t)stab->st_size, linksize); + linkname[linksize] = '\0'; + if (strcmp(linkname, te->linkname) == 0) { + free(linkname); + return; + } else { + free(linkname); + } + break; + case TAR_FILETYPE_CHARDEV: + if (S_ISCHR(stab->st_mode) && stab->st_rdev == te->dev) + return; + break; + case TAR_FILETYPE_BLOCKDEV: + if (S_ISBLK(stab->st_mode) && stab->st_rdev == te->dev) + return; + break; + case TAR_FILETYPE_FIFO: + if (S_ISFIFO(stab->st_mode)) + return; + break; + case TAR_FILETYPE_HARDLINK: + /* Fall through. */ + case TAR_FILETYPE_FILE: + /* Only check metadata for non-conffiles. */ + if (!(namenode->flags & FNNF_NEW_CONFF) && + !(S_ISREG(stab->st_mode) && te->size == stab->st_size)) + break; + if (strcmp(oldhash, namenode->newhash) == 0) + return; + break; + default: + internerr("unknown tar type '%d', but already checked", te->type); + } + + forcibleerr(FORCE_OVERWRITE, + _("trying to overwrite shared '%.250s', which is different " + "from other instances of package %.250s"), + namenode->name, pkg_name(tc->pkg, pnaw_nonambig)); +} + +void setupfnamevbs(const char *filename) { + varbuf_rollback(&fname_state); + varbuf_add_str(&fnamevb, filename); + varbuf_end_str(&fnamevb); + + varbuf_rollback(&fnametmp_state); + varbuf_add_str(&fnametmpvb, filename); + varbuf_add_str(&fnametmpvb, DPKGTEMPEXT); + varbuf_end_str(&fnametmpvb); + + varbuf_rollback(&fnamenew_state); + varbuf_add_str(&fnamenewvb, filename); + varbuf_add_str(&fnamenewvb, DPKGNEWEXT); + varbuf_end_str(&fnamenewvb); + + debug(dbg_eachfiledetail, "setupvnamevbs main='%s' tmp='%s' new='%s'", + fnamevb.buf, fnametmpvb.buf, fnamenewvb.buf); +} + +static bool +linktosameexistingdir(const struct tar_entry *ti, const char *fname, + struct varbuf *symlinkfn) +{ + struct stat oldstab, newstab; + int statr; + + statr= stat(fname, &oldstab); + if (statr) { + if (!(errno == ENOENT || errno == ELOOP || errno == ENOTDIR)) + ohshite(_("failed to stat (dereference) existing symlink '%.250s'"), + fname); + return false; + } + if (!S_ISDIR(oldstab.st_mode)) + return false; + + /* But is it to the same dir? */ + if (ti->linkname[0] == '/') { + varbuf_set_str(symlinkfn, dpkg_fsys_get_dir()); + } else { + const char *lastslash; + + lastslash= strrchr(fname, '/'); + if (lastslash == NULL) + internerr("tar entry filename '%s' does not contain '/'", fname); + varbuf_set_buf(symlinkfn, fname, (lastslash - fname) + 1); + } + varbuf_add_str(symlinkfn, ti->linkname); + varbuf_end_str(symlinkfn); + + statr= stat(symlinkfn->buf, &newstab); + if (statr) { + if (!(errno == ENOENT || errno == ELOOP || errno == ENOTDIR)) + ohshite(_("failed to stat (dereference) proposed new symlink target" + " '%.250s' for symlink '%.250s'"), symlinkfn->buf, fname); + return false; + } + if (!S_ISDIR(newstab.st_mode)) + return false; + if (newstab.st_dev != oldstab.st_dev || + newstab.st_ino != oldstab.st_ino) + return false; + return true; +} + +int +tarobject(struct tar_archive *tar, struct tar_entry *ti) +{ + static struct varbuf conffderefn, symlinkfn; + const char *usename; + struct fsys_namenode *namenode, *usenode; + + struct conffile *conff; + struct tarcontext *tc = tar->ctx; + bool existingdir, keepexisting; + bool refcounting; + char oldhash[MD5HASHLEN + 1]; + int statr; + struct stat stab, stabtmp; + struct file_stat nodestat; + struct fsys_namenode_list *nifd, **oldnifd; + struct pkgset *divpkgset; + struct pkginfo *otherpkg; + + tar_entry_update_from_system(ti); + + /* Perform some sanity checks on the tar entry. */ + if (strchr(ti->name, '\n')) + ohshit(_("newline not allowed in archive object name '%.255s'"), ti->name); + + namenode = fsys_hash_find_node(ti->name, FHFF_NONE); + + if (namenode->flags & FNNF_RM_CONFF_ON_UPGRADE) + ohshit(_("conffile '%s' marked for removal on upgrade, shipped in package"), + ti->name); + + /* Append to list of files. + * The trailing ‘/’ put on the end of names in tarfiles has already + * been stripped by tar_extractor(). */ + oldnifd = tc->newfiles_queue->tail; + nifd = tar_fsys_namenode_queue_push(tc->newfiles_queue, namenode); + nifd->namenode->flags |= FNNF_NEW_INARCHIVE; + + debug(dbg_eachfile, + "tarobject ti->name='%s' mode=%lo owner=%u:%u type=%d(%c)" + " ti->linkname='%s' namenode='%s' flags=%o instead='%s'", + ti->name, (long)ti->stat.mode, + (unsigned)ti->stat.uid, (unsigned)ti->stat.gid, + ti->type, + ti->type >= '0' && ti->type <= '6' ? "-hlcbdp"[ti->type - '0'] : '?', + ti->linkname, + nifd->namenode->name, nifd->namenode->flags, + nifd->namenode->divert && nifd->namenode->divert->useinstead + ? nifd->namenode->divert->useinstead->name : ""); + + if (nifd->namenode->divert && nifd->namenode->divert->camefrom) { + divpkgset = nifd->namenode->divert->pkgset; + + if (divpkgset) { + forcibleerr(FORCE_OVERWRITE_DIVERTED, + _("trying to overwrite '%.250s', which is the " + "diverted version of '%.250s' (package: %.100s)"), + nifd->namenode->name, nifd->namenode->divert->camefrom->name, + divpkgset->name); + } else { + forcibleerr(FORCE_OVERWRITE_DIVERTED, + _("trying to overwrite '%.250s', which is the " + "diverted version of '%.250s'"), + nifd->namenode->name, nifd->namenode->divert->camefrom->name); + } + } + + if (nifd->namenode->statoverride) { + nodestat = *nifd->namenode->statoverride; + nodestat.mode |= ti->stat.mode & S_IFMT; + } else { + nodestat = ti->stat; + } + + usenode = namenodetouse(nifd->namenode, tc->pkg, &tc->pkg->available); + usename = usenode->name; + + trig_file_activate(usenode, tc->pkg); + + if (nifd->namenode->flags & FNNF_NEW_CONFF) { + /* If it's a conffile we have to extract it next to the installed + * version (i.e. we do the usual link-following). */ + if (conffderef(tc->pkg, &conffderefn, usename)) + usename= conffderefn.buf; + debug(dbg_conff, "tarobject FNNF_NEW_CONFF deref='%s'", usename); + } + + setupfnamevbs(usename); + + statr= lstat(fnamevb.buf,&stab); + if (statr) { + /* The lstat failed. */ + if (errno != ENOENT && errno != ENOTDIR) + ohshite(_("unable to stat '%.255s' (which was about to be installed)"), + ti->name); + /* OK, so it doesn't exist. + * However, it's possible that we were in the middle of some other + * backup/restore operation and were rudely interrupted. + * So, we see if we have .dpkg-tmp, and if so we restore it. */ + if (rename(fnametmpvb.buf,fnamevb.buf)) { + /* Trying to remove a directory or a file on a read-only filesystem, + * even if non-existent, always returns EROFS. */ + if (errno == EROFS) { + /* If the file does not exist the access() function will remap the + * EROFS into an ENOENT, otherwise restore EROFS to fail with that. */ + if (access(fnametmpvb.buf, F_OK) == 0) + errno = EROFS; + } + + if (errno != ENOENT && errno != ENOTDIR) + ohshite(_("unable to clean up mess surrounding '%.255s' before " + "installing another version"), ti->name); + debug(dbg_eachfiledetail,"tarobject nonexistent"); + } else { + debug(dbg_eachfiledetail,"tarobject restored tmp to main"); + statr= lstat(fnamevb.buf,&stab); + if (statr) + ohshite(_("unable to stat restored '%.255s' before installing" + " another version"), ti->name); + } + } else { + debug(dbg_eachfiledetail,"tarobject already exists"); + } + + /* Check to see if it's a directory or link to one and we don't need to + * do anything. This has to be done now so that we don't die due to + * a file overwriting conflict. */ + existingdir = false; + switch (ti->type) { + case TAR_FILETYPE_SYMLINK: + /* If it's already an existing directory, do nothing. */ + if (!statr && S_ISDIR(stab.st_mode)) { + debug(dbg_eachfiledetail, "tarobject symlink exists as directory"); + existingdir = true; + } else if (!statr && S_ISLNK(stab.st_mode)) { + if (linktosameexistingdir(ti, fnamevb.buf, &symlinkfn)) + existingdir = true; + } + break; + case TAR_FILETYPE_DIR: + /* If it's already an existing directory, do nothing. */ + if (!stat(fnamevb.buf,&stabtmp) && S_ISDIR(stabtmp.st_mode)) { + debug(dbg_eachfiledetail, "tarobject directory exists"); + existingdir = true; + } + break; + case TAR_FILETYPE_FILE: + case TAR_FILETYPE_CHARDEV: + case TAR_FILETYPE_BLOCKDEV: + case TAR_FILETYPE_FIFO: + case TAR_FILETYPE_HARDLINK: + break; + default: + ohshit(_("archive contained object '%.255s' of unknown type 0x%x"), + ti->name, ti->type); + } + + keepexisting = false; + refcounting = false; + if (!existingdir) { + struct fsys_node_pkgs_iter *iter; + + iter = fsys_node_pkgs_iter_new(nifd->namenode); + while ((otherpkg = fsys_node_pkgs_iter_next(iter))) { + if (otherpkg == tc->pkg) + continue; + debug(dbg_eachfile, "tarobject ... found in %s", + pkg_name(otherpkg, pnaw_always)); + + /* A pkgset can share files between its instances. Overwriting + * is allowed when they are not getting in sync, otherwise the + * file content must match the installed file. */ + if (otherpkg->set == tc->pkg->set && + otherpkg->installed.multiarch == PKG_MULTIARCH_SAME && + tc->pkg->available.multiarch == PKG_MULTIARCH_SAME) { + if (statr == 0 && tc->pkgset_getting_in_sync) + refcounting = true; + debug(dbg_eachfiledetail, "tarobject ... shared with %s %s (syncing=%d)", + pkg_name(otherpkg, pnaw_always), + versiondescribe_c(&otherpkg->installed.version, vdew_nonambig), + tc->pkgset_getting_in_sync); + continue; + } + + if (nifd->namenode->divert && nifd->namenode->divert->useinstead) { + /* Right, so we may be diverting this file. This makes the conflict + * OK iff one of us is the diverting package (we don't need to + * check for both being the diverting package, obviously). */ + divpkgset = nifd->namenode->divert->pkgset; + debug(dbg_eachfile, "tarobject ... diverted, divpkgset=%s", + divpkgset ? divpkgset->name : ""); + if (otherpkg->set == divpkgset || tc->pkg->set == divpkgset) + continue; + } + + /* If the new object is a directory and the previous object does + * not exist assume it's also a directory and skip further checks. + * XXX: Ideally with more information about the installed files we + * could perform more clever checks. */ + if (statr != 0 && ti->type == TAR_FILETYPE_DIR) { + debug(dbg_eachfile, "tarobject ... assuming shared directory"); + continue; + } + + ensure_package_clientdata(otherpkg); + + /* Nope? Hmm, file conflict, perhaps. Check Replaces. */ + switch (otherpkg->clientdata->replacingfilesandsaid) { + case 2: + keepexisting = true; + /* Fall through. */ + case 1: + continue; + } + + /* Is the package with the conflicting file in the “config files only” + * state? If so it must be a config file and we can silently take it + * over. */ + if (otherpkg->status == PKG_STAT_CONFIGFILES) + continue; + + /* Perhaps we're removing a conflicting package? */ + if (otherpkg->clientdata->istobe == PKG_ISTOBE_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 (strcmp(conff->name, nifd->namenode->name) == 0) + break; + } + if (conff) { + debug(dbg_eachfiledetail, "tarobject other's obsolete conffile"); + /* process_archive() will have copied its hash already. */ + continue; + } + } + + if (does_replace(tc->pkg, &tc->pkg->available, + otherpkg, &otherpkg->installed)) { + printf(_("Replacing files in old package %s (%s) ...\n"), + pkg_name(otherpkg, pnaw_nonambig), + versiondescribe(&otherpkg->installed.version, vdew_nonambig)); + otherpkg->clientdata->replacingfilesandsaid = 1; + } else if (does_replace(otherpkg, &otherpkg->installed, + tc->pkg, &tc->pkg->available)) { + printf(_("Replaced by files in installed package %s (%s) ...\n"), + pkg_name(otherpkg, pnaw_nonambig), + versiondescribe(&otherpkg->installed.version, vdew_nonambig)); + otherpkg->clientdata->replacingfilesandsaid = 2; + nifd->namenode->flags &= ~FNNF_NEW_INARCHIVE; + keepexisting = true; + } else { + /* At this point we are replacing something without a Replaces. */ + if (!statr && S_ISDIR(stab.st_mode)) { + forcibleerr(FORCE_OVERWRITE_DIR, + _("trying to overwrite directory '%.250s' " + "in package %.250s %.250s with nondirectory"), + nifd->namenode->name, pkg_name(otherpkg, pnaw_nonambig), + versiondescribe(&otherpkg->installed.version, + vdew_nonambig)); + } else { + forcibleerr(FORCE_OVERWRITE, + _("trying to overwrite '%.250s', " + "which is also in package %.250s %.250s"), + nifd->namenode->name, pkg_name(otherpkg, pnaw_nonambig), + versiondescribe(&otherpkg->installed.version, + vdew_nonambig)); + } + } + } + fsys_node_pkgs_iter_free(iter); + } + + if (keepexisting) { + if (nifd->namenode->flags & FNNF_NEW_CONFF) + nifd->namenode->flags |= FNNF_OBS_CONFF; + tar_fsys_namenode_queue_pop(tc->newfiles_queue, oldnifd, nifd); + tarobject_skip_entry(tc, ti); + return 0; + } + + if (filter_should_skip(ti)) { + nifd->namenode->flags &= ~FNNF_NEW_INARCHIVE; + nifd->namenode->flags |= FNNF_FILTERED; + tarobject_skip_entry(tc, ti); + + return 0; + } + + if (existingdir) + return 0; + + /* Compute the hash of the previous object, before we might replace it + * with the new version on forced overwrites. */ + if (refcounting) { + debug(dbg_eachfiledetail, "tarobject computing on-disk file '%s' digest, refcounting", + fnamevb.buf); + if (nifd->namenode->flags & FNNF_NEW_CONFF) { + md5hash_prev_conffile(tc->pkg, oldhash, fnamenewvb.buf, nifd->namenode); + } else if (S_ISREG(stab.st_mode)) { + md5hash(tc->pkg, oldhash, fnamevb.buf); + } else { + strcpy(oldhash, EMPTYHASHFLAG); + } + } + + if (refcounting && !in_force(FORCE_OVERWRITE)) { + /* If we are not forced to overwrite the path and are refcounting, + * just compute the hash w/o extracting the object. */ + tarobject_hash(tc, ti, nifd->namenode); + } else { + /* Now, at this stage we want to make sure neither of .dpkg-new and + * .dpkg-tmp are hanging around. */ + path_remove_tree(fnamenewvb.buf); + path_remove_tree(fnametmpvb.buf); + + /* Now we start to do things that we need to be able to undo + * if something goes wrong. Watch out for the CLEANUP comments to + * keep an eye on what's installed on the disk at each point. */ + push_cleanup(cu_installnew, ~ehflag_normaltidy, 1, nifd->namenode); + + /* + * CLEANUP: Now we either have the old file on the disk, or not, in + * its original filename. + */ + + /* Extract whatever it is as .dpkg-new ... */ + tarobject_extract(tc, ti, fnamenewvb.buf, &nodestat, nifd->namenode); + } + + /* For shared files, check now if the object matches. */ + if (refcounting) + tarobject_matches(tc, fnamevb.buf, &stab, oldhash, + fnamenewvb.buf, ti, nifd->namenode); + + /* If we didn't extract anything, there's nothing else to do. */ + if (refcounting && !in_force(FORCE_OVERWRITE)) + return 0; + + tarobject_set_perms(ti, fnamenewvb.buf, &nodestat); + tarobject_set_mtime(ti, fnamenewvb.buf); + tarobject_set_se_context(fnamevb.buf, fnamenewvb.buf, nodestat.mode); + + /* + * CLEANUP: Now we have extracted the new object in .dpkg-new (or, + * if the file already exists as a directory and we were trying to + * extract a directory or symlink, we returned earlier, so we don't + * need to worry about that here). + * + * The old file is still in the original filename, + */ + + /* First, check to see if it's a conffile. If so we don't install + * it now - we leave it in .dpkg-new for --configure to take care of. */ + if (nifd->namenode->flags & FNNF_NEW_CONFF) { + debug(dbg_conffdetail,"tarobject conffile extracted"); + nifd->namenode->flags |= FNNF_ELIDE_OTHER_LISTS; + return 0; + } + + /* Now we move the old file out of the way, the backup file will + * be deleted later. */ + if (statr) { + /* Don't try to back it up if it didn't exist. */ + debug(dbg_eachfiledetail,"tarobject new - no backup"); + } else { + if (ti->type == TAR_FILETYPE_DIR || S_ISDIR(stab.st_mode)) { + /* One of the two is a directory - can't do atomic install. */ + debug(dbg_eachfiledetail,"tarobject directory, nonatomic"); + nifd->namenode->flags |= FNNF_NO_ATOMIC_OVERWRITE; + if (rename(fnamevb.buf,fnametmpvb.buf)) + ohshite(_("unable to move aside '%.255s' to install new version"), + ti->name); + } else if (S_ISLNK(stab.st_mode)) { + ssize_t symlink_len; + int rc; + + /* We can't make a symlink with two hardlinks, so we'll have to + * copy it. (Pretend that making a copy of a symlink is the same + * as linking to it.) */ + varbuf_reset(&symlinkfn); + varbuf_grow(&symlinkfn, stab.st_size + 1); + symlink_len = readlink(fnamevb.buf, symlinkfn.buf, symlinkfn.size); + if (symlink_len < 0) + ohshite(_("unable to read link '%.255s'"), ti->name); + else if (symlink_len > stab.st_size) + ohshit(_("symbolic link '%.250s' size has changed from %jd to %zd"), + fnamevb.buf, (intmax_t)stab.st_size, symlink_len); + else if (symlink_len < stab.st_size) + warning(_("symbolic link '%.250s' size has changed from %jd to %zd"), + fnamevb.buf, (intmax_t)stab.st_size, symlink_len); + varbuf_trunc(&symlinkfn, symlink_len); + varbuf_end_str(&symlinkfn); + if (symlink(symlinkfn.buf,fnametmpvb.buf)) + ohshite(_("unable to make backup symlink for '%.255s'"), ti->name); + rc = lchown(fnametmpvb.buf, stab.st_uid, stab.st_gid); + if (forcible_nonroot_error(rc)) + ohshite(_("unable to chown backup symlink for '%.255s'"), ti->name); + tarobject_set_se_context(fnamevb.buf, fnametmpvb.buf, stab.st_mode); + } else { + debug(dbg_eachfiledetail, "tarobject nondirectory, 'link' backup"); + if (link(fnamevb.buf,fnametmpvb.buf)) + ohshite(_("unable to make backup link of '%.255s' before installing new version"), + ti->name); + } + } + + /* + * CLEANUP: Now the old file is in .dpkg-tmp, and the new file is still + * in .dpkg-new. + */ + + if (ti->type == TAR_FILETYPE_FILE || ti->type == TAR_FILETYPE_HARDLINK || + ti->type == TAR_FILETYPE_SYMLINK) { + nifd->namenode->flags |= FNNF_DEFERRED_RENAME; + + debug(dbg_eachfiledetail, "tarobject done and installation deferred"); + } else { + if (rename(fnamenewvb.buf, fnamevb.buf)) + ohshite(_("unable to install new version of '%.255s'"), ti->name); + + /* + * CLEANUP: Now the new file is in the destination file, and the + * old file is in .dpkg-tmp to be cleaned up later. We now need + * to take a different attitude to cleanup, because we need to + * remove the new file. + */ + + nifd->namenode->flags |= FNNF_PLACED_ON_DISK; + nifd->namenode->flags |= FNNF_ELIDE_OTHER_LISTS; + + debug(dbg_eachfiledetail, "tarobject done and installed"); + } + + return 0; +} + +#if defined(SYNC_FILE_RANGE_WAIT_BEFORE) +static void +tar_writeback_barrier(struct fsys_namenode_list *files, struct pkginfo *pkg) +{ + struct fsys_namenode_list *cfile; + + for (cfile = files; cfile; cfile = cfile->next) { + struct fsys_namenode *usenode; + int fd; + + if (!(cfile->namenode->flags & FNNF_DEFERRED_FSYNC)) + continue; + + usenode = namenodetouse(cfile->namenode, pkg, &pkg->available); + + setupfnamevbs(usenode->name); + + fd = open(fnamenewvb.buf, O_WRONLY); + if (fd < 0) + ohshite(_("unable to open '%.255s'"), fnamenewvb.buf); + /* Ignore the return code as it should be considered equivalent to an + * asynchronous hint for the kernel, we are doing an fsync() later on + * anyway. */ + sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WAIT_BEFORE); + if (close(fd)) + ohshite(_("error closing/writing '%.255s'"), fnamenewvb.buf); + } +} +#else +static void +tar_writeback_barrier(struct fsys_namenode_list *files, struct pkginfo *pkg) +{ +} +#endif + +void +tar_deferred_extract(struct fsys_namenode_list *files, struct pkginfo *pkg) +{ + struct fsys_namenode_list *cfile; + struct fsys_namenode *usenode; + + tar_writeback_barrier(files, pkg); + + for (cfile = files; cfile; cfile = cfile->next) { + debug(dbg_eachfile, "deferred extract of '%.255s'", cfile->namenode->name); + + if (!(cfile->namenode->flags & FNNF_DEFERRED_RENAME)) + continue; + + usenode = namenodetouse(cfile->namenode, pkg, &pkg->available); + + setupfnamevbs(usenode->name); + + if (cfile->namenode->flags & FNNF_DEFERRED_FSYNC) { + int fd; + + debug(dbg_eachfiledetail, "deferred extract needs fsync"); + + fd = open(fnamenewvb.buf, O_WRONLY); + if (fd < 0) + ohshite(_("unable to open '%.255s'"), fnamenewvb.buf); + if (fsync(fd)) + ohshite(_("unable to sync file '%.255s'"), fnamenewvb.buf); + if (close(fd)) + ohshite(_("error closing/writing '%.255s'"), fnamenewvb.buf); + + cfile->namenode->flags &= ~FNNF_DEFERRED_FSYNC; + } + + debug(dbg_eachfiledetail, "deferred extract needs rename"); + + if (rename(fnamenewvb.buf, fnamevb.buf)) + ohshite(_("unable to install new version of '%.255s'"), + cfile->namenode->name); + + cfile->namenode->flags &= ~FNNF_DEFERRED_RENAME; + + /* + * CLEANUP: Now the new file is in the destination file, and the + * old file is in .dpkg-tmp to be cleaned up later. We now need + * to take a different attitude to cleanup, because we need to + * remove the new file. + */ + + cfile->namenode->flags |= FNNF_PLACED_ON_DISK; + cfile->namenode->flags |= FNNF_ELIDE_OTHER_LISTS; + + debug(dbg_eachfiledetail, "deferred extract done and installed"); + } +} + +void +enqueue_deconfigure(struct pkginfo *pkg, struct pkginfo *pkg_removal, + enum pkgwant reason) +{ + struct pkg_deconf_list *newdeconf; + + ensure_package_clientdata(pkg); + pkg->clientdata->istobe = PKG_ISTOBE_DECONFIGURE; + newdeconf = m_malloc(sizeof(*newdeconf)); + newdeconf->next = deconfigure; + newdeconf->pkg = pkg; + newdeconf->pkg_removal = pkg_removal; + newdeconf->reason = reason; + deconfigure = newdeconf; +} + +void +clear_deconfigure_queue(void) +{ + struct pkg_deconf_list *deconf, *deconf_next; + + for (deconf = deconfigure; deconf; deconf = deconf_next) { + deconf_next = deconf->next; + free(deconf); + } + deconfigure = NULL; +} + +/** + * Try if we can deconfigure the package for installation and queue it if so. + * + * This function gets called in the Breaks context, when trying to install + * a package that might require another to be deconfigured to be able to + * proceed. + * + * First checks whether the pdep is forced. + * + * @retval 0 Not possible (why is printed). + * @retval 1 Deconfiguration queued ok (no message printed). + * @retval 2 Forced (no deconfiguration needed, why is printed). + */ +static int +try_deconfigure_can(struct pkginfo *pkg, struct deppossi *pdep, + struct pkginfo *pkg_install, const char *why) +{ + if (force_breaks(pdep)) { + warning(_("ignoring dependency problem with installation of %s:\n%s"), + pkgbin_name(pkg_install, &pkg->available, pnaw_nonambig), why); + return 2; + } else if (f_autodeconf) { + enqueue_deconfigure(pkg, NULL, PKG_WANT_INSTALL); + return 1; + } else { + notice(_("no, cannot proceed with installation of %s (--auto-deconfigure will help):\n%s"), + pkgbin_name(pkg_install, &pkg->available, pnaw_nonambig), why); + return 0; + } +} + +/** + * Try if we can deconfigure the package for removal and queue it if so. + * + * This function gets called in the Conflicts+Depends context, when trying + * to install a package that might require another to be fully removed to + * be able to proceed. + * + * First checks whether the pdep is forced, then if auto-configure is enabled + * we make sure Essential and Protected are not allowed to be removed unless + * forced. + * + * @retval 0 Not possible (why is printed). + * @retval 1 Deconfiguration queued ok (no message printed). + * @retval 2 Forced (no deconfiguration needed, why is printed). + */ +static int +try_remove_can(struct deppossi *pdep, + struct pkginfo *pkg_removal, const char *why) +{ + struct pkginfo *pkg = pdep->up->up; + + if (force_depends(pdep)) { + warning(_("ignoring dependency problem with removal of %s:\n%s"), + pkg_name(pkg_removal, pnaw_nonambig), why); + return 2; + } else if (f_autodeconf) { + if (pkg->installed.essential) { + if (in_force(FORCE_REMOVE_ESSENTIAL)) { + warning(_("considering deconfiguration of essential\n" + " package %s, to enable removal of %s"), + pkg_name(pkg, pnaw_nonambig), pkg_name(pkg_removal, pnaw_nonambig)); + } else { + notice(_("no, %s is essential, will not deconfigure\n" + " it in order to enable removal of %s"), + pkg_name(pkg, pnaw_nonambig), pkg_name(pkg_removal, pnaw_nonambig)); + return 0; + } + } + if (pkg->installed.is_protected) { + if (in_force(FORCE_REMOVE_PROTECTED)) { + warning(_("considering deconfiguration of protected\n" + " package %s, to enable removal of %s"), + pkg_name(pkg, pnaw_nonambig), pkg_name(pkg_removal, pnaw_nonambig)); + } else { + notice(_("no, %s is protected, will not deconfigure\n" + " it in order to enable removal of %s"), + pkg_name(pkg, pnaw_nonambig), pkg_name(pkg_removal, pnaw_nonambig)); + return 0; + } + } + + enqueue_deconfigure(pkg, pkg_removal, PKG_WANT_DEINSTALL); + return 1; + } else { + notice(_("no, cannot proceed with removal of %s (--auto-deconfigure will help):\n%s"), + pkg_name(pkg_removal, pnaw_nonambig), why); + return 0; + } +} + +void check_breaks(struct dependency *dep, struct pkginfo *pkg, + const char *pfilename) { + struct pkginfo *fixbydeconf; + struct varbuf why = VARBUF_INIT; + int ok; + + fixbydeconf = NULL; + if (depisok(dep, &why, &fixbydeconf, NULL, false)) { + varbuf_destroy(&why); + return; + } + + varbuf_end_str(&why); + + if (fixbydeconf && f_autodeconf) { + ensure_package_clientdata(fixbydeconf); + + if (fixbydeconf->clientdata->istobe != PKG_ISTOBE_NORMAL) + internerr("package %s being fixed by deconf is not to be normal, " + "is to be %d", + pkg_name(pkg, pnaw_always), fixbydeconf->clientdata->istobe); + + notice(_("considering deconfiguration of %s, which would be broken by installation of %s ..."), + pkg_name(fixbydeconf, pnaw_nonambig), + pkgbin_name(pkg, &pkg->available, pnaw_nonambig)); + + ok = try_deconfigure_can(fixbydeconf, dep->list, pkg, why.buf); + if (ok == 1) { + notice(_("yes, will deconfigure %s (broken by %s)"), + pkg_name(fixbydeconf, pnaw_nonambig), + pkgbin_name(pkg, &pkg->available, pnaw_nonambig)); + } + } else { + notice(_("regarding %s containing %s:\n%s"), pfilename, + pkgbin_name(pkg, &pkg->available, pnaw_nonambig), why.buf); + ok= 0; + } + varbuf_destroy(&why); + if (ok > 0) return; + + if (force_breaks(dep->list)) { + warning(_("ignoring breakage, may proceed anyway!")); + return; + } + + if (fixbydeconf && !f_autodeconf) { + ohshit(_("installing %.250s would break %.250s, and\n" + " deconfiguration is not permitted (--auto-deconfigure might help)"), + pkgbin_name(pkg, &pkg->available, pnaw_nonambig), + pkg_name(fixbydeconf, pnaw_nonambig)); + } else { + ohshit(_("installing %.250s would break existing software"), + pkgbin_name(pkg, &pkg->available, pnaw_nonambig)); + } +} + +void check_conflict(struct dependency *dep, struct pkginfo *pkg, + const char *pfilename) { + struct pkginfo *fixbyrm; + struct deppossi *pdep, flagdeppossi = { 0 }; + struct varbuf conflictwhy = VARBUF_INIT, removalwhy = VARBUF_INIT; + struct dependency *providecheck; + + fixbyrm = NULL; + if (depisok(dep, &conflictwhy, &fixbyrm, NULL, false)) { + varbuf_destroy(&conflictwhy); + varbuf_destroy(&removalwhy); + return; + } + if (fixbyrm) { + ensure_package_clientdata(fixbyrm); + if (fixbyrm->clientdata->istobe == PKG_ISTOBE_INSTALLNEW) { + fixbyrm= dep->up; + ensure_package_clientdata(fixbyrm); + } + if (((pkg->available.essential || pkg->available.is_protected) && + (fixbyrm->installed.essential || fixbyrm->installed.is_protected)) || + (((fixbyrm->want != PKG_WANT_INSTALL && + fixbyrm->want != PKG_WANT_HOLD) || + does_replace(pkg, &pkg->available, fixbyrm, &fixbyrm->installed)) && + ((!fixbyrm->installed.essential || in_force(FORCE_REMOVE_ESSENTIAL)) || + (!fixbyrm->installed.is_protected || in_force(FORCE_REMOVE_PROTECTED))))) { + if (fixbyrm->clientdata->istobe != PKG_ISTOBE_NORMAL && + fixbyrm->clientdata->istobe != PKG_ISTOBE_DECONFIGURE) + internerr("package %s to be fixed by removal is not to be normal " + "nor deconfigure, is to be %d", + pkg_name(pkg, pnaw_always), fixbyrm->clientdata->istobe); + fixbyrm->clientdata->istobe = PKG_ISTOBE_REMOVE; + notice(_("considering removing %s in favour of %s ..."), + pkg_name(fixbyrm, pnaw_nonambig), + pkgbin_name(pkg, &pkg->available, pnaw_nonambig)); + if (!(fixbyrm->status == PKG_STAT_INSTALLED || + fixbyrm->status == PKG_STAT_TRIGGERSPENDING || + fixbyrm->status == PKG_STAT_TRIGGERSAWAITED)) { + notice(_("%s is not properly installed; ignoring any dependencies on it"), + pkg_name(fixbyrm, pnaw_nonambig)); + pdep = NULL; + } else { + for (pdep = fixbyrm->set->depended.installed; + pdep; + pdep = pdep->rev_next) { + if (pdep->up->type != dep_depends && pdep->up->type != dep_predepends) + continue; + if (depisok(pdep->up, &removalwhy, NULL, NULL, false)) + continue; + varbuf_end_str(&removalwhy); + if (!try_remove_can(pdep,fixbyrm,removalwhy.buf)) + break; + } + if (!pdep) { + /* If we haven't found a reason not to yet, let's look some more. */ + for (providecheck= fixbyrm->installed.depends; + providecheck; + providecheck= providecheck->next) { + if (providecheck->type != dep_provides) continue; + for (pdep = providecheck->list->ed->depended.installed; + pdep; + pdep = pdep->rev_next) { + if (pdep->up->type != dep_depends && pdep->up->type != dep_predepends) + continue; + if (depisok(pdep->up, &removalwhy, NULL, NULL, false)) + continue; + varbuf_end_str(&removalwhy); + notice(_("may have trouble removing %s, as it provides %s ..."), + pkg_name(fixbyrm, pnaw_nonambig), + providecheck->list->ed->name); + if (!try_remove_can(pdep,fixbyrm,removalwhy.buf)) + goto break_from_both_loops_at_once; + } + } + break_from_both_loops_at_once:; + } + } + if (!pdep && skip_due_to_hold(fixbyrm)) { + pdep= &flagdeppossi; + } + if (!pdep && (fixbyrm->eflag & PKG_EFLAG_REINSTREQ)) { + if (in_force(FORCE_REMOVE_REINSTREQ)) { + notice(_("package %s requires reinstallation, but will " + "remove anyway as you requested"), + pkg_name(fixbyrm, pnaw_nonambig)); + } else { + notice(_("package %s requires reinstallation, will not remove"), + pkg_name(fixbyrm, pnaw_nonambig)); + pdep= &flagdeppossi; + } + } + if (!pdep) { + /* This conflict is OK - we'll remove the conflictor. */ + enqueue_conflictor(fixbyrm); + varbuf_destroy(&conflictwhy); varbuf_destroy(&removalwhy); + notice(_("yes, will remove %s in favour of %s"), + pkg_name(fixbyrm, pnaw_nonambig), + pkgbin_name(pkg, &pkg->available, pnaw_nonambig)); + return; + } + /* Put it back. */ + fixbyrm->clientdata->istobe = PKG_ISTOBE_NORMAL; + } + } + varbuf_end_str(&conflictwhy); + notice(_("regarding %s containing %s:\n%s"), pfilename, + pkgbin_name(pkg, &pkg->available, pnaw_nonambig), conflictwhy.buf); + if (!force_conflicts(dep->list)) + ohshit(_("conflicting packages - not installing %.250s"), + pkgbin_name(pkg, &pkg->available, pnaw_nonambig)); + warning(_("ignoring conflict, may proceed anyway!")); + varbuf_destroy(&conflictwhy); + + return; +} + +void cu_cidir(int argc, void **argv) { + char *cidir= (char*)argv[0]; + char *cidirrest= (char*)argv[1]; + cidirrest[-1] = '\0'; + path_remove_tree(cidir); + free(cidir); +} + +void cu_fileslist(int argc, void **argv) { + tar_pool_release(); +} + +int +archivefiles(const char *const *argv) +{ + const char *const *volatile argp; + char **volatile arglist = NULL; + int i; + jmp_buf ejbuf; + enum modstatdb_rw msdbflags; + + trigproc_install_hooks(); + + if (f_noact) + msdbflags = msdbrw_readonly; + else if (cipaction->arg_int == act_avail) + msdbflags = msdbrw_readonly | msdbrw_available_write; + else if (in_force(FORCE_NON_ROOT)) + msdbflags = msdbrw_write; + else + msdbflags = msdbrw_needsuperuser; + + modstatdb_open(msdbflags); + + checkpath(); + pkg_infodb_upgrade(); + + log_message("startup archives %s", cipaction->olong); + + if (f_recursive) { + const char *const *ap; + int nfiles = 0; + + if (!*argv) + badusage(_("--%s --recursive needs at least one path argument"),cipaction->olong); + + for (ap = argv; *ap; ap++) { + struct treeroot *tree; + struct treenode *node; + + tree = treewalk_open((const char *)*ap, TREEWALK_FOLLOW_LINKS, NULL); + + while ((node = treewalk_next(tree))) { + const char *nodename; + + if (!S_ISREG(treenode_get_mode(node))) + continue; + + /* Check if it looks like a .deb file. */ + nodename = treenode_get_pathname(node); + if (strcmp(nodename + strlen(nodename) - 4, DEBEXT) != 0) + continue; + + arglist = m_realloc(arglist, sizeof(char *) * (nfiles + 2)); + arglist[nfiles++] = m_strdup(nodename); + } + + treewalk_close(tree); + } + + if (!nfiles) + ohshit(_("searched, but found no packages (files matching *.deb)")); + + arglist[nfiles] = NULL; + argp = (const char **volatile)arglist; + } else { + if (!*argv) badusage(_("--%s needs at least one package archive file argument"), + cipaction->olong); + argp= argv; + } + + /* Perform some sanity checks on the passed archives. */ + for (i = 0; argp[i]; i++) { + struct stat st; + + /* We need the filename to exist. */ + if (stat(argp[i], &st) < 0) + ohshite(_("cannot access archive '%s'"), argp[i]); + + /* We cannot work with anything that is not a regular file. */ + if (!S_ISREG(st.st_mode)) + ohshit(_("archive '%s' is not a regular file"), argp[i]); + } + + currenttime = time(NULL); + + /* Initialize fname variables contents. */ + + varbuf_reset(&fnamevb); + varbuf_reset(&fnametmpvb); + varbuf_reset(&fnamenewvb); + + varbuf_add_str(&fnamevb, dpkg_fsys_get_dir()); + varbuf_add_str(&fnametmpvb, dpkg_fsys_get_dir()); + varbuf_add_str(&fnamenewvb, dpkg_fsys_get_dir()); + + varbuf_snapshot(&fnamevb, &fname_state); + varbuf_snapshot(&fnametmpvb, &fnametmp_state); + varbuf_snapshot(&fnamenewvb, &fnamenew_state); + + ensure_diversions(); + ensure_statoverrides(STATDB_PARSE_NORMAL); + + for (i = 0; argp[i]; i++) { + if (setjmp(ejbuf)) { + pop_error_context(ehflag_bombout); + if (abort_processing) + break; + continue; + } + push_error_context_jump(&ejbuf, print_error_perarchive, argp[i]); + + dpkg_selabel_load(); + + process_archive(argp[i]); + onerr_abort++; + m_output(stdout, _("")); + m_output(stderr, _("")); + onerr_abort--; + + pop_error_context(ehflag_normaltidy); + } + + dpkg_selabel_close(); + + if (arglist) { + for (i = 0; arglist[i]; i++) + free(arglist[i]); + free(arglist); + } + + switch (cipaction->arg_int) { + case act_install: + case act_configure: + case act_triggers: + case act_remove: + case act_purge: + process_queue(); + case act_unpack: + case act_avail: + break; + default: + internerr("unknown action '%d'", cipaction->arg_int); + } + + trigproc_run_deferred(); + modstatdb_shutdown(); + + return 0; +} + +/** + * Decide whether we want to install a new version of the package. + * + * @param pkg The package with the version we might want to install + * + * @retval true If the package should be skipped. + * @retval false If the package should be installed. + */ +bool +wanttoinstall(struct pkginfo *pkg) +{ + int rc; + + if (pkg->want != PKG_WANT_INSTALL && pkg->want != PKG_WANT_HOLD) { + if (f_alsoselect) { + printf(_("Selecting previously unselected package %s.\n"), + pkgbin_name(pkg, &pkg->available, pnaw_nonambig)); + return true; + } else { + printf(_("Skipping unselected package %s.\n"), + pkgbin_name(pkg, &pkg->available, pnaw_nonambig)); + return false; + } + } + + if (pkg->eflag & PKG_EFLAG_REINSTREQ) + return true; + if (pkg->status < PKG_STAT_UNPACKED) + return true; + + rc = dpkg_version_compare(&pkg->available.version, &pkg->installed.version); + if (rc > 0) { + return true; + } else if (rc == 0) { + /* Same version fully installed. */ + if (f_skipsame && pkg->available.arch == pkg->installed.arch) { + notice(_("version %.250s of %.250s already installed, skipping"), + versiondescribe(&pkg->installed.version, vdew_nonambig), + pkg_name(pkg, pnaw_nonambig)); + return false; + } else { + return true; + } + } else { + if (in_force(FORCE_DOWNGRADE)) { + warning(_("downgrading %.250s from %.250s to %.250s"), + pkg_name(pkg, pnaw_nonambig), + versiondescribe(&pkg->installed.version, vdew_nonambig), + versiondescribe(&pkg->available.version, vdew_nonambig)); + return true; + } else { + notice(_("will not downgrade %.250s from %.250s to %.250s, skipping"), + pkg_name(pkg, pnaw_nonambig), + versiondescribe(&pkg->installed.version, vdew_nonambig), + versiondescribe(&pkg->available.version, vdew_nonambig)); + return false; + } + } +} diff --git a/src/main/archives.h b/src/main/archives.h new file mode 100644 index 0000000..3d97446 --- /dev/null +++ b/src/main/archives.h @@ -0,0 +1,99 @@ +/* + * dpkg - main program for package management + * archives.h - functions common to archives.c and unpack.c + * + * Copyright © 1995 Ian Jackson + * Copyright © 2008-2013, 2015 Guillem Jover + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ARCHIVES_H +#define ARCHIVES_H + +#include + +#include + +struct tarcontext { + int backendpipe; + struct pkginfo *pkg; + /** A queue of fsys_namenode that have been extracted anew. */ + struct fsys_namenode_queue *newfiles_queue; + /** Are all “Multi-arch: same” instances about to be in sync? */ + bool pkgset_getting_in_sync; +}; + +struct pkg_deconf_list { + struct pkg_deconf_list *next; + struct pkginfo *pkg; + struct pkginfo *pkg_removal; + enum pkgwant reason; +}; + +extern struct varbuf_state fname_state; +extern struct varbuf_state fnametmp_state; +extern struct varbuf_state fnamenew_state; +extern struct varbuf fnamevb; +extern struct varbuf fnametmpvb; +extern struct varbuf fnamenewvb; +extern struct pkg_deconf_list *deconfigure; + +void clear_deconfigure_queue(void); +void enqueue_deconfigure(struct pkginfo *pkg, struct pkginfo *pkg_removal, + enum pkgwant reason); +void enqueue_conflictor(struct pkginfo *pkg); + +void cu_pathname(int argc, void **argv); +void cu_cidir(int argc, void **argv); +void cu_fileslist(int argc, void **argv); +void cu_backendpipe(int argc, void **argv); + +void cu_installnew(int argc, void **argv); + +void cu_prermupgrade(int argc, void **argv); +void cu_prerminfavour(int argc, void **argv); +void cu_preinstverynew(int argc, void **argv); +void cu_preinstnew(int argc, void **argv); +void cu_preinstupgrade(int argc, void **argv); +void cu_postrmupgrade(int argc, void **argv); + +void cu_prermdeconfigure(int argc, void **argv); +void ok_prermdeconfigure(int argc, void **argv); + +void setupfnamevbs(const char *filename); + +int +tarobject(struct tar_archive *tar, struct tar_entry *ti); +int +tarfileread(struct tar_archive *tar, char *buf, int len); +void +tar_deferred_extract(struct fsys_namenode_list *files, struct pkginfo *pkg); + +struct fsys_namenode_list * +tar_fsys_namenode_queue_push(struct fsys_namenode_queue *queue, + struct fsys_namenode *namenode); + +bool +filesavespackage(struct fsys_namenode_list *, struct pkginfo *, + struct pkginfo *pkgbeinginstalled); + +void check_conflict(struct dependency *dep, struct pkginfo *pkg, + const char *pfilename); +void check_breaks(struct dependency *dep, struct pkginfo *pkg, + const char *pfilename); + +extern int cleanup_pkg_failed, cleanup_conflictor_failed; + +#endif /* ARCHIVES_H */ diff --git a/src/main/cleanup.c b/src/main/cleanup.c new file mode 100644 index 0000000..0f7d6ff --- /dev/null +++ b/src/main/cleanup.c @@ -0,0 +1,260 @@ +/* + * dpkg - main program for package management + * cleanup.c - cleanup functions, used when we need to unwind + * + * Copyright © 1995 Ian Jackson + * Copyright © 2007-2015 Guillem Jover + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "archives.h" + +int cleanup_pkg_failed=0, cleanup_conflictor_failed=0; + +/** + * Something went wrong and we're undoing. + * + * We have the following possible situations for non-conffiles: + * «pathname».dpkg-tmp exists - in this case we want to remove + * «pathname» if it exists and replace it with «pathname».dpkg-tmp. + * This undoes the backup operation. + * «pathname».dpkg-tmp does not exist - «pathname» may be on the disk, + * as a new file which didn't fail, remove it if it is. + * + * In both cases, we also make sure we delete «pathname».dpkg-new in + * case that's still hanging around. + * + * For conffiles, we simply delete «pathname».dpkg-new. For these, + * «pathname».dpkg-tmp shouldn't exist, as we don't make a backup + * at this stage. Just to be on the safe side, though, we don't + * look for it. + */ +void cu_installnew(int argc, void **argv) { + struct fsys_namenode *namenode = argv[0]; + struct stat stab; + + cleanup_pkg_failed++; cleanup_conflictor_failed++; + + debug(dbg_eachfile, "cu_installnew '%s' flags=%o", + namenode->name, namenode->flags); + + setupfnamevbs(namenode->name); + + if (!(namenode->flags & FNNF_NEW_CONFF) && !lstat(fnametmpvb.buf,&stab)) { + /* OK, «pathname».dpkg-tmp exists. Remove «pathname» and + * restore «pathname».dpkg-tmp ... */ + if (namenode->flags & FNNF_NO_ATOMIC_OVERWRITE) { + /* If we can't do an atomic overwrite we have to delete first any + * link to the new version we may have created. */ + debug(dbg_eachfiledetail,"cu_installnew restoring nonatomic"); + if (secure_remove(fnamevb.buf) && errno != ENOENT && errno != ENOTDIR) + ohshite(_("unable to remove newly-installed version of '%.250s' to allow" + " reinstallation of backup copy"),namenode->name); + } else { + debug(dbg_eachfiledetail,"cu_installnew restoring atomic"); + } + /* Either we can do an atomic restore, or we've made room: */ + if (rename(fnametmpvb.buf,fnamevb.buf)) + ohshite(_("unable to restore backup version of '%.250s'"), namenode->name); + /* If «pathname».dpkg-tmp was still a hard link to «pathname», then the + * atomic rename did nothing, so we make sure to remove the backup. */ + else if (unlink(fnametmpvb.buf) && errno != ENOENT) + ohshite(_("unable to remove backup copy of '%.250s'"), namenode->name); + } else if (namenode->flags & FNNF_PLACED_ON_DISK) { + debug(dbg_eachfiledetail,"cu_installnew removing new file"); + if (secure_remove(fnamevb.buf) && errno != ENOENT && errno != ENOTDIR) + ohshite(_("unable to remove newly-installed version of '%.250s'"), + namenode->name); + } else { + debug(dbg_eachfiledetail,"cu_installnew not restoring"); + } + /* Whatever, we delete «pathname».dpkg-new now, if it still exists. */ + if (secure_remove(fnamenewvb.buf) && errno != ENOENT && errno != ENOTDIR) + ohshite(_("unable to remove newly-extracted version of '%.250s'"), + namenode->name); + + cleanup_pkg_failed--; cleanup_conflictor_failed--; +} + +void cu_prermupgrade(int argc, void **argv) { + struct pkginfo *pkg= (struct pkginfo*)argv[0]; + + if (cleanup_pkg_failed++) return; + maintscript_postinst(pkg, "abort-upgrade", + versiondescribe(&pkg->available.version, vdew_nonambig), + NULL); + pkg_clear_eflags(pkg, PKG_EFLAG_REINSTREQ); + post_postinst_tasks(pkg, PKG_STAT_INSTALLED); + cleanup_pkg_failed--; +} + +/* + * Also has conflictor in argv[1] and infavour in argv[2]. + * conflictor may be NULL if deconfigure was due to Breaks or multi-arch + * instance sync. + */ +void ok_prermdeconfigure(int argc, void **argv) { + struct pkginfo *deconf= (struct pkginfo*)argv[0]; + + if (cipaction->arg_int == act_install) + enqueue_package(deconf); +} + +/* + * conflictor may be NULL if deconfigure was due to Breaks or multi-arch + * instance sync. + */ +void cu_prermdeconfigure(int argc, void **argv) { + struct pkginfo *deconf= (struct pkginfo*)argv[0]; + struct pkginfo *conflictor = (struct pkginfo *)argv[1]; + struct pkginfo *infavour= (struct pkginfo*)argv[2]; + + if (conflictor) { + maintscript_postinst(deconf, "abort-deconfigure", + "in-favour", + pkgbin_name(infavour, &infavour->available, + pnaw_nonambig), + versiondescribe(&infavour->available.version, + vdew_nonambig), + "removing", + pkg_name(conflictor, pnaw_nonambig), + versiondescribe(&conflictor->installed.version, + vdew_nonambig), + NULL); + } else { + maintscript_postinst(deconf, "abort-deconfigure", + "in-favour", + pkgbin_name(infavour, &infavour->available, + pnaw_nonambig), + versiondescribe(&infavour->available.version, + vdew_nonambig), + NULL); + } + + post_postinst_tasks(deconf, PKG_STAT_INSTALLED); +} + +void cu_prerminfavour(int argc, void **argv) { + struct pkginfo *conflictor= (struct pkginfo*)argv[0]; + struct pkginfo *infavour= (struct pkginfo*)argv[1]; + + if (cleanup_conflictor_failed++) return; + maintscript_postinst(conflictor, "abort-remove", + "in-favour", + pkgbin_name(infavour, &infavour->available, + pnaw_nonambig), + versiondescribe(&infavour->available.version, + vdew_nonambig), + NULL); + pkg_clear_eflags(conflictor, PKG_EFLAG_REINSTREQ); + post_postinst_tasks(conflictor, PKG_STAT_INSTALLED); + cleanup_conflictor_failed--; +} + +void cu_preinstverynew(int argc, void **argv) { + struct pkginfo *pkg= (struct pkginfo*)argv[0]; + char *cidir= (char*)argv[1]; + char *cidirrest= (char*)argv[2]; + + if (cleanup_pkg_failed++) return; + maintscript_new(pkg, POSTRMFILE, "post-removal", cidir, cidirrest, + "abort-install", NULL); + pkg_set_status(pkg, PKG_STAT_NOTINSTALLED); + pkg_clear_eflags(pkg, PKG_EFLAG_REINSTREQ); + pkgbin_blank(&pkg->installed); + modstatdb_note(pkg); + cleanup_pkg_failed--; +} + +void cu_preinstnew(int argc, void **argv) { + struct pkginfo *pkg= (struct pkginfo*)argv[0]; + char *cidir= (char*)argv[1]; + char *cidirrest= (char*)argv[2]; + + if (cleanup_pkg_failed++) return; + maintscript_new(pkg, POSTRMFILE, "post-removal", cidir, cidirrest, + "abort-install", + versiondescribe(&pkg->installed.version, vdew_nonambig), + versiondescribe(&pkg->available.version, vdew_nonambig), + NULL); + pkg_set_status(pkg, PKG_STAT_CONFIGFILES); + pkg_clear_eflags(pkg, PKG_EFLAG_REINSTREQ); + modstatdb_note(pkg); + cleanup_pkg_failed--; +} + +void cu_preinstupgrade(int argc, void **argv) { + struct pkginfo *pkg= (struct pkginfo*)argv[0]; + char *cidir= (char*)argv[1]; + char *cidirrest= (char*)argv[2]; + enum pkgstatus *oldstatusp= (enum pkgstatus*)argv[3]; + + if (cleanup_pkg_failed++) return; + maintscript_new(pkg, POSTRMFILE, "post-removal", cidir, cidirrest, + "abort-upgrade", + versiondescribe(&pkg->installed.version, vdew_nonambig), + versiondescribe(&pkg->available.version, vdew_nonambig), + NULL); + pkg_set_status(pkg, *oldstatusp); + pkg_clear_eflags(pkg, PKG_EFLAG_REINSTREQ); + modstatdb_note(pkg); + cleanup_pkg_failed--; +} + +void cu_postrmupgrade(int argc, void **argv) { + struct pkginfo *pkg= (struct pkginfo*)argv[0]; + + if (cleanup_pkg_failed++) return; + maintscript_installed(pkg, PREINSTFILE, "pre-installation", + "abort-upgrade", + versiondescribe(&pkg->available.version, vdew_nonambig), + NULL); + cleanup_pkg_failed--; +} + +void cu_prermremove(int argc, void **argv) { + struct pkginfo *pkg= (struct pkginfo*)argv[0]; + const enum pkgstatus *oldpkgstatus = (enum pkgstatus *)argv[1]; + + if (cleanup_pkg_failed++) return; + maintscript_postinst(pkg, "abort-remove", NULL); + pkg_clear_eflags(pkg, PKG_EFLAG_REINSTREQ); + post_postinst_tasks(pkg, *oldpkgstatus); + cleanup_pkg_failed--; +} diff --git a/src/main/configure.c b/src/main/configure.c new file mode 100644 index 0000000..c2d58c7 --- /dev/null +++ b/src/main/configure.c @@ -0,0 +1,825 @@ +/* + * dpkg - main program for package management + * configure.c - configure packages + * + * Copyright © 1995 Ian Jackson + * Copyright © 1999, 2002 Wichert Akkerman + * Copyright © 2007-2015 Guillem Jover + * Copyright © 2011 Linaro Limited + * Copyright © 2011 Raphaël Hertzog + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" + +enum DPKG_ATTR_ENUM_FLAGS conffopt { + CFOF_PROMPT = DPKG_BIT(0), + CFOF_KEEP = DPKG_BIT(1), + CFOF_INSTALL = DPKG_BIT(2), + CFOF_BACKUP = DPKG_BIT(3), + CFOF_NEW_CONFF = DPKG_BIT(4), + CFOF_IS_NEW = DPKG_BIT(5), + CFOF_IS_OLD = DPKG_BIT(6), + CFOF_USER_DEL = DPKG_BIT(7), + + CFO_KEEP = CFOF_KEEP, + CFO_IDENTICAL = CFOF_KEEP, + CFO_INSTALL = CFOF_INSTALL, + CFO_NEW_CONFF = CFOF_NEW_CONFF | CFOF_INSTALL, + CFO_PROMPT = CFOF_PROMPT, + CFO_PROMPT_KEEP = CFOF_PROMPT | CFOF_KEEP, + CFO_PROMPT_INSTALL = CFOF_PROMPT | CFOF_INSTALL, +}; + +static int conffoptcells[2][2] = { + /* Distro !edited. */ /* Distro edited. */ + { CFO_KEEP, CFO_INSTALL }, /* User !edited. */ + { CFO_KEEP, CFO_PROMPT_KEEP }, /* User edited. */ +}; + +static int +show_prompt(const char *cfgfile, const char *realold, const char *realnew, + int useredited, int distedited, enum conffopt what) +{ + const char *s; + int c, cc; + + /* Flush the terminal's input in case the user involuntarily + * typed some characters. */ + tcflush(STDIN_FILENO, TCIFLUSH); + + fputs("\n", stderr); + if (strcmp(cfgfile, realold) == 0) + fprintf(stderr, _("Configuration file '%s'\n"), cfgfile); + else + fprintf(stderr, _("Configuration file '%s' (actually '%s')\n"), + cfgfile, realold); + + if (what & CFOF_IS_NEW) { + fprintf(stderr, + _(" ==> File on system created by you or by a script.\n" + " ==> File also in package provided by package maintainer.\n")); + } else { + fprintf(stderr, !useredited ? + _(" Not modified since installation.\n") : + !(what & CFOF_USER_DEL) ? + _(" ==> Modified (by you or by a script) since installation.\n") : + _(" ==> Deleted (by you or by a script) since installation.\n")); + + fprintf(stderr, distedited ? + _(" ==> Package distributor has shipped an updated version.\n") : + _(" Version in package is the same as at last installation.\n")); + } + + /* No --force-confdef but a forcible situation. */ + /* TODO: check if this condition can not be simplified to + * just !in_force(FORCE_CONFF_DEF) */ + if (!(in_force(FORCE_CONFF_DEF) && (what & (CFOF_INSTALL | CFOF_KEEP)))) { + if (in_force(FORCE_CONFF_NEW)) { + fprintf(stderr, + _(" ==> Using new file as you requested.\n")); + return 'y'; + } else if (in_force(FORCE_CONFF_OLD)) { + fprintf(stderr, + _(" ==> Using current old file as you requested.\n")); + return 'n'; + } + } + + /* Force the default action (if there is one. */ + if (in_force(FORCE_CONFF_DEF)) { + if (what & CFOF_KEEP) { + fprintf(stderr, + _(" ==> Keeping old config file as default.\n")); + return 'n'; + } else if (what & CFOF_INSTALL) { + fprintf(stderr, + _(" ==> Using new config file as default.\n")); + return 'y'; + } + } + + fprintf(stderr, + _(" What would you like to do about it ? Your options are:\n" + " Y or I : install the package maintainer's version\n" + " N or O : keep your currently-installed version\n" + " D : show the differences between the versions\n" + " Z : start a shell to examine the situation\n")); + + if (what & CFOF_KEEP) + fprintf(stderr, + _(" The default action is to keep your current version.\n")); + else if (what & CFOF_INSTALL) + fprintf(stderr, + _(" The default action is to install the new version.\n")); + + s = path_basename(cfgfile); + fprintf(stderr, "*** %s (Y/I/N/O/D/Z) %s ? ", s, + (what & CFOF_KEEP) ? _("[default=N]") : + (what & CFOF_INSTALL) ? _("[default=Y]") : + _("[no default]")); + + if (ferror(stderr)) + ohshite(_("error writing to stderr, discovered before conffile prompt")); + + cc = 0; + while ((c = getchar()) != EOF && c != '\n') + if (!isspace(c) && !cc) + cc = tolower(c); + + if (c == EOF) { + if (ferror(stdin)) + ohshite(_("read error on stdin at conffile prompt")); + ohshit(_("end of file on stdin at conffile prompt")); + } + + if (!cc) { + if (what & CFOF_KEEP) + return 'n'; + else if (what & CFOF_INSTALL) + return 'y'; + } + + return cc; +} + +/** + * Show a diff between two files. + * + * @param old The path to the old file. + * @param new The path to the new file. + */ +static void +show_diff(const char *old, const char *new) +{ + struct pager *pager; + pid_t pid; + + pager = pager_spawn(_("conffile difference visualizer")); + + pid = subproc_fork(); + if (!pid) { + /* Child process. */ + struct command cmd; + + command_init(&cmd, DIFF, _("conffile difference visualizer")); + command_add_arg(&cmd, DIFF); + command_add_arg(&cmd, "-Nu"); + command_add_arg(&cmd, old); + command_add_arg(&cmd, new); + command_exec(&cmd); + } + + /* Parent process. */ + subproc_reap(pid, _("conffile difference visualizer"), SUBPROC_NOCHECK); + pager_reap(pager); +} + +/** + * Spawn a new shell. + * + * Create a subprocess and execute a shell to allow the user to manually + * solve the conffile conflict. + * + * @param confold The path to the old conffile. + * @param confnew The path to the new conffile. + */ +static void +spawn_shell(const char *confold, const char *confnew) +{ + pid_t pid; + + fputs(_("Useful environment variables:\n"), stderr); + fputs(" - DPKG_SHELL_REASON\n", stderr); + fputs(" - DPKG_CONFFILE_OLD\n", stderr); + fputs(" - DPKG_CONFFILE_NEW\n", stderr); + fputs(_("Type 'exit' when you're done.\n"), stderr); + + pid = subproc_fork(); + if (!pid) { + /* Set useful variables for the user. */ + setenv("DPKG_SHELL_REASON", "conffile-prompt", 1); + setenv("DPKG_CONFFILE_OLD", confold, 1); + setenv("DPKG_CONFFILE_NEW", confnew, 1); + + command_shell(NULL, _("conffile shell")); + } + + /* Parent process. */ + subproc_reap(pid, _("conffile shell"), SUBPROC_NOCHECK); +} + +/** + * Prompt the user for how to resolve a conffile conflict. + * + * When encountering a conffile conflict during configuration, the user will + * normally be presented with a textual menu of possible actions. This + * behavior is modified via various --force flags and perhaps on whether + * or not a terminal is available to do the prompting. + * + * @param pkg The package owning the conffile. + * @param cfgfile The path to the old conffile. + * @param realold The path to the old conffile, dereferenced in case of a + * symlink, otherwise equal to cfgfile. + * @param realnew The path to the new conffile, dereferenced in case of a + * symlink). + * @param useredited A flag to indicate whether the file has been edited + * locally. Set to nonzero to indicate that the file has been modified. + * @param distedited A flag to indicate whether the file has been updated + * between package versions. Set to nonzero to indicate that the file + * has been updated. + * @param what Hints on what action should be taken by default. + * + * @return The action which should be taken based on user input and/or the + * default actions as configured by cmdline/configuration options. + */ +static enum conffopt +promptconfaction(struct pkginfo *pkg, const char *cfgfile, + const char *realold, const char *realnew, + int useredited, int distedited, enum conffopt what) +{ + int cc; + + if (!(what & CFOF_PROMPT)) + return what; + + statusfd_send("status: %s : %s : '%s' '%s' %i %i ", + cfgfile, "conffile-prompt", + realold, realnew, useredited, distedited); + + do { + cc = show_prompt(cfgfile, realold, realnew, + useredited, distedited, what); + + /* FIXME: Say something if silently not install. */ + if (cc == 'd') + show_diff(realold, realnew); + + if (cc == 'z') + spawn_shell(realold, realnew); + } while (!strchr("yino", cc)); + + log_message("conffile %s %s", cfgfile, + (cc == 'i' || cc == 'y') ? "install" : "keep"); + + what &= CFOF_USER_DEL; + + switch (cc) { + case 'i': + case 'y': + what |= CFOF_INSTALL | CFOF_BACKUP; + break; + + case 'n': + case 'o': + what |= CFOF_KEEP | CFOF_BACKUP; + break; + + default: + internerr("unknown response '%d'", cc); + } + + return what; +} + +/** + * Configure the ghost conffile instance. + * + * When the first instance of a package set is configured, the *.dpkg-new + * files gets installed into their destination, which makes configuration of + * conffiles from subsequent package instances be skipped along with updates + * to the Conffiles field hash. + * + * In case the conffile has already been processed, sync the hash from an + * already configured package instance conffile. + * + * @param pkg The current package being configured. + * @param conff The current conffile being configured. + */ +static void +deferred_configure_ghost_conffile(struct pkginfo *pkg, struct conffile *conff) +{ + struct pkginfo *otherpkg; + struct conffile *otherconff; + + for (otherpkg = &pkg->set->pkg; otherpkg; otherpkg = otherpkg->arch_next) { + if (otherpkg == pkg) + continue; + if (otherpkg->status <= PKG_STAT_HALFCONFIGURED) + continue; + + for (otherconff = otherpkg->installed.conffiles; otherconff; + otherconff = otherconff->next) { + if (otherconff->obsolete || otherconff->remove_on_upgrade) + continue; + + /* Check if we need to propagate the new hash from + * an already processed conffile in another package + * instance. */ + if (strcmp(otherconff->name, conff->name) == 0) { + conff->hash = otherconff->hash; + modstatdb_note(pkg); + return; + } + } + } +} + +static void +deferred_configure_conffile(struct pkginfo *pkg, struct conffile *conff) +{ + struct fsys_namenode *usenode; + char currenthash[MD5HASHLEN + 1], newdisthash[MD5HASHLEN + 1]; + int useredited, distedited; + enum conffopt what; + struct stat stab; + struct varbuf cdr = VARBUF_INIT, cdr2 = VARBUF_INIT; + char *cdr2rest; + int rc; + + usenode = namenodetouse(fsys_hash_find_node(conff->name, FHFF_NO_COPY), + pkg, &pkg->installed); + + rc = conffderef(pkg, &cdr, usenode->name); + if (rc == -1) { + conff->hash = EMPTYHASHFLAG; + return; + } + md5hash(pkg, currenthash, cdr.buf); + + varbuf_set_varbuf(&cdr2, &cdr); + /* XXX: Make sure there's enough room for extensions. */ + varbuf_grow(&cdr2, 50); + cdr2rest = cdr2.buf + strlen(cdr.buf); + /* From now on we can just strcpy(cdr2rest, extension); */ + + strcpy(cdr2rest, DPKGNEWEXT); + /* If the .dpkg-new file is no longer there, ignore this one. */ + if (lstat(cdr2.buf, &stab)) { + if (errno == ENOENT) { + /* But, sync the conffile hash value from another + * package set instance. */ + deferred_configure_ghost_conffile(pkg, conff); + return; + } + ohshite(_("unable to stat new distributed conffile '%.250s'"), + cdr2.buf); + } + md5hash(pkg, newdisthash, cdr2.buf); + + /* Copy the permissions from the installed version to the new + * distributed version. */ + if (!stat(cdr.buf, &stab)) + file_copy_perms(cdr.buf, cdr2.buf); + else if (errno != ENOENT) + ohshite(_("unable to stat current installed conffile '%.250s'"), + cdr.buf); + + /* Select what to do. */ + if (strcmp(currenthash, newdisthash) == 0) { + /* They're both the same so there's no point asking silly + * questions. */ + useredited = -1; + distedited = -1; + what = CFO_IDENTICAL; + } else if (strcmp(currenthash, NONEXISTENTFLAG) == 0 && in_force(FORCE_CONFF_MISS)) { + fprintf(stderr, + _("\n" + "Configuration file '%s', does not exist on system.\n" + "Installing new config file as you requested.\n"), + usenode->name); + what = CFO_NEW_CONFF; + useredited = -1; + distedited = -1; + } else if (strcmp(conff->hash, NEWCONFFILEFLAG) == 0) { + if (strcmp(currenthash, NONEXISTENTFLAG) == 0) { + what = CFO_NEW_CONFF; + useredited = -1; + distedited = -1; + } else { + useredited = 1; + distedited = 1; + what = conffoptcells[useredited][distedited] | + CFOF_IS_NEW; + } + } else { + useredited = strcmp(conff->hash, currenthash) != 0; + distedited = strcmp(conff->hash, newdisthash) != 0; + + if (in_force(FORCE_CONFF_ASK) && useredited) + what = CFO_PROMPT_KEEP; + else + what = conffoptcells[useredited][distedited]; + + if (strcmp(currenthash, NONEXISTENTFLAG) == 0) + what |= CFOF_USER_DEL; + } + + debug(dbg_conff, + "deferred_configure '%s' (= '%s') useredited=%d distedited=%d what=%o", + usenode->name, cdr.buf, useredited, distedited, what); + + what = promptconfaction(pkg, usenode->name, cdr.buf, cdr2.buf, + useredited, distedited, what); + + switch (what & ~(CFOF_IS_NEW | CFOF_USER_DEL)) { + case CFO_KEEP | CFOF_BACKUP: + strcpy(cdr2rest, DPKGOLDEXT); + if (unlink(cdr2.buf) && errno != ENOENT) + warning(_("%s: failed to remove old backup '%.250s': %s"), + pkg_name(pkg, pnaw_nonambig), cdr2.buf, + strerror(errno)); + + varbuf_add_str(&cdr, DPKGDISTEXT); + varbuf_end_str(&cdr); + strcpy(cdr2rest, DPKGNEWEXT); + trig_path_activate(usenode, pkg); + if (rename(cdr2.buf, cdr.buf)) + warning(_("%s: failed to rename '%.250s' to '%.250s': %s"), + pkg_name(pkg, pnaw_nonambig), cdr2.buf, cdr.buf, + strerror(errno)); + break; + case CFO_KEEP: + strcpy(cdr2rest, DPKGNEWEXT); + if (unlink(cdr2.buf)) + warning(_("%s: failed to remove '%.250s': %s"), + pkg_name(pkg, pnaw_nonambig), cdr2.buf, + strerror(errno)); + break; + case CFO_INSTALL | CFOF_BACKUP: + strcpy(cdr2rest, DPKGDISTEXT); + if (unlink(cdr2.buf) && errno != ENOENT) + warning(_("%s: failed to remove old distributed version '%.250s': %s"), + pkg_name(pkg, pnaw_nonambig), cdr2.buf, + strerror(errno)); + strcpy(cdr2rest, DPKGOLDEXT); + if (unlink(cdr2.buf) && errno != ENOENT) + warning(_("%s: failed to remove '%.250s' (before overwrite): %s"), + pkg_name(pkg, pnaw_nonambig), cdr2.buf, + strerror(errno)); + if (!(what & CFOF_USER_DEL)) + if (link(cdr.buf, cdr2.buf)) + warning(_("%s: failed to link '%.250s' to '%.250s': %s"), + pkg_name(pkg, pnaw_nonambig), cdr.buf, + cdr2.buf, strerror(errno)); + /* Fall through. */ + case CFO_INSTALL: + printf(_("Installing new version of config file %s ...\n"), + usenode->name); + /* Fall through. */ + case CFO_NEW_CONFF: + strcpy(cdr2rest, DPKGNEWEXT); + trig_path_activate(usenode, pkg); + if (rename(cdr2.buf, cdr.buf)) + ohshite(_("unable to install '%.250s' as '%.250s'"), + cdr2.buf, cdr.buf); + break; + default: + internerr("unknown conffopt '%d'", what); + } + + conff->hash = nfstrsave(newdisthash); + modstatdb_note(pkg); + + varbuf_destroy(&cdr); + varbuf_destroy(&cdr2); +} + +/** + * Process the deferred configure package. + * + * @param pkg The package to act on. + */ +void +deferred_configure(struct pkginfo *pkg) +{ + struct varbuf aemsgs = VARBUF_INIT; + struct conffile *conff; + struct pkginfo *otherpkg; + enum dep_check ok; + + if (pkg->status == PKG_STAT_NOTINSTALLED) + ohshit(_("no package named '%s' is installed, cannot configure"), + pkg_name(pkg, pnaw_nonambig)); + if (pkg->status == PKG_STAT_INSTALLED) + ohshit(_("package %.250s is already installed and configured"), + pkg_name(pkg, pnaw_nonambig)); + if (pkg->status != PKG_STAT_UNPACKED && + pkg->status != PKG_STAT_HALFCONFIGURED) + ohshit(_("package %.250s is not ready for configuration\n" + " cannot configure (current status '%.250s')"), + pkg_name(pkg, pnaw_nonambig), + pkg_status_name(pkg)); + + for (otherpkg = &pkg->set->pkg; otherpkg; otherpkg = otherpkg->arch_next) { + if (otherpkg == pkg) + continue; + if (otherpkg->status <= PKG_STAT_CONFIGFILES) + continue; + + if (otherpkg->status < PKG_STAT_UNPACKED) + ohshit(_("package %s cannot be configured because " + "%s is not ready (current status '%s')"), + pkg_name(pkg, pnaw_always), + pkg_name(otherpkg, pnaw_always), + pkg_status_name(otherpkg)); + + if (dpkg_version_compare(&pkg->installed.version, + &otherpkg->installed.version)) + ohshit(_("package %s %s cannot be configured because " + "%s is at a different version (%s)"), + pkg_name(pkg, pnaw_always), + versiondescribe(&pkg->installed.version, + vdew_nonambig), + pkg_name(otherpkg, pnaw_always), + versiondescribe(&otherpkg->installed.version, + vdew_nonambig)); + } + + if (dependtry >= DEPEND_TRY_CYCLES) + if (findbreakcycle(pkg)) + sincenothing = 0; + + ok = dependencies_ok(pkg, NULL, &aemsgs); + if (ok == DEP_CHECK_DEFER) { + varbuf_destroy(&aemsgs); + ensure_package_clientdata(pkg); + pkg->clientdata->istobe = PKG_ISTOBE_INSTALLNEW; + enqueue_package(pkg); + return; + } + + trigproc_reset_cycle(); + + /* + * At this point removal from the queue is confirmed. This + * represents irreversible progress wrt trigger cycles. Only + * packages in PKG_STAT_UNPACKED are automatically added to the + * configuration queue, and during configuration and trigger + * processing new packages can't enter into unpacked. + */ + + ok = breakses_ok(pkg, &aemsgs) ? ok : DEP_CHECK_HALT; + if (ok == DEP_CHECK_HALT) { + sincenothing = 0; + varbuf_end_str(&aemsgs); + notice(_("dependency problems prevent configuration of %s:\n%s"), + pkg_name(pkg, pnaw_nonambig), aemsgs.buf); + varbuf_destroy(&aemsgs); + ohshit(_("dependency problems - leaving unconfigured")); + } else if (aemsgs.used) { + varbuf_end_str(&aemsgs); + notice(_("%s: dependency problems, but configuring anyway as you requested:\n%s"), + pkg_name(pkg, pnaw_nonambig), aemsgs.buf); + } + varbuf_destroy(&aemsgs); + sincenothing = 0; + + if (pkg->eflag & PKG_EFLAG_REINSTREQ) + forcibleerr(FORCE_REMOVE_REINSTREQ, + _("package is in a very bad inconsistent state; you should\n" + " reinstall it before attempting configuration")); + + printf(_("Setting up %s (%s) ...\n"), pkg_name(pkg, pnaw_nonambig), + versiondescribe(&pkg->installed.version, vdew_nonambig)); + log_action("configure", pkg, &pkg->installed); + + trig_activate_packageprocessing(pkg); + + if (f_noact) { + pkg_set_status(pkg, PKG_STAT_INSTALLED); + ensure_package_clientdata(pkg); + pkg->clientdata->istobe = PKG_ISTOBE_NORMAL; + return; + } + + if (pkg->status == PKG_STAT_UNPACKED) { + debug(dbg_general, "deferred_configure updating conffiles"); + /* This will not do at all the right thing with overridden + * conffiles or conffiles that are the ‘target’ of an override; + * all the references here would be to the ‘contested’ + * filename, and in any case there'd only be one hash for both + * ‘versions’ of the conffile. + * + * Overriding conffiles is a silly thing to do anyway :-). */ + + modstatdb_note(pkg); + + /* On entry, the ‘new’ version of each conffile has been + * unpacked as ‘*.dpkg-new’, and the ‘installed’ version is + * as-yet untouched in ‘*’. The hash of the ‘old distributed’ + * version is in the conffiles data for the package. If + * ‘*.dpkg-new’ no longer exists we assume that we've + * already processed this one. */ + for (conff = pkg->installed.conffiles; conff; conff = conff->next) { + if (conff->obsolete || conff->remove_on_upgrade) + continue; + deferred_configure_conffile(pkg, conff); + } + + pkg_set_status(pkg, PKG_STAT_HALFCONFIGURED); + } + + if (pkg->status != PKG_STAT_HALFCONFIGURED) + internerr("package %s in state %s, instead of half-configured", + pkg_name(pkg, pnaw_always), pkg_status_name(pkg)); + + modstatdb_note(pkg); + + maintscript_postinst(pkg, "configure", + dpkg_version_is_informative(&pkg->configversion) ? + versiondescribe(&pkg->configversion, + vdew_nonambig) : "", + NULL); + + pkg_reset_eflags(pkg); + pkg->trigpend_head = NULL; + post_postinst_tasks(pkg, PKG_STAT_INSTALLED); +} + +/** + * Dereference a file by following all possibly used symlinks. + * + * @param[in] pkg The package to act on. + * @param[out] result The dereference conffile path. + * @param[in] in The conffile path to dereference. + * + * @return An error code for the operation. + * @retval 0 Everything went ok. + * @retval -1 Otherwise. + */ +int +conffderef(struct pkginfo *pkg, struct varbuf *result, const char *in) +{ + static struct varbuf target = VARBUF_INIT; + struct stat stab; + ssize_t r; + int loopprotect; + + varbuf_set_str(result, dpkg_fsys_get_dir()); + varbuf_add_str(result, in); + varbuf_end_str(result); + + loopprotect = 0; + + for (;;) { + debug(dbg_conffdetail, "conffderef in='%s' current working='%s'", + in, result->buf); + if (lstat(result->buf, &stab)) { + if (errno != ENOENT) + warning(_("%s: unable to stat config file '%s'\n" + " (= '%s'): %s"), + pkg_name(pkg, pnaw_nonambig), in, + result->buf, strerror(errno)); + debug(dbg_conffdetail, "conffderef nonexistent"); + return 0; + } else if (S_ISREG(stab.st_mode)) { + debug(dbg_conff, "conffderef in='%s' result='%s'", + in, result->buf); + return 0; + } else if (S_ISLNK(stab.st_mode)) { + debug(dbg_conffdetail, "conffderef symlink loopprotect=%d", + loopprotect); + if (loopprotect++ >= 25) { + warning(_("%s: config file '%s' is a circular link\n" + " (= '%s')"), + pkg_name(pkg, pnaw_nonambig), in, + result->buf); + return -1; + } + + varbuf_reset(&target); + varbuf_grow(&target, stab.st_size + 1); + r = readlink(result->buf, target.buf, target.size); + if (r < 0) { + warning(_("%s: unable to readlink conffile '%s'\n" + " (= '%s'): %s"), + pkg_name(pkg, pnaw_nonambig), in, + result->buf, strerror(errno)); + return -1; + } else if (r != stab.st_size) { + warning(_("symbolic link '%.250s' size has " + "changed from %jd to %zd"), + result->buf, (intmax_t)stab.st_size, r); + /* If the returned size is smaller, let's + * proceed, otherwise error out. */ + if (r > stab.st_size) + return -1; + } + varbuf_trunc(&target, r); + varbuf_end_str(&target); + + debug(dbg_conffdetail, + "conffderef readlink gave %zd, '%s'", + r, target.buf); + + if (target.buf[0] == '/') { + varbuf_set_str(result, dpkg_fsys_get_dir()); + debug(dbg_conffdetail, + "conffderef readlink absolute"); + } else { + for (r = result->used - 1; r > 0 && result->buf[r] != '/'; r--) + ; + if (r < 0) { + warning(_("%s: conffile '%.250s' resolves to degenerate filename\n" + " ('%s' is a symlink to '%s')"), + pkg_name(pkg, pnaw_nonambig), + in, result->buf, target.buf); + return -1; + } + if (result->buf[r] == '/') + r++; + varbuf_trunc(result, r); + debug(dbg_conffdetail, + "conffderef readlink relative to '%.*s'", + (int)result->used, result->buf); + } + varbuf_add_varbuf(result, &target); + varbuf_end_str(result); + } else { + warning(_("%s: conffile '%.250s' is not a plain file or symlink (= '%s')"), + pkg_name(pkg, pnaw_nonambig), in, result->buf); + return -1; + } + } +} + +/** + * Generate a file contents MD5 hash. + * + * The caller is responsible for providing a buffer for the hash result + * at least MD5HASHLEN + 1 characters long. + * + * @param[in] pkg The package to act on. + * @param[out] hashbuf The buffer to store the generated hash. + * @param[in] fn The filename. + */ +void +md5hash(struct pkginfo *pkg, char *hashbuf, const char *fn) +{ + struct dpkg_error err; + static int fd; + + fd = open(fn, O_RDONLY); + + if (fd >= 0) { + push_cleanup(cu_closefd, ehflag_bombout, 1, &fd); + if (fd_md5(fd, hashbuf, -1, &err) < 0) + ohshit(_("cannot compute MD5 digest for file '%s': %s"), + fn, err.str); + pop_cleanup(ehflag_normaltidy); /* fd = open(cdr.buf) */ + close(fd); + } else if (errno == ENOENT) { + strcpy(hashbuf, NONEXISTENTFLAG); + } else { + warning(_("%s: unable to open %s to compute its digest: %s"), + pkg_name(pkg, pnaw_nonambig), fn, strerror(errno)); + strcpy(hashbuf, EMPTYHASHFLAG); + } +} diff --git a/src/main/depcon.c b/src/main/depcon.c new file mode 100644 index 0000000..e8efdd3 --- /dev/null +++ b/src/main/depcon.c @@ -0,0 +1,705 @@ +/* + * dpkg - main program for package management + * depcon.c - dependency and conflict checking + * + * Copyright © 1994,1995 Ian Jackson + * Copyright © 2006-2014 Guillem Jover + * Copyright © 2011 Linaro Limited + * Copyright © 2011 Raphaël Hertzog + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "main.h" + +struct deppossi_pkg_iterator { + struct deppossi *possi; + struct pkginfo *pkg_next; + enum which_pkgbin which_pkgbin; +}; + +struct deppossi_pkg_iterator * +deppossi_pkg_iter_new(struct deppossi *possi, enum which_pkgbin wpb) +{ + struct deppossi_pkg_iterator *iter; + + iter = m_malloc(sizeof(*iter)); + iter->possi = possi; + iter->pkg_next = &possi->ed->pkg; + iter->which_pkgbin = wpb; + + return iter; +} + +struct pkginfo * +deppossi_pkg_iter_next(struct deppossi_pkg_iterator *iter) +{ + struct pkginfo *pkg_cur; + struct pkgbin *pkgbin; + + while ((pkg_cur = iter->pkg_next)) { + iter->pkg_next = pkg_cur->arch_next; + + switch (iter->which_pkgbin) { + case wpb_installed: + pkgbin = &pkg_cur->installed; + break; + case wpb_available: + pkgbin = &pkg_cur->available; + break; + case wpb_by_istobe: + if (pkg_cur->clientdata && + pkg_cur->clientdata->istobe == PKG_ISTOBE_INSTALLNEW) + pkgbin = &pkg_cur->available; + else + pkgbin = &pkg_cur->installed; + break; + default: + internerr("unknown which_pkgbin %d", iter->which_pkgbin); + } + + if (archsatisfied(pkgbin, iter->possi)) + return pkg_cur; + } + + return NULL; +} + +void +deppossi_pkg_iter_free(struct deppossi_pkg_iterator *iter) +{ + free(iter); +} + +struct cyclesofarlink { + struct cyclesofarlink *prev; + struct pkginfo *pkg; + struct deppossi *possi; +}; + +static bool findbreakcyclerecursive(struct pkginfo *pkg, + struct cyclesofarlink *sofar); + +static bool +foundcyclebroken(struct cyclesofarlink *thislink, struct cyclesofarlink *sofar, + struct pkginfo *dependedon, struct deppossi *possi) +{ + struct cyclesofarlink *sol; + + if(!possi) + return false; + + /* We're investigating the dependency ‘possi’ to see if it + * is part of a loop. To this end we look to see whether the + * depended-on package is already one of the packages whose + * dependencies we're searching. */ + for (sol = sofar; sol && sol->pkg != dependedon; sol = sol->prev); + + /* If not, we do a recursive search on it to see what we find. */ + if (!sol) + return findbreakcyclerecursive(dependedon, thislink); + + debug(dbg_depcon,"found cycle"); + /* Right, we now break one of the links. We prefer to break + * a dependency of a package without a postinst script, as + * this is a null operation. If this is not possible we break + * the other link in the recursive calling tree which mentions + * this package (this being the first package involved in the + * cycle). It doesn't particularly matter which we pick, but if + * we break the earliest dependency we came across we may be + * able to do something straight away when findbreakcycle returns. */ + sofar= thislink; + for (sol = sofar; !(sol != sofar && sol->pkg == dependedon); sol = sol->prev) { + if (!pkg_infodb_has_file(sol->pkg, &sol->pkg->installed, POSTINSTFILE)) + break; + } + + /* Now we have either a package with no postinst, or the other + * occurrence of the current package in the list. */ + sol->possi->cyclebreak = true; + + debug(dbg_depcon, "cycle broken at %s -> %s", + pkg_name(sol->possi->up->up, pnaw_always), sol->possi->ed->name); + + return true; +} + +/** + * Cycle breaking works recursively down the package dependency tree. + * + * ‘sofar’ is the list of packages we've descended down already - if we + * encounter any of its packages again in a dependency we have found a cycle. + */ +static bool +findbreakcyclerecursive(struct pkginfo *pkg, struct cyclesofarlink *sofar) +{ + struct cyclesofarlink thislink, *sol; + struct dependency *dep; + struct deppossi *possi, *providelink; + struct pkginfo *provider, *pkg_pos; + + if (pkg->clientdata->color == PKG_CYCLE_BLACK) + return false; + pkg->clientdata->color = PKG_CYCLE_GRAY; + + if (debug_has_flag(dbg_depcondetail)) { + struct varbuf str_pkgs = VARBUF_INIT; + + for (sol = sofar; sol; sol = sol->prev) { + varbuf_add_str(&str_pkgs, " <- "); + varbuf_add_pkgbin_name(&str_pkgs, sol->pkg, &sol->pkg->installed, pnaw_nonambig); + } + varbuf_end_str(&str_pkgs); + debug(dbg_depcondetail, "findbreakcyclerecursive %s %s", + pkg_name(pkg, pnaw_always), str_pkgs.buf); + varbuf_destroy(&str_pkgs); + } + thislink.pkg= pkg; + thislink.prev = sofar; + thislink.possi = NULL; + for (dep= pkg->installed.depends; dep; dep= dep->next) { + if (dep->type != dep_depends && dep->type != dep_predepends) continue; + for (possi= dep->list; possi; possi= possi->next) { + struct deppossi_pkg_iterator *possi_iter; + + /* Don't find the same cycles again. */ + if (possi->cyclebreak) continue; + thislink.possi= possi; + + possi_iter = deppossi_pkg_iter_new(possi, wpb_installed); + while ((pkg_pos = deppossi_pkg_iter_next(possi_iter))) + if (foundcyclebroken(&thislink, sofar, pkg_pos, possi)) { + deppossi_pkg_iter_free(possi_iter); + return true; + } + deppossi_pkg_iter_free(possi_iter); + + /* Right, now we try all the providers ... */ + for (providelink = possi->ed->depended.installed; + providelink; + providelink = providelink->rev_next) { + if (providelink->up->type != dep_provides) continue; + provider= providelink->up->up; + if (provider->clientdata->istobe == PKG_ISTOBE_NORMAL) + continue; + /* We don't break things at ‘provides’ links, so ‘possi’ is + * still the one we use. */ + if (foundcyclebroken(&thislink, sofar, provider, possi)) + return true; + } + } + } + /* Nope, we didn't find a cycle to break. */ + pkg->clientdata->color = PKG_CYCLE_BLACK; + return false; +} + +bool +findbreakcycle(struct pkginfo *pkg) +{ + struct pkg_hash_iter *iter; + struct pkginfo *tpkg; + + /* Clear the visited flag of all packages before we traverse them. */ + iter = pkg_hash_iter_new(); + while ((tpkg = pkg_hash_iter_next_pkg(iter))) { + ensure_package_clientdata(tpkg); + tpkg->clientdata->color = PKG_CYCLE_WHITE; + } + pkg_hash_iter_free(iter); + + return findbreakcyclerecursive(pkg, NULL); +} + +void describedepcon(struct varbuf *addto, struct dependency *dep) { + struct varbuf depstr = VARBUF_INIT; + + varbufdependency(&depstr, dep); + varbuf_end_str(&depstr); + + switch (dep->type) { + case dep_depends: + varbuf_printf(addto, _("%s depends on %s"), + pkg_name(dep->up, pnaw_nonambig), depstr.buf); + break; + case dep_predepends: + varbuf_printf(addto, _("%s pre-depends on %s"), + pkg_name(dep->up, pnaw_nonambig), depstr.buf); + break; + case dep_recommends: + varbuf_printf(addto, _("%s recommends %s"), + pkg_name(dep->up, pnaw_nonambig), depstr.buf); + break; + case dep_suggests: + varbuf_printf(addto, _("%s suggests %s"), + pkg_name(dep->up, pnaw_nonambig), depstr.buf); + break; + case dep_breaks: + varbuf_printf(addto, _("%s breaks %s"), + pkg_name(dep->up, pnaw_nonambig), depstr.buf); + break; + case dep_conflicts: + varbuf_printf(addto, _("%s conflicts with %s"), + pkg_name(dep->up, pnaw_nonambig), depstr.buf); + break; + case dep_enhances: + varbuf_printf(addto, _("%s enhances %s"), + pkg_name(dep->up, pnaw_nonambig), depstr.buf); + break; + default: + internerr("unknown deptype '%d'", dep->type); + } + + varbuf_destroy(&depstr); +} + +/* + * *whynot must already have been initialized; it need not be + * empty though - it will be reset before use. + * + * If depisok returns false for ‘not OK’ it will contain a description, + * newline-terminated BUT NOT NUL-TERMINATED, of the reason. + * + * If depisok returns true it will contain garbage. + * allowunconfigd should be non-zero during the ‘Pre-Depends’ checking + * before a package is unpacked, when it is sufficient for the package + * to be unpacked provided that both the unpacked and previously-configured + * versions are acceptable. + * + * On false return (‘not OK’), *canfixbyremove refers to a package which + * if removed (dep_conflicts) or deconfigured (dep_breaks) will fix + * the problem. Caller may pass NULL for canfixbyremove and need not + * initialize *canfixbyremove. + * + * On false return (‘not OK’), *canfixbytrigaw refers to a package which + * can fix the problem if all the packages listed in Triggers-Awaited have + * their triggers processed. Caller may pass NULL for canfixbytrigaw and + * need not initialize *canfixbytrigaw. + */ +bool +depisok(struct dependency *dep, struct varbuf *whynot, + struct pkginfo **canfixbyremove, struct pkginfo **canfixbytrigaw, + bool allowunconfigd) +{ + struct deppossi *possi; + struct deppossi *provider; + struct pkginfo *pkg_pos; + + /* Use this buffer so that when internationalization comes along we + * don't have to rewrite the code completely, only redo the sprintf strings + * (assuming we have the fancy argument-number-specifiers). + * Allow 250x3 for package names, versions, &c, + 250 for ourselves. */ + char linebuf[1024]; + + if (dep->type != dep_depends && + dep->type != dep_predepends && + dep->type != dep_breaks && + dep->type != dep_conflicts && + dep->type != dep_recommends && + dep->type != dep_suggests && + dep->type != dep_enhances) + internerr("unknown dependency type %d", dep->type); + + if (canfixbyremove) + *canfixbyremove = NULL; + if (canfixbytrigaw) + *canfixbytrigaw = NULL; + + /* The dependency is always OK if we're trying to remove the depend*ing* + * package. */ + switch (dep->up->clientdata->istobe) { + case PKG_ISTOBE_REMOVE: + case PKG_ISTOBE_DECONFIGURE: + return true; + case PKG_ISTOBE_NORMAL: + /* Only installed packages can be made dependency problems. */ + switch (dep->up->status) { + case PKG_STAT_INSTALLED: + case PKG_STAT_TRIGGERSPENDING: + case PKG_STAT_TRIGGERSAWAITED: + break; + case PKG_STAT_HALFCONFIGURED: + case PKG_STAT_UNPACKED: + case PKG_STAT_HALFINSTALLED: + if (dep->type == dep_predepends || + dep->type == dep_conflicts || + dep->type == dep_breaks) + break; + /* Fall through. */ + case PKG_STAT_CONFIGFILES: + case PKG_STAT_NOTINSTALLED: + return true; + default: + internerr("unknown status depending '%d'", dep->up->status); + } + break; + case PKG_ISTOBE_INSTALLNEW: + case PKG_ISTOBE_PREINSTALL: + break; + default: + internerr("unknown istobe depending '%d'", dep->up->clientdata->istobe); + } + + /* Describe the dependency, in case we have to moan about it. */ + varbuf_reset(whynot); + varbuf_add_char(whynot, ' '); + describedepcon(whynot, dep); + varbuf_add_char(whynot, '\n'); + + /* TODO: Check dep_enhances as well. */ + if (dep->type == dep_depends || dep->type == dep_predepends || + dep->type == dep_recommends || dep->type == dep_suggests ) { + /* Go through the alternatives. As soon as we find one that + * we like, we return ‘true’ straight away. Otherwise, when we get to + * the end we'll have accumulated all the reasons in whynot and + * can return ‘false’. */ + + for (possi= dep->list; possi; possi= possi->next) { + struct deppossi_pkg_iterator *possi_iter; + + possi_iter = deppossi_pkg_iter_new(possi, wpb_by_istobe); + while ((pkg_pos = deppossi_pkg_iter_next(possi_iter))) { + switch (pkg_pos->clientdata->istobe) { + case PKG_ISTOBE_REMOVE: + sprintf(linebuf, _(" %.250s is to be removed.\n"), + pkg_name(pkg_pos, pnaw_nonambig)); + break; + case PKG_ISTOBE_DECONFIGURE: + sprintf(linebuf, _(" %.250s is to be deconfigured.\n"), + pkg_name(pkg_pos, pnaw_nonambig)); + break; + case PKG_ISTOBE_INSTALLNEW: + if (versionsatisfied(&pkg_pos->available, possi)) { + deppossi_pkg_iter_free(possi_iter); + return true; + } + sprintf(linebuf, _(" %.250s is to be installed, but is version " + "%.250s.\n"), + pkgbin_name(pkg_pos, &pkg_pos->available, pnaw_nonambig), + versiondescribe(&pkg_pos->available.version, vdew_nonambig)); + break; + case PKG_ISTOBE_NORMAL: + case PKG_ISTOBE_PREINSTALL: + switch (pkg_pos->status) { + case PKG_STAT_INSTALLED: + case PKG_STAT_TRIGGERSPENDING: + if (versionsatisfied(&pkg_pos->installed, possi)) { + deppossi_pkg_iter_free(possi_iter); + return true; + } + sprintf(linebuf, _(" %.250s is installed, but is version " + "%.250s.\n"), + pkg_name(pkg_pos, pnaw_nonambig), + versiondescribe(&pkg_pos->installed.version, vdew_nonambig)); + break; + case PKG_STAT_NOTINSTALLED: + /* Don't say anything about this yet - it might be a virtual package. + * Later on, if nothing has put anything in linebuf, we know that it + * isn't and issue a diagnostic then. */ + *linebuf = '\0'; + break; + case PKG_STAT_TRIGGERSAWAITED: + if (canfixbytrigaw && versionsatisfied(&pkg_pos->installed, possi)) + *canfixbytrigaw = pkg_pos; + /* Fall through. */ + case PKG_STAT_UNPACKED: + case PKG_STAT_HALFCONFIGURED: + if (allowunconfigd) { + if (!dpkg_version_is_informative(&pkg_pos->configversion)) { + sprintf(linebuf, _(" %.250s is unpacked, but has never been " + "configured.\n"), + pkg_name(pkg_pos, pnaw_nonambig)); + break; + } else if (!versionsatisfied(&pkg_pos->installed, possi)) { + sprintf(linebuf, _(" %.250s is unpacked, but is version " + "%.250s.\n"), + pkg_name(pkg_pos, pnaw_nonambig), + versiondescribe(&pkg_pos->installed.version, + vdew_nonambig)); + break; + } else if (!dpkg_version_relate(&pkg_pos->configversion, + possi->verrel, + &possi->version)) { + sprintf(linebuf, _(" %.250s latest configured version is " + "%.250s.\n"), + pkg_name(pkg_pos, pnaw_nonambig), + versiondescribe(&pkg_pos->configversion, vdew_nonambig)); + break; + } else { + deppossi_pkg_iter_free(possi_iter); + return true; + } + } + /* Fall through. */ + default: + sprintf(linebuf, _(" %.250s is %s.\n"), + pkg_name(pkg_pos, pnaw_nonambig), + gettext(statusstrings[pkg_pos->status])); + break; + } + break; + default: + internerr("unknown istobe depended '%d'", pkg_pos->clientdata->istobe); + } + varbuf_add_str(whynot, linebuf); + } + deppossi_pkg_iter_free(possi_iter); + + /* See if the package we're about to install Provides it. */ + for (provider = possi->ed->depended.available; + provider; + provider = provider->rev_next) { + if (provider->up->type != dep_provides) continue; + if (!pkg_virtual_deppossi_satisfied(possi, provider)) + continue; + if (provider->up->up->clientdata->istobe == PKG_ISTOBE_INSTALLNEW) + return true; + } + + /* Now look at the packages already on the system. */ + for (provider = possi->ed->depended.installed; + provider; + provider = provider->rev_next) { + if (provider->up->type != dep_provides) continue; + if (!pkg_virtual_deppossi_satisfied(possi, provider)) + continue; + + switch (provider->up->up->clientdata->istobe) { + case PKG_ISTOBE_INSTALLNEW: + /* Don't pay any attention to the Provides field of the + * currently-installed version of the package we're trying + * to install. We dealt with that by using the available + * information above. */ + continue; + case PKG_ISTOBE_REMOVE: + sprintf(linebuf, _(" %.250s provides %.250s but is to be removed.\n"), + pkg_name(provider->up->up, pnaw_nonambig), + possi->ed->name); + break; + case PKG_ISTOBE_DECONFIGURE: + sprintf(linebuf, _(" %.250s provides %.250s but is to be deconfigured.\n"), + pkg_name(provider->up->up, pnaw_nonambig), + possi->ed->name); + break; + case PKG_ISTOBE_NORMAL: + case PKG_ISTOBE_PREINSTALL: + if (provider->up->up->status == PKG_STAT_INSTALLED || + provider->up->up->status == PKG_STAT_TRIGGERSPENDING) + return true; + if (provider->up->up->status == PKG_STAT_TRIGGERSAWAITED) + *canfixbytrigaw = provider->up->up; + sprintf(linebuf, _(" %.250s provides %.250s but is %s.\n"), + pkg_name(provider->up->up, pnaw_nonambig), + possi->ed->name, + gettext(statusstrings[provider->up->up->status])); + break; + default: + internerr("unknown istobe provider '%d'", + provider->up->up->clientdata->istobe); + } + varbuf_add_str(whynot, linebuf); + } + + if (!*linebuf) { + /* If the package wasn't installed at all, and we haven't said + * yet why this isn't satisfied, we should say so now. */ + sprintf(linebuf, _(" %.250s is not installed.\n"), possi->ed->name); + varbuf_add_str(whynot, linebuf); + } + } + + return false; + } else { + int nconflicts; + + /* It's conflicts or breaks. There's only one main alternative, + * but we also have to consider Providers. We return ‘false’ as soon + * as we find something that matches the conflict, and only describe + * it then. If we get to the end without finding anything we return + * ‘true’. */ + + possi= dep->list; + nconflicts= 0; + + if (possi->ed != possi->up->up->set) { + struct deppossi_pkg_iterator *possi_iter; + + /* If the package conflicts with or breaks itself it must mean + * other packages which provide the same virtual name. We + * therefore don't look at the real package and go on to the + * virtual ones. */ + + possi_iter = deppossi_pkg_iter_new(possi, wpb_by_istobe); + while ((pkg_pos = deppossi_pkg_iter_next(possi_iter))) { + switch (pkg_pos->clientdata->istobe) { + case PKG_ISTOBE_REMOVE: + break; + case PKG_ISTOBE_INSTALLNEW: + if (!versionsatisfied(&pkg_pos->available, possi)) + break; + sprintf(linebuf, _(" %.250s (version %.250s) is to be installed.\n"), + pkgbin_name(pkg_pos, &pkg_pos->available, pnaw_nonambig), + versiondescribe(&pkg_pos->available.version, vdew_nonambig)); + varbuf_add_str(whynot, linebuf); + if (!canfixbyremove) { + deppossi_pkg_iter_free(possi_iter); + return false; + } + nconflicts++; + *canfixbyremove = pkg_pos; + break; + case PKG_ISTOBE_DECONFIGURE: + if (dep->type == dep_breaks) + break; /* Already deconfiguring this. */ + /* Fall through. */ + case PKG_ISTOBE_NORMAL: + case PKG_ISTOBE_PREINSTALL: + switch (pkg_pos->status) { + case PKG_STAT_NOTINSTALLED: + case PKG_STAT_CONFIGFILES: + break; + case PKG_STAT_HALFINSTALLED: + case PKG_STAT_UNPACKED: + case PKG_STAT_HALFCONFIGURED: + if (dep->type == dep_breaks) + break; /* No problem. */ + /* Fall through. */ + case PKG_STAT_INSTALLED: + case PKG_STAT_TRIGGERSPENDING: + case PKG_STAT_TRIGGERSAWAITED: + if (!versionsatisfied(&pkg_pos->installed, possi)) + break; + sprintf(linebuf, _(" %.250s (version %.250s) is present and %s.\n"), + pkg_name(pkg_pos, pnaw_nonambig), + versiondescribe(&pkg_pos->installed.version, vdew_nonambig), + gettext(statusstrings[pkg_pos->status])); + varbuf_add_str(whynot, linebuf); + if (!canfixbyremove) { + deppossi_pkg_iter_free(possi_iter); + return false; + } + nconflicts++; + *canfixbyremove = pkg_pos; + } + break; + default: + internerr("unknown istobe conflict '%d'", pkg_pos->clientdata->istobe); + } + } + deppossi_pkg_iter_free(possi_iter); + } + + /* See if the package we're about to install Provides it. */ + for (provider = possi->ed->depended.available; + provider; + provider = provider->rev_next) { + if (provider->up->type != dep_provides) continue; + if (provider->up->up->clientdata->istobe != PKG_ISTOBE_INSTALLNEW) + continue; + if (provider->up->up->set == dep->up->set) + continue; /* Conflicts and provides the same. */ + if (!pkg_virtual_deppossi_satisfied(possi, provider)) + continue; + sprintf(linebuf, _(" %.250s provides %.250s and is to be installed.\n"), + pkgbin_name(provider->up->up, &provider->up->up->available, + pnaw_nonambig), possi->ed->name); + varbuf_add_str(whynot, linebuf); + /* We can't remove the one we're about to install: */ + if (canfixbyremove) + *canfixbyremove = NULL; + return false; + } + + /* Now look at the packages already on the system. */ + for (provider = possi->ed->depended.installed; + provider; + provider = provider->rev_next) { + if (provider->up->type != dep_provides) continue; + + if (provider->up->up->set == dep->up->set) + continue; /* Conflicts and provides the same. */ + + if (!pkg_virtual_deppossi_satisfied(possi, provider)) + continue; + + switch (provider->up->up->clientdata->istobe) { + case PKG_ISTOBE_INSTALLNEW: + /* Don't pay any attention to the Provides field of the + * currently-installed version of the package we're trying + * to install. We dealt with that package by using the + * available information above. */ + continue; + case PKG_ISTOBE_REMOVE: + continue; + case PKG_ISTOBE_DECONFIGURE: + if (dep->type == dep_breaks) + continue; /* Already deconfiguring. */ + /* Fall through. */ + case PKG_ISTOBE_NORMAL: + case PKG_ISTOBE_PREINSTALL: + switch (provider->up->up->status) { + case PKG_STAT_NOTINSTALLED: + case PKG_STAT_CONFIGFILES: + continue; + case PKG_STAT_HALFINSTALLED: + case PKG_STAT_UNPACKED: + case PKG_STAT_HALFCONFIGURED: + if (dep->type == dep_breaks) + break; /* No problem. */ + /* Fall through. */ + case PKG_STAT_INSTALLED: + case PKG_STAT_TRIGGERSPENDING: + case PKG_STAT_TRIGGERSAWAITED: + sprintf(linebuf, + _(" %.250s provides %.250s and is present and %s.\n"), + pkg_name(provider->up->up, pnaw_nonambig), possi->ed->name, + gettext(statusstrings[provider->up->up->status])); + varbuf_add_str(whynot, linebuf); + if (!canfixbyremove) + return false; + nconflicts++; + *canfixbyremove= provider->up->up; + break; + } + break; + default: + internerr("unknown istobe conflict provider '%d'", + provider->up->up->clientdata->istobe); + } + } + + if (!nconflicts) + return true; + if (nconflicts > 1) + *canfixbyremove = NULL; + return false; + + } /* if (dependency) {...} else {...} */ +} diff --git a/src/main/enquiry.c b/src/main/enquiry.c new file mode 100644 index 0000000..cc605c2 --- /dev/null +++ b/src/main/enquiry.c @@ -0,0 +1,906 @@ +/* + * dpkg - main program for package management + * enquiry.c - status enquiry and listing options + * + * Copyright © 1995,1996 Ian Jackson + * Copyright © 2006, 2008-2016 Guillem Jover + * Copyright © 2011 Linaro Limited + * Copyright © 2011 Raphaël Hertzog + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" + +struct audit_problem { + bool (*check)(struct pkginfo *, const struct audit_problem *problem); + union { + int number; + const char *string; + } value; + const char *explanation; +}; + +static bool +audit_reinstreq(struct pkginfo *pkg, const struct audit_problem *problem) +{ + return pkg->eflag & PKG_EFLAG_REINSTREQ; +} + +static bool +audit_status(struct pkginfo *pkg, const struct audit_problem *problem) +{ + if (pkg->eflag & PKG_EFLAG_REINSTREQ) + return false; + return (int)pkg->status == problem->value.number; +} + +static bool +audit_infofile(struct pkginfo *pkg, const struct audit_problem *problem) +{ + if (pkg->status < PKG_STAT_HALFINSTALLED) + return false; + return !pkg_infodb_has_file(pkg, &pkg->installed, problem->value.string); +} + +static bool +audit_arch(struct pkginfo *pkg, const struct audit_problem *problem) +{ + if (pkg->status < PKG_STAT_HALFINSTALLED) + return false; + return pkg->installed.arch->type == (enum dpkg_arch_type)problem->value.number; +} + +static const struct audit_problem audit_problems[] = { + { + .check = audit_reinstreq, + .value.number = 0, + .explanation = N_( + "The following packages are in a mess due to serious problems during\n" + "installation. They must be reinstalled for them (and any packages\n" + "that depend on them) to function properly:\n") + }, { + .check = audit_status, + .value.number = PKG_STAT_UNPACKED, + .explanation = N_( + "The following packages have been unpacked but not yet configured.\n" + "They must be configured using dpkg --configure or the configure\n" + "menu option in dselect for them to work:\n") + }, { + .check = audit_status, + .value.number = PKG_STAT_HALFCONFIGURED, + .explanation = N_( + "The following packages are only half configured, probably due to problems\n" + "configuring them the first time. The configuration should be retried using\n" + "dpkg --configure or the configure menu option in dselect:\n") + }, { + .check = audit_status, + .value.number = PKG_STAT_HALFINSTALLED, + .explanation = N_( + "The following packages are only half installed, due to problems during\n" + "installation. The installation can probably be completed by retrying it;\n" + "the packages can be removed using dselect or dpkg --remove:\n") + }, { + .check = audit_status, + .value.number = PKG_STAT_TRIGGERSAWAITED, + .explanation = N_( + "The following packages are awaiting processing of triggers that they\n" + "have activated in other packages. This processing can be requested using\n" + "dselect or dpkg --configure --pending (or dpkg --triggers-only):\n") + }, { + .check = audit_status, + .value.number = PKG_STAT_TRIGGERSPENDING, + .explanation = N_( + "The following packages have been triggered, but the trigger processing\n" + "has not yet been done. Trigger processing can be requested using\n" + "dselect or dpkg --configure --pending (or dpkg --triggers-only):\n") + }, { + .check = audit_infofile, + .value.string = LISTFILE, + .explanation = N_( + "The following packages are missing the list control file in the\n" + "database, they need to be reinstalled:\n") + }, { + .check = audit_infofile, + .value.string = HASHFILE, + .explanation = N_( + "The following packages are missing the md5sums control file in the\n" + "database, they need to be reinstalled:\n") + }, { + .check = audit_arch, + .value.number = DPKG_ARCH_EMPTY, + .explanation = N_("The following packages do not have an architecture:\n") + }, { + .check = audit_arch, + .value.number = DPKG_ARCH_ILLEGAL, + .explanation = N_("The following packages have an illegal architecture:\n") + }, { + .check = audit_arch, + .value.number = DPKG_ARCH_UNKNOWN, + .explanation = N_( + "The following packages have an unknown foreign architecture, which will\n" + "cause dependency issues on front-ends. This can be fixed by registering\n" + "the foreign architecture with dpkg --add-architecture:\n") + }, { + .check = NULL + } +}; + +static void describebriefly(struct pkginfo *pkg) { + int maxl, l; + const char *pdesc; + + maxl= 57; + l= strlen(pkg->set->name); + if (l>20) maxl -= (l-20); + + pdesc = pkgbin_synopsis(pkg, &pkg->installed, &l); + l = min(l, maxl); + + printf(" %-20s %.*s\n", pkg_name(pkg, pnaw_nonambig), l, pdesc); +} + +static struct pkginfo * +pkg_array_mapper(const char *name) +{ + struct pkginfo *pkg; + + pkg = dpkg_options_parse_pkgname(cipaction, name); + if (pkg->status == PKG_STAT_NOTINSTALLED) + notice(_("package '%s' is not installed"), pkg_name(pkg, pnaw_nonambig)); + + return pkg; +} + +int +audit(const char *const *argv) +{ + const struct audit_problem *problem; + struct pkg_array array; + bool head_running = false; + int i; + + modstatdb_open(msdbrw_readonly); + + if (!*argv) + pkg_array_init_from_hash(&array); + else + pkg_array_init_from_names(&array, pkg_array_mapper, (const char **)argv); + + pkg_array_sort(&array, pkg_sorter_by_nonambig_name_arch); + + for (problem = audit_problems; problem->check; problem++) { + bool head = false; + + for (i = 0; i < array.n_pkgs; i++) { + struct pkginfo *pkg = array.pkgs[i]; + + if (!problem->check(pkg, problem)) + continue; + if (!head_running) { + if (modstatdb_is_locked()) + puts(_( +"Another process has locked the database for writing, and might currently be\n" +"modifying it, some of the following problems might just be due to that.\n")); + head_running = true; + } + if (!head) { + fputs(gettext(problem->explanation), stdout); + head = true; + } + describebriefly(pkg); + } + + if (head) putchar('\n'); + } + + pkg_array_destroy(&array); + + m_output(stdout, _("")); + + return 0; +} + +struct sectionentry { + struct sectionentry *next; + const char *name; + int count; +}; + +static bool +yettobeunpacked(struct pkginfo *pkg, const char **thissect) +{ + if (pkg->want != PKG_WANT_INSTALL) + return false; + + switch (pkg->status) { + case PKG_STAT_UNPACKED: + case PKG_STAT_INSTALLED: + case PKG_STAT_HALFCONFIGURED: + case PKG_STAT_TRIGGERSPENDING: + case PKG_STAT_TRIGGERSAWAITED: + return false; + case PKG_STAT_NOTINSTALLED: + case PKG_STAT_HALFINSTALLED: + case PKG_STAT_CONFIGFILES: + if (thissect) + *thissect = str_is_set(pkg->section) ? pkg->section : + C_("section", ""); + return true; + default: + internerr("unknown package status '%d'", pkg->status); + } + return false; +} + +int +unpackchk(const char *const *argv) +{ + int totalcount, sects; + struct sectionentry *sectionentries, *se, **sep; + struct pkg_hash_iter *iter; + struct pkginfo *pkg; + const char *thissect; + char buf[20]; + int width; + + if (*argv) + badusage(_("--%s takes no arguments"), cipaction->olong); + + modstatdb_open(msdbrw_readonly); + + totalcount= 0; + sectionentries = NULL; + sects= 0; + iter = pkg_hash_iter_new(); + while ((pkg = pkg_hash_iter_next_pkg(iter))) { + if (!yettobeunpacked(pkg, &thissect)) continue; + for (se= sectionentries; se && strcasecmp(thissect,se->name); se= se->next); + if (!se) { + se = nfmalloc(sizeof(*se)); + for (sep= §ionentries; + *sep && strcasecmp(thissect,(*sep)->name) > 0; + sep= &(*sep)->next); + se->name= thissect; + se->count= 0; + se->next= *sep; + *sep= se; + sects++; + } + se->count++; totalcount++; + } + pkg_hash_iter_free(iter); + + if (totalcount == 0) + return 0; + + if (totalcount <= 12) { + iter = pkg_hash_iter_new(); + while ((pkg = pkg_hash_iter_next_pkg(iter))) { + if (!yettobeunpacked(pkg, NULL)) + continue; + describebriefly(pkg); + } + pkg_hash_iter_free(iter); + } else if (sects <= 12) { + for (se= sectionentries; se; se= se->next) { + sprintf(buf,"%d",se->count); + printf(_(" %d in %s: "),se->count,se->name); + width= 70-strlen(se->name)-strlen(buf); + while (width > 59) { putchar(' '); width--; } + iter = pkg_hash_iter_new(); + while ((pkg = pkg_hash_iter_next_pkg(iter))) { + const char *pkgname; + + if (!yettobeunpacked(pkg,&thissect)) continue; + if (strcasecmp(thissect,se->name)) continue; + pkgname = pkg_name(pkg, pnaw_nonambig); + width -= strlen(pkgname); + width--; + if (width < 4) { printf(" ..."); break; } + printf(" %s", pkgname); + } + pkg_hash_iter_free(iter); + putchar('\n'); + } + } else { + printf(P_(" %d package, from the following section:", + " %d packages, from the following sections:", totalcount), + totalcount); + width= 0; + for (se= sectionentries; se; se= se->next) { + sprintf(buf,"%d",se->count); + width -= (6 + strlen(se->name) + strlen(buf)); + if (width < 0) { putchar('\n'); width= 73 - strlen(se->name) - strlen(buf); } + printf(" %s (%d)",se->name,se->count); + } + putchar('\n'); + } + + m_output(stdout, _("")); + + return 0; +} + +static const struct assert_feature { + const char *name; + const char *desc; + const char *version; +} assert_features[] = { + { + .name = "support-predepends", + .desc = N_("the Pre-Depends field"), + .version = "1.1.0", + }, { + .name = "working-epoch", + .desc = N_("epochs in versions"), + .version = "1.4.0.7", + }, { + .name = "long-filenames", + .desc = N_("long filenames in .deb archives"), + .version = "1.4.1.17", + }, { + .name = "multi-conrep", + .desc = N_("multiple Conflicts and Replaces"), + .version = "1.4.1.19", + }, { + .name = "multi-arch", + .desc = N_("multi-arch fields and semantics"), + .version = "1.16.2", + }, { + .name = "versioned-provides", + .desc = N_("versioned relationships in the Provides field"), + .version = "1.17.11", + }, { + .name = "protected-field", + .desc = N_("the Protected field"), + .version = "1.20.1", + }, { + .name = NULL, + } +}; + +static int +assert_version_support(const char *const *argv, + const struct assert_feature *feature) +{ + const char *running_version_str; + struct dpkg_version running_version = DPKG_VERSION_INIT; + struct dpkg_version version = { 0, feature->version, NULL }; + struct dpkg_error err = DPKG_ERROR_INIT; + + if (*argv) + badusage(_("--%s takes no arguments"), cipaction->olong); + + /* + * When using the feature asserts, we want to know whether the currently + * running dpkg, which we might be running under (say from within a + * maintainer script) and which might have a different version, supports + * the requested feature. As dpkg is an Essential package, it is expected + * to work even when just unpacked, and so its own version is enough. + */ + running_version_str = getenv("DPKG_RUNNING_VERSION"); + + /* + * If we are not running from within a maintainer script, then that means + * once we do, the executed dpkg will support the requested feature, if + * we know about it. Always return success in that case. + */ + if (str_is_unset(running_version_str)) + return 0; + + if (parseversion(&running_version, running_version_str, &err) < 0) + ohshit(_("cannot parse dpkg running version '%s': %s"), + running_version_str, err.str); + + if (dpkg_version_relate(&running_version, DPKG_RELATION_GE, &version)) + return 0; + + printf(_("Running version of dpkg does not support %s.\n" + " Please upgrade to at least dpkg %s, and then try again.\n"), + feature->desc, versiondescribe(&version, vdew_nonambig)); + return 1; +} + +const char *assert_feature_name; + +int +assert_feature(const char *const *argv) +{ + const struct assert_feature *feature; + + if (strcmp(assert_feature_name, "help") == 0) { + printf(_("%s assert options - assert whether features are supported:\n"), + dpkg_get_progname()); + + for (feature = assert_features; feature->name; feature++) { + printf(" %-19s %-9s %s\n", feature->name, feature->version, + gettext(feature->desc)); + } + + exit(0); + } + + for (feature = assert_features; feature->name; feature++) { + if (strcmp(feature->name, assert_feature_name) != 0) + continue; + + return assert_version_support(argv, feature); + } + + badusage(_("unknown --%s-"), cipaction->olong); +} + +/** + * Print a single package which: + * (a) is the target of one or more relevant predependencies. + * (b) has itself no unsatisfied pre-dependencies. + * + * If such a package is present output is the Packages file entry, + * which can be massaged as appropriate. + * + * Exit status: + * 0 = a package printed, OK + * 1 = no suitable package available + * 2 = error + */ +int +predeppackage(const char *const *argv) +{ + static struct varbuf vb; + + struct pkg_hash_iter *iter; + struct pkginfo *pkg = NULL, *startpkg, *trypkg; + struct dependency *dep; + struct deppossi *possi, *provider; + + if (*argv) + badusage(_("--%s takes no arguments"), cipaction->olong); + + modstatdb_open(msdbrw_readonly | msdbrw_available_readonly); + /* We use clientdata->istobe to detect loops. */ + clear_istobes(); + + dep = NULL; + iter = pkg_hash_iter_new(); + while (!dep && (pkg = pkg_hash_iter_next_pkg(iter))) { + /* Ignore packages user doesn't want. */ + if (pkg->want != PKG_WANT_INSTALL) + continue; + /* Ignore packages not available. */ + if (!pkg->archives) + continue; + pkg->clientdata->istobe = PKG_ISTOBE_PREINSTALL; + for (dep= pkg->available.depends; dep; dep= dep->next) { + if (dep->type != dep_predepends) continue; + if (depisok(dep, &vb, NULL, NULL, true)) + continue; + /* This will leave dep non-NULL, and so exit the loop. */ + break; + } + pkg->clientdata->istobe = PKG_ISTOBE_NORMAL; + /* If dep is NULL we go and get the next package. */ + } + pkg_hash_iter_free(iter); + + if (!dep) + return 1; /* Not found. */ + if (pkg == NULL) + internerr("unexpected unfound package"); + + startpkg= pkg; + pkg->clientdata->istobe = PKG_ISTOBE_PREINSTALL; + + /* OK, we have found an unsatisfied predependency. + * Now go and find the first thing we need to install, as a first step + * towards satisfying it. */ + do { + /* We search for a package which would satisfy dep, and put it in pkg. */ + for (possi = dep->list, pkg = NULL; + !pkg && possi; + possi=possi->next) { + struct deppossi_pkg_iterator *possi_iter; + + possi_iter = deppossi_pkg_iter_new(possi, wpb_available); + while (!pkg && (trypkg = deppossi_pkg_iter_next(possi_iter))) { + if (trypkg->archives && + trypkg->clientdata->istobe == PKG_ISTOBE_NORMAL && + versionsatisfied(&trypkg->available, possi)) { + pkg = trypkg; + break; + } + for (provider = possi->ed->depended.available; + !pkg && provider; + provider = provider->next) { + if (provider->up->type != dep_provides) + continue; + if (!pkg_virtual_deppossi_satisfied(possi, provider)) + continue; + trypkg = provider->up->up; + if (!trypkg->archives) + continue; + if (trypkg->clientdata->istobe == PKG_ISTOBE_NORMAL) { + pkg = trypkg; + break; + } + } + } + deppossi_pkg_iter_free(possi_iter); + } + if (!pkg) { + varbuf_reset(&vb); + describedepcon(&vb,dep); + varbuf_end_str(&vb); + notice(_("cannot see how to satisfy pre-dependency:\n %s"), vb.buf); + ohshit(_("cannot satisfy pre-dependencies for %.250s (wanted due to %.250s)"), + pkgbin_name(dep->up, &dep->up->available, pnaw_nonambig), + pkgbin_name(startpkg, &startpkg->available, pnaw_nonambig)); + } + pkg->clientdata->istobe = PKG_ISTOBE_PREINSTALL; + for (dep= pkg->available.depends; dep; dep= dep->next) { + if (dep->type != dep_predepends) continue; + if (depisok(dep, &vb, NULL, NULL, true)) + continue; + /* This will leave dep non-NULL, and so exit the loop. */ + break; + } + } while (dep); + + /* OK, we've found it - pkg has no unsatisfied pre-dependencies! */ + write_stanza(stdout, _(""), pkg, &pkg->available); + + m_output(stdout, _("")); + + return 0; +} + +int +printarch(const char *const *argv) +{ + if (*argv) + badusage(_("--%s takes no arguments"), cipaction->olong); + + printf("%s\n", dpkg_arch_get(DPKG_ARCH_NATIVE)->name); + + m_output(stdout, _("")); + + return 0; +} + +int +print_foreign_arches(const char *const *argv) +{ + struct dpkg_arch *arch; + + if (*argv) + badusage(_("--%s takes no arguments"), cipaction->olong); + + dpkg_arch_load_list(); + + for (arch = dpkg_arch_get_list(); arch; arch = arch->next) { + if (arch->type != DPKG_ARCH_FOREIGN) + continue; + + printf("%s\n", arch->name); + } + + m_output(stdout, _("")); + + return 0; +} + +int +validate_pkgname(const char *const *argv) +{ + const char *emsg; + + if (!argv[0] || argv[1]) + badusage(_("--%s takes one argument"), cipaction->olong); + + emsg = pkg_name_is_illegal(argv[0]); + if (emsg) + ohshit(_("package name '%s' is invalid: %s"), argv[0], emsg); + + return 0; +} + +int +validate_trigname(const char *const *argv) +{ + const char *emsg; + + if (!argv[0] || argv[1]) + badusage(_("--%s takes one argument"), cipaction->olong); + + emsg = trig_name_is_illegal(argv[0]); + if (emsg) + ohshit(_("trigger name '%s' is invalid: %s"), argv[0], emsg); + + return 0; +} + +int +validate_archname(const char *const *argv) +{ + const char *emsg; + + if (!argv[0] || argv[1]) + badusage(_("--%s takes one argument"), cipaction->olong); + + emsg = dpkg_arch_name_is_illegal(argv[0]); + if (emsg) + ohshit(_("architecture name '%s' is invalid: %s"), argv[0], emsg); + + return 0; +} + +int +validate_version(const char *const *argv) +{ + struct dpkg_version version; + struct dpkg_error err; + + if (!argv[0] || argv[1]) + badusage(_("--%s takes one argument"), cipaction->olong); + + if (parseversion(&version, argv[0], &err) < 0) { + dpkg_error_print(&err, _("version '%s' has bad syntax"), argv[0]); + dpkg_error_destroy(&err); + + return 1; + } + + return 0; +} + +int +cmpversions(const char *const *argv) +{ + struct relationinfo { + const char *op; + /* These values are exit status codes. */ + int if_lesser, if_equal, if_greater; + int if_none_a, if_none_both, if_none_b; + bool obsolete; + }; + + static const struct relationinfo relationinfos[]= { + { + .op = "le", + .if_lesser = EXIT_SUCCESS, + .if_equal = EXIT_SUCCESS, + .if_greater = EXIT_FAILURE, + .if_none_a = EXIT_SUCCESS, + .if_none_both = EXIT_SUCCESS, + .if_none_b = EXIT_FAILURE, + }, { + .op = "lt", + .if_lesser = EXIT_SUCCESS, + .if_equal = EXIT_FAILURE, + .if_greater = EXIT_FAILURE, + .if_none_a = EXIT_SUCCESS, + .if_none_both = EXIT_FAILURE, + .if_none_b = EXIT_FAILURE, + }, { + .op = "eq", + .if_lesser = EXIT_FAILURE, + .if_equal = EXIT_SUCCESS, + .if_greater = EXIT_FAILURE, + .if_none_a = EXIT_FAILURE, + .if_none_both = EXIT_SUCCESS, + .if_none_b = EXIT_FAILURE, + }, { + .op = "ne", + .if_lesser = EXIT_SUCCESS, + .if_equal = EXIT_FAILURE, + .if_greater = EXIT_SUCCESS, + .if_none_a = EXIT_SUCCESS, + .if_none_both = EXIT_FAILURE, + .if_none_b = EXIT_SUCCESS, + }, { + .op = "ge", + .if_lesser = EXIT_FAILURE, + .if_equal = EXIT_SUCCESS, + .if_greater = EXIT_SUCCESS, + .if_none_a = EXIT_FAILURE, + .if_none_both = EXIT_SUCCESS, + .if_none_b = EXIT_SUCCESS, + }, { + .op = "gt", + .if_lesser = EXIT_FAILURE, + .if_equal = EXIT_FAILURE, + .if_greater = EXIT_SUCCESS, + .if_none_a = EXIT_FAILURE, + .if_none_both = EXIT_FAILURE, + .if_none_b = EXIT_SUCCESS, + }, + + /* These treat an empty version as later than any version. */ + { + .op = "le-nl", + .if_lesser = EXIT_SUCCESS, + .if_equal = EXIT_SUCCESS, + .if_greater = EXIT_FAILURE, + .if_none_a = EXIT_FAILURE, + .if_none_both = EXIT_SUCCESS, + .if_none_b = EXIT_SUCCESS, + }, { + .op = "lt-nl", + .if_lesser = EXIT_SUCCESS, + .if_equal = EXIT_FAILURE, + .if_greater = EXIT_FAILURE, + .if_none_a = EXIT_FAILURE, + .if_none_both = EXIT_FAILURE, + .if_none_b = EXIT_SUCCESS, + }, { + .op = "ge-nl", + .if_lesser = EXIT_FAILURE, + .if_equal = EXIT_SUCCESS, + .if_greater = EXIT_SUCCESS, + .if_none_a = EXIT_SUCCESS, + .if_none_both = EXIT_SUCCESS, + .if_none_b = EXIT_FAILURE, + }, { + .op = "gt-nl", + .if_lesser = EXIT_FAILURE, + .if_equal = EXIT_FAILURE, + .if_greater = EXIT_SUCCESS, + .if_none_a = EXIT_SUCCESS, + .if_none_both = EXIT_FAILURE, + .if_none_b = EXIT_FAILURE, + }, + + /* For compatibility with dpkg control file syntax. */ + { + .op = "<", + .if_lesser = EXIT_SUCCESS, + .if_equal = EXIT_SUCCESS, + .if_greater = EXIT_FAILURE, + .if_none_a = EXIT_SUCCESS, + .if_none_both = EXIT_SUCCESS, + .if_none_b = EXIT_FAILURE, + .obsolete = true, + }, { + .op = "<=", + .if_lesser = EXIT_SUCCESS, + .if_equal = EXIT_SUCCESS, + .if_greater = EXIT_FAILURE, + .if_none_a = EXIT_SUCCESS, + .if_none_both = EXIT_SUCCESS, + .if_none_b = EXIT_FAILURE, + }, { + .op = "<<", + .if_lesser = EXIT_SUCCESS, + .if_equal = EXIT_FAILURE, + .if_greater = EXIT_FAILURE, + .if_none_a = EXIT_SUCCESS, + .if_none_both = EXIT_FAILURE, + .if_none_b = EXIT_FAILURE, + }, { + .op = "=", + .if_lesser = EXIT_FAILURE, + .if_equal = EXIT_SUCCESS, + .if_greater = EXIT_FAILURE, + .if_none_a = EXIT_FAILURE, + .if_none_both = EXIT_SUCCESS, + .if_none_b = EXIT_FAILURE, + }, { + .op = ">", + .if_lesser = EXIT_FAILURE, + .if_equal = EXIT_SUCCESS, + .if_greater = EXIT_SUCCESS, + .if_none_a = EXIT_FAILURE, + .if_none_both = EXIT_SUCCESS, + .if_none_b = EXIT_SUCCESS, + .obsolete = true, + }, { + .op = ">=", + .if_lesser = EXIT_FAILURE, + .if_equal = EXIT_SUCCESS, + .if_greater = EXIT_SUCCESS, + .if_none_a = EXIT_FAILURE, + .if_none_both = EXIT_SUCCESS, + .if_none_b = EXIT_SUCCESS, + }, { + .op = ">>", + .if_lesser = EXIT_FAILURE, + .if_equal = EXIT_FAILURE, + .if_greater = EXIT_SUCCESS, + .if_none_a = EXIT_FAILURE, + .if_none_both = EXIT_FAILURE, + .if_none_b = EXIT_SUCCESS, + }, { + .op = NULL, + } + }; + + const struct relationinfo *rip; + struct dpkg_version a, b; + struct dpkg_error err; + int rc; + + if (!argv[0] || !argv[1] || !argv[2] || argv[3]) + badusage(_("--compare-versions takes three arguments:" + " ")); + + for (rip = relationinfos; rip->op && strcmp(rip->op, argv[1]); rip++) + ; + + if (!rip->op) + badusage(_("--compare-versions bad relation")); + + if (rip->obsolete) + warning(_("--%s used with obsolete relation operator '%s'"), + cipaction->olong, rip->op); + + if (*argv[0] && strcmp(argv[0],"")) { + if (parseversion(&a, argv[0], &err) < 0) { + dpkg_error_print(&err, _("version '%s' has bad syntax"), argv[0]); + dpkg_error_destroy(&err); + } + } else { + dpkg_version_blank(&a); + } + if (*argv[2] && strcmp(argv[2],"")) { + if (parseversion(&b, argv[2], &err) < 0) { + dpkg_error_print(&err, _("version '%s' has bad syntax"), argv[2]); + dpkg_error_destroy(&err); + } + } else { + dpkg_version_blank(&b); + } + if (!dpkg_version_is_informative(&a)) { + if (dpkg_version_is_informative(&b)) + return rip->if_none_a; + else + return rip->if_none_both; + } else if (!dpkg_version_is_informative(&b)) { + return rip->if_none_b; + } + rc = dpkg_version_compare(&a, &b); + debug(dbg_general, "cmpversions a='%s' b='%s' r=%d", + versiondescribe_c(&a,vdew_always), + versiondescribe_c(&b,vdew_always), + rc); + if (rc > 0) + return rip->if_greater; + else if (rc < 0) + return rip->if_lesser; + else + return rip->if_equal; +} diff --git a/src/main/errors.c b/src/main/errors.c new file mode 100644 index 0000000..50d4155 --- /dev/null +++ b/src/main/errors.c @@ -0,0 +1,137 @@ +/* + * dpkg - main program for package management + * errors.c - per package error handling + * + * Copyright © 1994,1995 Ian Jackson + * Copyright © 2007-2014 Guillem Jover + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "main.h" + +bool abort_processing = false; + +static int nerrs = 0; + +struct error_report { + struct error_report *next; + char *what; +}; + +static struct error_report *reports = NULL; +static struct error_report **lastreport= &reports; +static struct error_report emergency; + +static void +enqueue_error_report(const char *arg) +{ + struct error_report *nr; + + nr = malloc(sizeof(*nr)); + if (!nr) { + notice(_("failed to allocate memory for new entry in list of failed packages: %s"), + strerror(errno)); + abort_processing = true; + nr= &emergency; + } + nr->what = m_strdup(arg); + nr->next = NULL; + *lastreport= nr; + lastreport= &nr->next; + + if (++nerrs < errabort) + return; + notice(_("too many errors, stopping")); + abort_processing = true; +} + +void +print_error_perpackage(const char *emsg, const void *data) +{ + const char *pkgname = data; + + notice(_("error processing package %s (--%s):\n %s"), + pkgname, cipaction->olong, emsg); + + statusfd_send("status: %s : %s : %s", pkgname, "error", emsg); + + enqueue_error_report(pkgname); +} + +void +print_error_perarchive(const char *emsg, const void *data) +{ + const char *filename = data; + + notice(_("error processing archive %s (--%s):\n %s"), + filename, cipaction->olong, emsg); + + statusfd_send("status: %s : %s : %s", filename, "error", emsg); + + enqueue_error_report(filename); +} + +int +reportbroken_retexitstatus(int ret) +{ + if (reports) { + fputs(_("Errors were encountered while processing:\n"),stderr); + while (reports) { + fprintf(stderr," %s\n",reports->what); + free(reports->what); + reports= reports->next; + } + } + if (abort_processing) { + fputs(_("Processing was halted because there were too many errors.\n"),stderr); + } + return nerrs ? 1 : ret; +} + +bool +skip_due_to_hold(struct pkginfo *pkg) +{ + if (pkg->want != PKG_WANT_HOLD) + return false; + if (in_force(FORCE_HOLD)) { + notice(_("package %s was on hold, processing it anyway as you requested"), + pkg_name(pkg, pnaw_nonambig)); + return false; + } + printf(_("Package %s is on hold, not touching it. Use --force-hold to override.\n"), + pkg_name(pkg, pnaw_nonambig)); + return true; +} + diff --git a/src/main/file-match.c b/src/main/file-match.c new file mode 100644 index 0000000..51bdb0d --- /dev/null +++ b/src/main/file-match.c @@ -0,0 +1,49 @@ +/* + * dpkg - main program for package management + * file-match.c - file name/type match tracking functions + * + * Copyright © 2011 Guillem Jover + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include + +#include + +#include "file-match.h" + +struct match_node * +match_node_new(const char *name, const char *type, struct match_node *next) +{ + struct match_node *node; + + node = m_malloc(sizeof(*node)); + node->next = next; + node->filename = m_strdup(name); + node->filetype = m_strdup(type); + + return node; +} + +void +match_node_free(struct match_node *node) +{ + free(node->filetype); + free(node->filename); + free(node); +} diff --git a/src/main/file-match.h b/src/main/file-match.h new file mode 100644 index 0000000..db7a40b --- /dev/null +++ b/src/main/file-match.h @@ -0,0 +1,35 @@ +/* + * dpkg - main program for package management + * file-match.h - file name/type match tracking functions + * + * Copyright © 2011 Guillem Jover + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DPKG_FILE_MATCH_H +#define DPKG_FILE_MATCH_H + +struct match_node { + struct match_node *next; + char *filetype; + char *filename; +}; + +struct match_node * +match_node_new(const char *name, const char *type, struct match_node *next); +void +match_node_free(struct match_node *node); + +#endif /* DPKG_FILE_MATCH_H */ diff --git a/src/main/filters.c b/src/main/filters.c new file mode 100644 index 0000000..f770fb7 --- /dev/null +++ b/src/main/filters.c @@ -0,0 +1,133 @@ +/* + * dpkg - main program for package management + * filters.c - filtering routines for excluding bits of packages + * + * Copyright © 2007, 2008 Tollef Fog Heen + * Copyright © 2008, 2010, 2012-2014 Guillem Jover + * Copyright © 2010 Canonical Ltd. + * written by Martin Pitt + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include + +#include +#include +#include +#include + +#include "main.h" +#include "filters.h" + +struct filter_node { + struct filter_node *next; + char *pattern; + bool include; +}; + +static struct filter_node *filter_head = NULL; +static struct filter_node **filter_tail = &filter_head; + +void +filter_add(const char *pattern, bool include) +{ + struct filter_node *filter; + + debug(dbg_general, "adding %s filter for '%s'", + include ? "include" : "exclude", pattern); + + filter = m_malloc(sizeof(*filter)); + filter->pattern = m_strdup(pattern); + filter->include = include; + filter->next = NULL; + + *filter_tail = filter; + filter_tail = &filter->next; +} + +bool +filter_should_skip(struct tar_entry *ti) +{ + struct filter_node *f; + bool skip = false; + + if (!filter_head) + return false; + + /* Last match wins. */ + for (f = filter_head; f != NULL; f = f->next) { + debug(dbg_eachfile, "filter comparing '%s' and '%s'", + &ti->name[1], f->pattern); + + if (fnmatch(f->pattern, &ti->name[1], 0) == 0) { + if (f->include) { + skip = false; + debug(dbg_eachfile, "filter including %s", + ti->name); + } else { + skip = true; + debug(dbg_eachfile, "filter removing %s", + ti->name); + } + } + } + + /* We need to keep directories (or symlinks to directories) if a + * glob excludes them, but a more specific include glob brings back + * files; XXX the current implementation will probably include more + * directories than necessary, but better err on the side of caution + * than failing with “no such file or directory” (which would leave + * the package in a very bad state). */ + if (skip && (ti->type == TAR_FILETYPE_DIR || + ti->type == TAR_FILETYPE_SYMLINK)) { + debug(dbg_eachfile, + "filter seeing if '%s' needs to be reincluded", + &ti->name[1]); + + for (f = filter_head; f != NULL; f = f->next) { + const char *wildcard; + int path_len; + + if (!f->include) + continue; + + /* Calculate the offset of the first wildcard + * character in the pattern. */ + wildcard = strpbrk(f->pattern, "*?[\\"); + if (wildcard) + path_len = wildcard - f->pattern; + else + path_len = strlen(f->pattern); + + /* Ignore any trailing slash for the comparison. */ + while (path_len && f->pattern[path_len - 1] == '/') + path_len--; + + debug(dbg_eachfiledetail, + "filter subpattern '%.*s'", path_len, f->pattern); + + if (strncmp(&ti->name[1], f->pattern, path_len) == 0) { + debug(dbg_eachfile, "filter reincluding %s", + ti->name); + return false; + } + } + } + + return skip; +} diff --git a/src/main/filters.h b/src/main/filters.h new file mode 100644 index 0000000..b878e7e --- /dev/null +++ b/src/main/filters.h @@ -0,0 +1,37 @@ +/* + * dpkg - main program for package management + * filters.h - external definitions for filter handling + * + * Copyright © 2007, 2008 Tollef Fog Heen + * Copyright © 2008, 2010 Guillem Jover + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DPKG_FILTERS_H +#define DPKG_FILTERS_H + +#include + +#include +#include + +DPKG_BEGIN_DECLS + +void filter_add(const char *glob, bool include); +bool filter_should_skip(struct tar_entry *ti); + +DPKG_END_DECLS + +#endif diff --git a/src/main/help.c b/src/main/help.c new file mode 100644 index 0000000..59e730e --- /dev/null +++ b/src/main/help.c @@ -0,0 +1,325 @@ +/* + * dpkg - main program for package management + * help.c - various helper routines + * + * Copyright © 1995 Ian Jackson + * Copyright © 2007-2015 Guillem Jover + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" + +const char *const statusstrings[]= { + [PKG_STAT_NOTINSTALLED] = N_("not installed"), + [PKG_STAT_CONFIGFILES] = N_("not installed but configs remain"), + [PKG_STAT_HALFINSTALLED] = N_("broken due to failed removal or installation"), + [PKG_STAT_UNPACKED] = N_("unpacked but not configured"), + [PKG_STAT_HALFCONFIGURED] = N_("broken due to postinst failure"), + [PKG_STAT_TRIGGERSAWAITED] = N_("awaiting trigger processing by another package"), + [PKG_STAT_TRIGGERSPENDING] = N_("triggered"), + [PKG_STAT_INSTALLED] = N_("installed") +}; + +struct fsys_namenode * +namenodetouse(struct fsys_namenode *namenode, struct pkginfo *pkg, + struct pkgbin *pkgbin) +{ + struct fsys_namenode *fnn; + + if (!namenode->divert) + return namenode; + + debug(dbg_eachfile, "namenodetouse namenode='%s' pkg=%s", + namenode->name, pkgbin_name(pkg, pkgbin, pnaw_always)); + + fnn = (namenode->divert->useinstead && namenode->divert->pkgset != pkg->set) + ? namenode->divert->useinstead : namenode; + + debug(dbg_eachfile, + "namenodetouse ... useinstead=%s camefrom=%s pkg=%s return %s", + namenode->divert->useinstead ? namenode->divert->useinstead->name : "", + namenode->divert->camefrom ? namenode->divert->camefrom->name : "", + namenode->divert->pkgset ? namenode->divert->pkgset->name : "", + fnn->name); + + return fnn; +} + +/** + * Verify that some programs can be found in the PATH. + */ +void checkpath(void) { + static const char *const prog_list[] = { + DPKG_DEFAULT_SHELL, + RM, + TAR, + DIFF, + BACKEND, + /* macOS uses dyld (Mach-O) instead of ld.so (ELF), and does not have + * an ldconfig. */ +#if defined(__APPLE__) && defined(__MACH__) + "update_dyld_shared_cache", +#elif defined(__GLIBC__) || defined(__UCLIBC__) || \ + defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) + "ldconfig", +#endif +#if BUILD_START_STOP_DAEMON + "start-stop-daemon", +#endif + NULL + }; + + const char *const *prog; + int warned= 0; + + for (prog = prog_list; *prog; prog++) { + if (!command_in_path(*prog)) { + warning(_("'%s' not found in PATH or not executable"), *prog); + warned++; + } + } + + if (warned) + forcibleerr(FORCE_BAD_PATH, + P_("%d expected program not found in PATH or not executable\n%s", + "%d expected programs not found in PATH or not executable\n%s", + warned), + warned, _("Note: root's PATH should usually contain " + "/usr/local/sbin, /usr/sbin and /sbin")); +} + +bool +ignore_depends(const struct pkginfo *pkg) +{ + struct pkg_list *id; + for (id= ignoredependss; id; id= id->next) + if (id->pkg == pkg) + return true; + return false; +} + +static bool +ignore_depends_possi(struct deppossi *possi) +{ + struct deppossi_pkg_iterator *possi_iter; + struct pkginfo *pkg; + + possi_iter = deppossi_pkg_iter_new(possi, wpb_installed); + while ((pkg = deppossi_pkg_iter_next(possi_iter))) { + if (ignore_depends(pkg)) { + deppossi_pkg_iter_free(possi_iter); + return true; + } + } + deppossi_pkg_iter_free(possi_iter); + + return false; +} + +bool +force_depends(struct deppossi *possi) +{ + return in_force(FORCE_DEPENDS) || + ignore_depends_possi(possi) || + ignore_depends(possi->up->up); +} + +bool +force_breaks(struct deppossi *possi) +{ + return in_force(FORCE_BREAKS) || + ignore_depends_possi(possi) || + ignore_depends(possi->up->up); +} + +bool +force_conflicts(struct deppossi *possi) +{ + return in_force(FORCE_CONFLICTS); +} + +void clear_istobes(void) { + struct pkg_hash_iter *iter; + struct pkginfo *pkg; + + iter = pkg_hash_iter_new(); + while ((pkg = pkg_hash_iter_next_pkg(iter)) != NULL) { + ensure_package_clientdata(pkg); + pkg->clientdata->istobe = PKG_ISTOBE_NORMAL; + pkg->clientdata->replacingfilesandsaid= 0; + } + pkg_hash_iter_free(iter); +} + +/* + * Returns true if the directory contains conffiles belonging to pkg, + * false otherwise. + */ +bool +dir_has_conffiles(struct fsys_namenode *file, struct pkginfo *pkg) +{ + struct conffile *conff; + size_t namelen; + + debug(dbg_veryverbose, "dir_has_conffiles '%s' (from %s)", file->name, + pkg_name(pkg, pnaw_always)); + namelen = strlen(file->name); + for (conff= pkg->installed.conffiles; conff; conff= conff->next) { + if (conff->obsolete || conff->remove_on_upgrade) + continue; + if (strncmp(file->name, conff->name, namelen) == 0 && + strlen(conff->name) > namelen && conff->name[namelen] == '/') { + debug(dbg_veryverbose, "directory %s has conffile %s from %s", + file->name, conff->name, pkg_name(pkg, pnaw_always)); + return true; + } + } + debug(dbg_veryverbose, "dir_has_conffiles no"); + return false; +} + +/* + * Returns true if the file is used by packages other than pkg, + * false otherwise. + */ +bool +dir_is_used_by_others(struct fsys_namenode *file, struct pkginfo *pkg) +{ + struct fsys_node_pkgs_iter *iter; + struct pkginfo *other_pkg; + + debug(dbg_veryverbose, "dir_is_used_by_others '%s' (except %s)", file->name, + pkg ? pkg_name(pkg, pnaw_always) : ""); + + iter = fsys_node_pkgs_iter_new(file); + while ((other_pkg = fsys_node_pkgs_iter_next(iter))) { + debug(dbg_veryverbose, "dir_is_used_by_others considering %s ...", + pkg_name(other_pkg, pnaw_always)); + if (other_pkg == pkg) + continue; + + fsys_node_pkgs_iter_free(iter); + debug(dbg_veryverbose, "dir_is_used_by_others yes"); + return true; + } + fsys_node_pkgs_iter_free(iter); + + debug(dbg_veryverbose, "dir_is_used_by_others no"); + return false; +} + +/* + * Returns true if the file is used by pkg, false otherwise. + */ +bool +dir_is_used_by_pkg(struct fsys_namenode *file, struct pkginfo *pkg, + struct fsys_namenode_list *list) +{ + struct fsys_namenode_list *node; + size_t namelen; + + debug(dbg_veryverbose, "dir_is_used_by_pkg '%s' (by %s)", + file->name, pkg ? pkg_name(pkg, pnaw_always) : ""); + + namelen = strlen(file->name); + + for (node = list; node; node = node->next) { + debug(dbg_veryverbose, "dir_is_used_by_pkg considering %s ...", + node->namenode->name); + + if (strncmp(file->name, node->namenode->name, namelen) == 0 && + strlen(node->namenode->name) > namelen && + node->namenode->name[namelen] == '/') { + debug(dbg_veryverbose, "dir_is_used_by_pkg yes"); + return true; + } + } + + debug(dbg_veryverbose, "dir_is_used_by_pkg no"); + + return false; +} + +/** + * Mark a conffile as obsolete. + * + * @param pkg The package owning the conffile. + * @param namenode The namenode for the obsolete conffile. + */ +void +conffile_mark_obsolete(struct pkginfo *pkg, struct fsys_namenode *namenode) +{ + struct conffile *conff; + + for (conff = pkg->installed.conffiles; conff; conff = conff->next) { + if (strcmp(conff->name, namenode->name) == 0) { + debug(dbg_conff, "marking %s conffile %s as obsolete", + pkg_name(pkg, pnaw_always), conff->name); + conff->obsolete = true; + return; + } + } +} + +/** + * Mark all package conffiles as old. + * + * @param pkg The package owning the conffiles. + */ +void +pkg_conffiles_mark_old(struct pkginfo *pkg) +{ + const struct conffile *conff; + + for (conff = pkg->installed.conffiles; conff; conff = conff->next) { + struct fsys_namenode *namenode; + + namenode = fsys_hash_find_node(conff->name, FHFF_NONE); /* XXX */ + namenode->flags |= FNNF_OLD_CONFF; + if (!namenode->oldhash) + namenode->oldhash = conff->hash; + debug(dbg_conffdetail, "%s '%s' namenode '%s' flags %o", __func__, + conff->name, namenode->name, namenode->flags); + } +} + +void +log_action(const char *action, struct pkginfo *pkg, struct pkgbin *pkgbin) +{ + log_message("%s %s %s %s", action, pkgbin_name(pkg, pkgbin, pnaw_always), + versiondescribe_c(&pkg->installed.version, vdew_nonambig), + versiondescribe_c(&pkg->available.version, vdew_nonambig)); + statusfd_send("processing: %s: %s", action, + pkgbin_name(pkg, pkgbin, pnaw_nonambig)); +} diff --git a/src/main/main.c b/src/main/main.c new file mode 100644 index 0000000..02887c2 --- /dev/null +++ b/src/main/main.c @@ -0,0 +1,781 @@ +/* + * dpkg - main program for package management + * main.c - main program + * + * Copyright © 1994,1995 Ian Jackson + * Copyright © 2006-2016 Guillem Jover + * Copyright © 2010 Canonical Ltd. + * written by Martin Pitt + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include + +#include +#include +#if HAVE_LOCALE_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "filters.h" + +static int +printversion(const char *const *argv) +{ + if (f_robot) { + printf("%s", PACKAGE_VERSION); + } else { + printf(_("Debian '%s' package management program version %s.\n"), + DPKG, PACKAGE_RELEASE); + printf(_( +"This is free software; see the GNU General Public License version 2 or\n" +"later for copying conditions. There is NO warranty.\n")); + } + + m_output(stdout, _("")); + + return 0; +} + +/* + * FIXME: Options that need fixing: + * dpkg --command-fd + */ + +static int +usage(const char *const *argv) +{ + printf(_( +"Usage: %s [