summaryrefslogtreecommitdiffstats
path: root/src/main
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 18:35:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 18:35:28 +0000
commitea314d2f45c40a006c0104157013ab4b857f665f (patch)
tree3ef2971cb3675c318b8d9effd987854ad3f6d3e8 /src/main
parentInitial commit. (diff)
downloaddpkg-ea314d2f45c40a006c0104157013ab4b857f665f.tar.xz
dpkg-ea314d2f45c40a006c0104157013ab4b857f665f.zip
Adding upstream version 1.22.4.upstream/1.22.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/main/archives.c1711
-rw-r--r--src/main/archives.h99
-rw-r--r--src/main/cleanup.c260
-rw-r--r--src/main/configure.c825
-rw-r--r--src/main/depcon.c705
-rw-r--r--src/main/enquiry.c906
-rw-r--r--src/main/errors.c137
-rw-r--r--src/main/file-match.c49
-rw-r--r--src/main/file-match.h35
-rw-r--r--src/main/filters.c133
-rw-r--r--src/main/filters.h37
-rw-r--r--src/main/help.c325
-rw-r--r--src/main/main.c781
-rw-r--r--src/main/main.h304
-rw-r--r--src/main/packages.c760
-rw-r--r--src/main/perpkgstate.c43
-rw-r--r--src/main/remove.c686
-rw-r--r--src/main/script.c399
-rw-r--r--src/main/select.c244
-rw-r--r--src/main/trigproc.c576
-rw-r--r--src/main/unpack.c1734
-rw-r--r--src/main/update.c124
-rw-r--r--src/main/verify.c243
23 files changed, 11116 insertions, 0 deletions
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 <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2007-2015 Guillem Jover <guillem@debian.org>
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <obstack.h>
+#define obstack_chunk_alloc m_malloc
+#define obstack_chunk_free free
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/path.h>
+#include <dpkg/fdio.h>
+#include <dpkg/buffer.h>
+#include <dpkg/subproc.h>
+#include <dpkg/command.h>
+#include <dpkg/file.h>
+#include <dpkg/treewalk.h>
+#include <dpkg/tarfn.h>
+#include <dpkg/options.h>
+#include <dpkg/triglib.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#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 : "<none>");
+
+ 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 : "<none>");
+ 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, _("<standard output>"));
+ m_output(stderr, _("<standard error>"));
+ 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 <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2013, 2015 Guillem Jover <guillem@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef ARCHIVES_H
+#define ARCHIVES_H
+
+#include <stdbool.h>
+
+#include <dpkg/tarfn.h>
+
+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 <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2007-2015 Guillem Jover <guillem@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/path.h>
+#include <dpkg/options.h>
+#include <dpkg/db-fsys.h>
+
+#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 <ijackson@chiark.greenend.org.uk>
+ * Copyright © 1999, 2002 Wichert Akkerman <wichert@deephackmode.org>
+ * Copyright © 2007-2015 Guillem Jover <guillem@debian.org>
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <termios.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/string.h>
+#include <dpkg/buffer.h>
+#include <dpkg/file.h>
+#include <dpkg/path.h>
+#include <dpkg/subproc.h>
+#include <dpkg/command.h>
+#include <dpkg/pager.h>
+#include <dpkg/triglib.h>
+#include <dpkg/db-fsys.h>
+
+#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 <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-2014 Guillem Jover <guillem@debian.org>
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#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 <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006, 2008-2016 Guillem Jover <guillem@debian.org>
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/arch.h>
+#include <dpkg/pkg-array.h>
+#include <dpkg/pkg-show.h>
+#include <dpkg/triglib.h>
+#include <dpkg/string.h>
+#include <dpkg/options.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#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 <package> 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, _("<standard output>"));
+
+ 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", "<unknown>");
+ 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= &sectionentries;
+ *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, _("<standard output>"));
+
+ 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-<feature>"), 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, _("<standard output>"), pkg, &pkg->available);
+
+ m_output(stdout, _("<standard output>"));
+
+ 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, _("<standard output>"));
+
+ 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, _("<standard output>"));
+
+ return 0;
+}
+
+int
+validate_pkgname(const char *const *argv)
+{
+ const char *emsg;
+
+ if (!argv[0] || argv[1])
+ badusage(_("--%s takes one <pkgname> 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 <trigname> 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 <archname> 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 <version> 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:"
+ " <version> <relation> <version>"));
+
+ 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],"<unknown>")) {
+ 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],"<unknown>")) {
+ 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 <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2007-2014 Guillem Jover <guillem@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/options.h>
+
+#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 <guillem@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <stdlib.h>
+
+#include <dpkg/dpkg.h>
+
+#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 <guillem@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#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 <tfheen@err.no>
+ * Copyright © 2008, 2010, 2012-2014 Guillem Jover <guillem@debian.org>
+ * Copyright © 2010 Canonical Ltd.
+ * written by Martin Pitt <martin.pitt@canonical.com>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <fnmatch.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/db-fsys.h>
+
+#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 <tfheen@err.no>
+ * Copyright © 2008, 2010 Guillem Jover <guillem@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef DPKG_FILTERS_H
+#define DPKG_FILTERS_H
+
+#include <stdbool.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/tarfn.h>
+
+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 <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2007-2015 Guillem Jover <guillem@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/path.h>
+#include <dpkg/file.h>
+#include <dpkg/command.h>
+#include <dpkg/db-fsys.h>
+
+#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 : "<none>",
+ namenode->divert->camefrom ? namenode->divert->camefrom->name : "<none>",
+ namenode->divert->pkgset ? namenode->divert->pkgset->name : "<none>",
+ 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) : "<none>");
+
+ 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) : "<none>");
+
+ 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 <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-2016 Guillem Jover <guillem@debian.org>
+ * Copyright © 2010 Canonical Ltd.
+ * written by Martin Pitt <martin.pitt@canonical.com>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <limits.h>
+#if HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#include <string.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/macros.h>
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/arch.h>
+#include <dpkg/subproc.h>
+#include <dpkg/command.h>
+#include <dpkg/pager.h>
+#include <dpkg/options.h>
+#include <dpkg/db-fsys.h>
+
+#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, _("<standard output>"));
+
+ return 0;
+}
+
+/*
+ * FIXME: Options that need fixing:
+ * dpkg --command-fd
+ */
+
+static int
+usage(const char *const *argv)
+{
+ printf(_(
+"Usage: %s [<option>...] <command>\n"
+"\n"), DPKG);
+
+ printf(_(
+"Commands:\n"
+" -i|--install <.deb file name>... | -R|--recursive <directory>...\n"
+" --unpack <.deb file name>... | -R|--recursive <directory>...\n"
+" -A|--record-avail <.deb file name>... | -R|--recursive <directory>...\n"
+" --configure <package>... | -a|--pending\n"
+" --triggers-only <package>... | -a|--pending\n"
+" -r|--remove <package>... | -a|--pending\n"
+" -P|--purge <package>... | -a|--pending\n"
+" -V|--verify [<package>...] Verify the integrity of package(s).\n"
+" --get-selections [<pattern>...] Get list of selections to stdout.\n"
+" --set-selections Set package selections from stdin.\n"
+" --clear-selections Deselect every non-essential package.\n"
+" --update-avail [<Packages-file>] Replace available packages info.\n"
+" --merge-avail [<Packages-file>] Merge with info from file.\n"
+" --clear-avail Erase existing available info.\n"
+" --forget-old-unavail Forget uninstalled unavailable pkgs.\n"
+" -s|--status [<package>...] Display package status details.\n"
+" -p|--print-avail [<package>...] Display available version details.\n"
+" -L|--listfiles <package>... List files 'owned' by package(s).\n"
+" -l|--list [<pattern>...] List packages concisely.\n"
+" -S|--search <pattern>... Find package(s) owning file(s).\n"
+" -C|--audit [<package>...] Check for broken package(s).\n"
+" --yet-to-unpack Print packages selected for installation.\n"
+" --predep-package Print pre-dependencies to unpack.\n"
+" --add-architecture <arch> Add <arch> to the list of architectures.\n"
+" --remove-architecture <arch> Remove <arch> from the list of architectures.\n"
+" --print-architecture Print dpkg architecture.\n"
+" --print-foreign-architectures Print allowed foreign architectures.\n"
+" --assert-help Show help on assertions.\n"
+" --assert-<feature> Assert support for the specified feature.\n"
+" --validate-<thing> <string> Validate a <thing>'s <string>.\n"
+" --compare-versions <a> <op> <b> Compare version numbers - see below.\n"
+" --force-help Show help on forcing.\n"
+" -Dh|--debug=help Show help on debugging.\n"
+"\n"));
+
+ printf(_(
+" -?, --help Show this help message.\n"
+" --version Show the version.\n"
+"\n"));
+
+ printf(_(
+"Validatable things: pkgname, archname, trigname, version.\n"
+"\n"));
+
+ printf(_(
+"Use dpkg with -b, --build, -c, --contents, -e, --control, -I, --info,\n"
+" -f, --field, -x, --extract, -X, --vextract, --ctrl-tarfile, --fsys-tarfile\n"
+"on archives (type %s --help).\n"
+"\n"), BACKEND);
+
+ printf(_(
+"Options:\n"
+" --admindir=<directory> Use <directory> instead of %s.\n"
+" --root=<directory> Install on a different root directory.\n"
+" --instdir=<directory> Change installation dir without changing admin dir.\n"
+" --pre-invoke=<command> Set a pre-invoke hook.\n"
+" --post-invoke=<command> Set a post-invoke hook.\n"
+" --path-exclude=<pattern> Do not install paths which match a shell pattern.\n"
+" --path-include=<pattern> Re-include a pattern after a previous exclusion.\n"
+" -O|--selected-only Skip packages not selected for install/upgrade.\n"
+" -E|--skip-same-version Skip packages with same installed version/arch.\n"
+" -G|--refuse-downgrade Skip packages with earlier version than installed.\n"
+" -B|--auto-deconfigure Install even if it would break some other package.\n"
+" --[no-]triggers Skip or force consequential trigger processing.\n"
+" --verify-format=<format> Verify output format (supported: 'rpm').\n"
+" --no-pager Disables the use of any pager.\n"
+" --no-debsig Do not try to verify package signatures.\n"
+" --no-act|--dry-run|--simulate\n"
+" Just say what we would do - don't do it.\n"
+" -D|--debug=<octal> Enable debugging (see -Dhelp or --debug=help).\n"
+" --status-fd <n> Send status change updates to file descriptor <n>.\n"
+" --status-logger=<command> Send status change updates to <command>'s stdin.\n"
+" --log=<filename> Log status changes and actions to <filename>.\n"
+" --ignore-depends=<package>[,...]\n"
+" Ignore dependencies involving <package>.\n"
+" --force-<thing>[,...] Override problems (see --force-help).\n"
+" --no-force-<thing>[,...] Stop when problems encountered.\n"
+" --refuse-<thing>[,...] Ditto.\n"
+" --abort-after <n> Abort after encountering <n> errors.\n"
+" --robot Use machine-readable output on some commands.\n"
+"\n"), ADMINDIR);
+
+ printf(_(
+"Comparison operators for --compare-versions are:\n"
+" lt le eq ne ge gt (treat empty version as earlier than any version);\n"
+" lt-nl le-nl ge-nl gt-nl (treat empty version as later than any version);\n"
+" < << <= = >= >> > (only for compatibility with control file syntax).\n"
+"\n"));
+
+ printf(_(
+"Use 'apt' or 'aptitude' for user-friendly package management.\n"));
+
+ m_output(stdout, _("<standard output>"));
+
+ return 0;
+}
+
+static const char printforhelp[] = N_(
+"Type dpkg --help for help about installing and deinstalling packages [*];\n"
+"Use 'apt' or 'aptitude' for user-friendly package management;\n"
+"Type dpkg -Dhelp for a list of dpkg debug flag values;\n"
+"Type dpkg --force-help for a list of forcing options;\n"
+"Type dpkg-deb --help for help about manipulating *.deb files;\n"
+"\n"
+"Options marked [*] produce a lot of output - pipe it through 'less' or 'more' !");
+
+int f_robot = 0;
+int f_pending=0, f_recursive=0, f_alsoselect=1, f_skipsame=0, f_noact=0;
+int f_autodeconf=0, f_nodebsig=0;
+int f_triggers = 0;
+
+int errabort = 50;
+struct pkg_list *ignoredependss = NULL;
+
+#define DBG_DEF(n, d) \
+ { .flag = dbg_##n, .name = #n, .desc = d }
+
+static const struct debuginfo {
+ int flag;
+ const char *name;
+ const char *desc;
+} debuginfos[] = {
+ DBG_DEF(general, N_("Generally helpful progress information")),
+ DBG_DEF(scripts, N_("Invocation and status of maintainer scripts")),
+ DBG_DEF(eachfile, N_("Output for each file processed")),
+ DBG_DEF(eachfiledetail, N_("Lots of output for each file processed")),
+ DBG_DEF(conff, N_("Output for each configuration file")),
+ DBG_DEF(conffdetail, N_("Lots of output for each configuration file")),
+ DBG_DEF(depcon, N_("Dependencies and conflicts")),
+ DBG_DEF(depcondetail, N_("Lots of dependencies/conflicts output")),
+ DBG_DEF(triggers, N_("Trigger activation and processing")),
+ DBG_DEF(triggersdetail, N_("Lots of output regarding triggers")),
+ DBG_DEF(triggersstupid, N_("Silly amounts of output regarding triggers")),
+ DBG_DEF(veryverbose, N_("Lots of drivel about eg the dpkg/info directory")),
+ DBG_DEF(stupidlyverbose, N_("Insane amounts of drivel")),
+ { 0, NULL, NULL }
+};
+
+static void
+set_debug(const struct cmdinfo *cpi, const char *value)
+{
+ long mask;
+
+ if (*value == 'h') {
+ const struct debuginfo *dip;
+
+ printf(_(
+"%s debugging option, --debug=<octal> or -D<octal>:\n"
+"\n"
+" Number Ref. in source Description\n"), DPKG);
+
+ for (dip = debuginfos; dip->name; dip++)
+ printf(" %6o %-16s %s\n", dip->flag, dip->name, gettext(dip->desc));
+
+ printf(_("\n"
+"Debugging options can be mixed using bitwise-or.\n"
+"Note that the meanings and values are subject to change.\n"));
+ m_output(stdout, _("<standard output>"));
+ exit(0);
+ }
+
+ mask = debug_parse_mask(value);
+ if (mask < 0)
+ badusage(_("--%s requires a positive octal argument"), cpi->olong);
+}
+
+static void
+set_no_pager(const struct cmdinfo *ci, const char *value)
+{
+ pager_enable(false);
+
+ /* Let's communicate this to our backends. */
+ setenv("DPKG_PAGER", "cat", 1);
+}
+
+static void
+set_filter(const struct cmdinfo *cip, const char *value)
+{
+ filter_add(value, cip->arg_int);
+}
+
+static void
+set_verify_format(const struct cmdinfo *cip, const char *value)
+{
+ if (!verify_set_output(value))
+ badusage(_("unknown verify output format '%s'"), value);
+}
+
+static void
+set_ignore_depends(const struct cmdinfo *cip, const char *value)
+{
+ char *copy, *p;
+
+ copy= m_malloc(strlen(value)+2);
+ strcpy(copy,value);
+ copy[strlen(value) + 1] = '\0';
+ for (p=copy; *p; p++) {
+ if (*p != ',') continue;
+ *p++ = '\0';
+ if (!*p || *p==',' || p==copy+1)
+ badusage(_("null package name in --%s comma-separated list '%.250s'"),
+ cip->olong, value);
+ }
+ p= copy;
+ while (*p) {
+ struct pkginfo *pkg;
+
+ pkg = dpkg_options_parse_pkgname(cip, p);
+ pkg_list_prepend(&ignoredependss, pkg);
+
+ p+= strlen(p)+1;
+ }
+
+ free(copy);
+}
+
+static void
+set_integer(const struct cmdinfo *cip, const char *value)
+{
+ *cip->iassignto = dpkg_options_parse_arg_int(cip, value);
+}
+
+static void
+set_pipe(const struct cmdinfo *cip, const char *value)
+{
+ long v;
+
+ v = dpkg_options_parse_arg_int(cip, value);
+
+ statusfd_add(v);
+}
+
+static bool
+is_invoke_action(enum action action)
+{
+ switch (action) {
+ case act_unpack:
+ case act_configure:
+ case act_install:
+ case act_triggers:
+ case act_remove:
+ case act_purge:
+ case act_arch_add:
+ case act_arch_remove:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static struct invoke_list pre_invoke_hooks = {
+ .head = NULL,
+ .tail = &pre_invoke_hooks.head,
+};
+static struct invoke_list post_invoke_hooks = {
+ .head = NULL,
+ .tail = &post_invoke_hooks.head,
+};
+static struct invoke_list status_loggers = {
+ .head = NULL,
+ .tail = &status_loggers.head,
+};
+
+static void
+set_invoke_hook(const struct cmdinfo *cip, const char *value)
+{
+ struct invoke_list *hook_list = cip->arg_ptr;
+ struct invoke_hook *hook_new;
+
+ hook_new = m_malloc(sizeof(*hook_new));
+ hook_new->command = m_strdup(value);
+ hook_new->next = NULL;
+
+ /* Add the new hook at the tail of the list to preserve the order. */
+ *hook_list->tail = hook_new;
+ hook_list->tail = &hook_new->next;
+}
+
+static void
+run_invoke_hooks(const char *action, struct invoke_list *hook_list)
+{
+ struct invoke_hook *hook;
+
+ setenv("DPKG_HOOK_ACTION", action, 1);
+
+ for (hook = hook_list->head; hook; hook = hook->next) {
+ int status;
+
+ /* XXX: As an optimization, use exec instead if no shell metachar are
+ * used “!$=&|\\`'"^~;<>{}[]()?*#”. */
+ status = system(hook->command);
+ if (status != 0)
+ ohshit(_("error executing hook '%s', exit code %d"), hook->command,
+ status);
+ }
+
+ unsetenv("DPKG_HOOK_ACTION");
+}
+
+static void
+free_invoke_hooks(struct invoke_list *hook_list)
+{
+ struct invoke_hook *hook, *hook_next;
+
+ for (hook = hook_list->head; hook; hook = hook_next) {
+ hook_next = hook->next;
+ free(hook->command);
+ free(hook);
+ }
+}
+
+static int
+run_logger(struct invoke_hook *hook, const char *name)
+{
+ pid_t pid;
+ int p[2];
+
+ m_pipe(p);
+
+ pid = subproc_fork();
+ if (pid == 0) {
+ /* Setup stdin and stdout. */
+ m_dup2(p[0], 0);
+ close(1);
+
+ close(p[0]);
+ close(p[1]);
+
+ command_shell(hook->command, name);
+ }
+ close(p[0]);
+
+ return p[1];
+}
+
+static void
+run_status_loggers(struct invoke_list *hook_list)
+{
+ struct invoke_hook *hook;
+
+ for (hook = hook_list->head; hook; hook = hook->next) {
+ int fd;
+
+ fd = run_logger(hook, _("status logger"));
+ statusfd_add(fd);
+ }
+}
+
+static int
+arch_add(const char *const *argv)
+{
+ struct dpkg_arch *arch;
+ const char *archname = *argv++;
+
+ if (archname == NULL || *argv)
+ badusage(_("--%s takes exactly one argument"), cipaction->olong);
+
+ dpkg_arch_load_list();
+
+ arch = dpkg_arch_add(archname);
+ switch (arch->type) {
+ case DPKG_ARCH_NATIVE:
+ case DPKG_ARCH_FOREIGN:
+ break;
+ case DPKG_ARCH_ILLEGAL:
+ ohshit(_("architecture '%s' is illegal: %s"), archname,
+ dpkg_arch_name_is_illegal(archname));
+ default:
+ ohshit(_("architecture '%s' is reserved and cannot be added"), archname);
+ }
+
+ dpkg_arch_save_list();
+
+ return 0;
+}
+
+static int
+arch_remove(const char *const *argv)
+{
+ const char *archname = *argv++;
+ struct dpkg_arch *arch;
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg;
+
+ if (archname == NULL || *argv)
+ badusage(_("--%s takes exactly one argument"), cipaction->olong);
+
+ modstatdb_open(msdbrw_readonly);
+
+ arch = dpkg_arch_find(archname);
+ if (arch->type != DPKG_ARCH_FOREIGN) {
+ warning(_("cannot remove non-foreign architecture '%s'"), arch->name);
+ return 0;
+ }
+
+ /* Check if it's safe to remove the architecture from the db. */
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ if (pkg->status < PKG_STAT_HALFINSTALLED)
+ continue;
+ if (pkg->installed.arch == arch) {
+ if (in_force(FORCE_ARCHITECTURE))
+ warning(_("removing architecture '%s' currently in use by database"),
+ arch->name);
+ else
+ ohshit(_("cannot remove architecture '%s' currently in use by the database"),
+ arch->name);
+ break;
+ }
+ }
+ pkg_hash_iter_free(iter);
+
+ dpkg_arch_unmark(arch);
+ dpkg_arch_save_list();
+
+ modstatdb_shutdown();
+
+ return 0;
+}
+
+int execbackend(const char *const *argv) DPKG_ATTR_NORET;
+int commandfd(const char *const *argv);
+
+/* This table has both the action entries in it and the normal options.
+ * The action entries are made with the ACTION macro, as they all
+ * have a very similar structure. */
+static const struct cmdinfo cmdinfos[]= {
+#define ACTIONBACKEND(longopt, shortopt, backend) \
+ { longopt, shortopt, 0, NULL, NULL, setaction, 0, (void *)backend, execbackend }
+
+ ACTION( "install", 'i', act_install, archivefiles ),
+ ACTION( "unpack", 0, act_unpack, archivefiles ),
+ ACTION( "record-avail", 'A', act_avail, archivefiles ),
+ ACTION( "configure", 0, act_configure, packages ),
+ ACTION( "remove", 'r', act_remove, packages ),
+ ACTION( "purge", 'P', act_purge, packages ),
+ ACTION( "triggers-only", 0, act_triggers, packages ),
+ ACTION( "verify", 'V', act_verify, verify ),
+ ACTIONBACKEND( "listfiles", 'L', DPKGQUERY),
+ ACTIONBACKEND( "status", 's', DPKGQUERY),
+ ACTION( "get-selections", 0, act_getselections, getselections ),
+ ACTION( "set-selections", 0, act_setselections, setselections ),
+ ACTION( "clear-selections", 0, act_clearselections, clearselections ),
+ ACTIONBACKEND( "print-avail", 'p', DPKGQUERY),
+ ACTION( "update-avail", 0, act_avreplace, updateavailable ),
+ ACTION( "merge-avail", 0, act_avmerge, updateavailable ),
+ ACTION( "clear-avail", 0, act_avclear, updateavailable ),
+ ACTION( "forget-old-unavail", 0, act_forgetold, forgetold ),
+ ACTION( "audit", 'C', act_audit, audit ),
+ ACTION( "yet-to-unpack", 0, act_unpackchk, unpackchk ),
+ ACTIONBACKEND( "list", 'l', DPKGQUERY),
+ ACTIONBACKEND( "search", 'S', DPKGQUERY),
+ ACTION_MUX( "assert", 0, act_assert_feature, assert_feature, &assert_feature_name),
+ ACTION( "add-architecture", 0, act_arch_add, arch_add ),
+ ACTION( "remove-architecture", 0, act_arch_remove, arch_remove ),
+ ACTION( "print-architecture", 0, act_printarch, printarch ),
+ ACTION( "print-foreign-architectures", 0, act_printforeignarches, print_foreign_arches ),
+ ACTION( "predep-package", 0, act_predeppackage, predeppackage ),
+ ACTION( "validate-pkgname", 0, act_validate_pkgname, validate_pkgname ),
+ ACTION( "validate-trigname", 0, act_validate_trigname, validate_trigname ),
+ ACTION( "validate-archname", 0, act_validate_archname, validate_archname ),
+ ACTION( "validate-version", 0, act_validate_version, validate_version ),
+ ACTION( "compare-versions", 0, act_cmpversions, cmpversions ),
+/*
+ ACTION( "command-fd", 'c', act_commandfd, commandfd ),
+*/
+ ACTION( "help", '?', act_help, usage),
+ ACTION( "version", 0, act_version, printversion),
+
+ { "pre-invoke", 0, 1, NULL, NULL, set_invoke_hook, 0, &pre_invoke_hooks },
+ { "post-invoke", 0, 1, NULL, NULL, set_invoke_hook, 0, &post_invoke_hooks },
+ { "path-exclude", 0, 1, NULL, NULL, set_filter, 0 },
+ { "path-include", 0, 1, NULL, NULL, set_filter, 1 },
+ { "verify-format", 0, 1, NULL, NULL, set_verify_format },
+ { "status-logger", 0, 1, NULL, NULL, set_invoke_hook, 0, &status_loggers },
+ { "status-fd", 0, 1, NULL, NULL, set_pipe, 0 },
+ { "log", 0, 1, NULL, &log_file, NULL, 0 },
+ { "pending", 'a', 0, &f_pending, NULL, NULL, 1 },
+ { "recursive", 'R', 0, &f_recursive, NULL, NULL, 1 },
+ { "no-act", 0, 0, &f_noact, NULL, NULL, 1 },
+ { "dry-run", 0, 0, &f_noact, NULL, NULL, 1 },
+ { "simulate", 0, 0, &f_noact, NULL, NULL, 1 },
+ { "no-pager", 0, 0, NULL, NULL, set_no_pager, 0 },
+ { "no-debsig", 0, 0, &f_nodebsig, NULL, NULL, 1 },
+ /* Alias ('G') for --refuse. */
+ { NULL, 'G', 0, NULL, NULL, reset_force_option, FORCE_DOWNGRADE },
+ { "selected-only", 'O', 0, &f_alsoselect, NULL, NULL, 0 },
+ { "triggers", 0, 0, &f_triggers, NULL, NULL, 1 },
+ { "no-triggers", 0, 0, &f_triggers, NULL, NULL, -1 },
+ /* TODO: Remove ('N') sometime. */
+ { "no-also-select", 'N', 0, &f_alsoselect, NULL, NULL, 0 },
+ { "skip-same-version", 'E', 0, &f_skipsame, NULL, NULL, 1 },
+ { "auto-deconfigure", 'B', 0, &f_autodeconf, NULL, NULL, 1 },
+ { "robot", 0, 0, &f_robot, NULL, NULL, 1 },
+ { "root", 0, 1, NULL, NULL, set_root, 0 },
+ { "abort-after", 0, 1, &errabort, NULL, set_integer, 0 },
+ { "admindir", 0, 1, NULL, NULL, set_admindir, 0 },
+ { "instdir", 0, 1, NULL, NULL, set_instdir, 0 },
+ { "ignore-depends", 0, 1, NULL, NULL, set_ignore_depends, 0 },
+ { "force", 0, 2, NULL, NULL, set_force_option, 1 },
+ { "refuse", 0, 2, NULL, NULL, set_force_option, 0 },
+ { "no-force", 0, 2, NULL, NULL, set_force_option, 0 },
+ { "debug", 'D', 1, NULL, NULL, set_debug, 0 },
+ ACTIONBACKEND( "build", 'b', BACKEND),
+ ACTIONBACKEND( "contents", 'c', BACKEND),
+ ACTIONBACKEND( "control", 'e', BACKEND),
+ ACTIONBACKEND( "info", 'I', BACKEND),
+ ACTIONBACKEND( "field", 'f', BACKEND),
+ ACTIONBACKEND( "extract", 'x', BACKEND),
+ ACTIONBACKEND( "vextract", 'X', BACKEND),
+ ACTIONBACKEND( "ctrl-tarfile", 0, BACKEND),
+ ACTIONBACKEND( "fsys-tarfile", 0, BACKEND),
+ { NULL, 0, 0, NULL, NULL, NULL, 0 }
+};
+
+int
+execbackend(const char *const *argv)
+{
+ struct command cmd;
+
+ command_init(&cmd, cipaction->arg_ptr, NULL);
+ command_add_arg(&cmd, cipaction->arg_ptr);
+ command_add_arg(&cmd, str_fmt("--%s", cipaction->olong));
+
+ /* Explicitly separate arguments from options as any user-supplied
+ * separator got stripped by the option parser */
+ command_add_arg(&cmd, "--");
+ command_add_argl(&cmd, (const char **)argv);
+
+ command_exec(&cmd);
+}
+
+int
+commandfd(const char *const *argv)
+{
+ struct varbuf linevb = VARBUF_INIT;
+ const char * pipein;
+ const char **newargs = NULL, **endargs;
+ char *ptr, *endptr;
+ FILE *in;
+ long infd;
+ int ret = 0;
+ int c, lno, i;
+ bool skipchar;
+
+ pipein = *argv++;
+ if (pipein == NULL || *argv)
+ badusage(_("--%s takes exactly one argument"), cipaction->olong);
+
+ infd = dpkg_options_parse_arg_int(cipaction, pipein);
+ in = fdopen(infd, "r");
+ if (in == NULL)
+ ohshite(_("couldn't open '%i' for stream"), (int)infd);
+
+ lno = 0;
+
+ for (;;) {
+ bool mode = false;
+ int argc= 1;
+
+ push_error_context();
+
+ do {
+ c = getc(in);
+ if (c == '\n')
+ lno++;
+ } while (c != EOF && c_isspace(c));
+ if (c == EOF) break;
+ if (c == '#') {
+ do { c= getc(in); if (c == '\n') lno++; } while (c != EOF && c != '\n');
+ continue;
+ }
+ varbuf_reset(&linevb);
+ do {
+ varbuf_add_char(&linevb, c);
+ c= getc(in);
+ if (c == '\n') lno++;
+
+ /* This isn't fully accurate, but overestimating can't hurt. */
+ if (c_isspace(c))
+ argc++;
+ } while (c != EOF && c != '\n');
+ if (c == EOF)
+ ohshit(_("unexpected end of file before end of line %d"), lno);
+ if (!argc) continue;
+ varbuf_end_str(&linevb);
+ newargs = m_realloc(newargs, sizeof(const char *) * (argc + 1));
+ argc= 1;
+ ptr= linevb.buf;
+ endptr = ptr + linevb.used + 1;
+ skipchar = false;
+ while(ptr < endptr) {
+ if (skipchar) {
+ skipchar = false;
+ } else if (*ptr == '\\') {
+ memmove(ptr, (ptr+1), (linevb.used-(linevb.buf - ptr)-1));
+ endptr--;
+ skipchar = true;
+ continue;
+ } else if (c_isspace(*ptr)) {
+ if (mode == true) {
+ *ptr = '\0';
+ mode = false;
+ }
+ } else {
+ if (mode == false) {
+ newargs[argc]= ptr;
+ argc++;
+ mode = true;
+ }
+ }
+ ptr++;
+ }
+ *ptr = '\0';
+ newargs[argc++] = NULL;
+
+ /* We strdup() each argument, but never free it, because the
+ * error messages contain references back to these strings.
+ * Freeing them, and reusing the memory, would make those
+ * error messages confusing, to say the least. */
+ for(i=1;i<argc;i++)
+ if (newargs[i])
+ newargs[i] = m_strdup(newargs[i]);
+ endargs = newargs;
+
+ setaction(NULL, NULL);
+ dpkg_options_parse((const char *const **)&endargs, cmdinfos, printforhelp);
+ if (!cipaction) badusage(_("need an action option"));
+
+ ret |= cipaction->action(endargs);
+
+ fsys_hash_reset();
+
+ pop_error_context(ehflag_normaltidy);
+ }
+
+ fclose(in);
+
+ return ret;
+}
+
+int main(int argc, const char *const *argv) {
+ char *force_string;
+ int ret;
+
+ dpkg_locales_init(PACKAGE);
+ dpkg_program_init("dpkg");
+ set_force_default(FORCE_ALL);
+ dpkg_options_load(DPKG, cmdinfos);
+ dpkg_options_parse(&argv, cmdinfos, printforhelp);
+
+ debug(dbg_general, "root=%s admindir=%s", dpkg_fsys_get_dir(), dpkg_db_get_dir());
+
+ /* When running as root, make sure our primary group is also root, so
+ * that files created by maintainer scripts have correct ownership. */
+ if (!in_force(FORCE_NON_ROOT) && getuid() == 0 && getgid() != 0)
+ if (setgid(0) < 0)
+ ohshite(_("cannot set primary group ID to root"));
+
+ if (!cipaction) badusage(_("need an action option"));
+
+ /* Always set environment, to avoid possible security risks. */
+ if (setenv("DPKG_ADMINDIR", dpkg_db_get_dir(), 1) < 0)
+ ohshite(_("unable to setenv for subprocesses"));
+ if (setenv("DPKG_ROOT", dpkg_fsys_get_dir(), 1) < 0)
+ ohshite(_("unable to setenv for subprocesses"));
+ force_string = get_force_string();
+ if (setenv("DPKG_FORCE", force_string, 1) < 0)
+ ohshite(_("unable to setenv for subprocesses"));
+ free(force_string);
+
+ if (!f_triggers)
+ f_triggers = (cipaction->arg_int == act_triggers && *argv) ? -1 : 1;
+
+ if (is_invoke_action(cipaction->arg_int)) {
+ run_invoke_hooks(cipaction->olong, &pre_invoke_hooks);
+ run_status_loggers(&status_loggers);
+ }
+
+ ret = cipaction->action(argv);
+
+ if (is_invoke_action(cipaction->arg_int))
+ run_invoke_hooks(cipaction->olong, &post_invoke_hooks);
+
+ free_invoke_hooks(&pre_invoke_hooks);
+ free_invoke_hooks(&post_invoke_hooks);
+
+ dpkg_program_done();
+ dpkg_locales_done();
+
+ return reportbroken_retexitstatus(ret);
+}
diff --git a/src/main/main.h b/src/main/main.h
new file mode 100644
index 0000000..f841e8d
--- /dev/null
+++ b/src/main/main.h
@@ -0,0 +1,304 @@
+/*
+ * dpkg - main program for package management
+ * main.h - external definitions for this program
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006, 2008-2016 Guillem Jover <guillem@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#include <dpkg/debug.h>
+#include <dpkg/pkg-list.h>
+
+#include "force.h"
+#include "actions.h"
+#include "security-mac.h"
+
+/* These two are defined in <dpkg/fsys.h>. */
+struct fsys_namenode_list;
+struct fsys_namenode;
+
+enum pkg_istobe {
+ /** Package is to be left in a normal state. */
+ PKG_ISTOBE_NORMAL,
+ /** Package is to be removed. */
+ PKG_ISTOBE_REMOVE,
+ /** Package is to be installed, configured or triggered. */
+ PKG_ISTOBE_INSTALLNEW,
+ /** Package is to be deconfigured. */
+ PKG_ISTOBE_DECONFIGURE,
+ /** Package is to be checked for Pre-Depends satisfiability. */
+ PKG_ISTOBE_PREINSTALL,
+};
+
+enum pkg_cycle_color {
+ PKG_CYCLE_WHITE,
+ PKG_CYCLE_GRAY,
+ PKG_CYCLE_BLACK,
+};
+
+struct perpackagestate {
+ enum pkg_istobe istobe;
+
+ /** Used during cycle detection. */
+ enum pkg_cycle_color color;
+
+ bool enqueued;
+
+ int replacingfilesandsaid;
+ int cmdline_seen;
+
+ /** Non-NULL iff in trigproc.c:deferred. */
+ struct pkg_list *trigprocdeferred;
+};
+
+extern const char *const statusstrings[];
+
+extern int f_robot;
+extern int f_pending, f_recursive, f_alsoselect, f_skipsame, f_noact;
+extern int f_autodeconf, f_nodebsig;
+extern int f_triggers;
+
+extern bool abort_processing;
+extern int errabort;
+extern struct pkg_list *ignoredependss;
+
+struct invoke_hook {
+ struct invoke_hook *next;
+ char *command;
+};
+
+struct invoke_list {
+ struct invoke_hook *head, **tail;
+};
+
+/* from perpkgstate.c */
+
+void ensure_package_clientdata(struct pkginfo *pkg);
+
+/* from archives.c */
+
+int archivefiles(const char *const *argv);
+void process_archive(const char *filename);
+bool wanttoinstall(struct pkginfo *pkg);
+
+/* from update.c */
+
+int forgetold(const char *const *argv);
+int updateavailable(const char *const *argv);
+
+/* from enquiry.c */
+
+extern const char *assert_feature_name;
+
+int audit(const char *const *argv);
+int unpackchk(const char *const *argv);
+int assert_feature(const char *const *argv);
+int validate_pkgname(const char *const *argv);
+int validate_trigname(const char *const *argv);
+int validate_archname(const char *const *argv);
+int validate_version(const char *const *argv);
+int predeppackage(const char *const *argv);
+int printarch(const char *const *argv);
+int printinstarch(const char *const *argv);
+int print_foreign_arches(const char *const *argv);
+int cmpversions(const char *const *argv);
+
+/* from verify.c */
+
+bool verify_set_output(const char *name);
+int verify(const char *const *argv);
+
+/* from select.c */
+
+int getselections(const char *const *argv);
+int setselections(const char *const *argv);
+int clearselections(const char *const *argv);
+
+/* from packages.c, remove.c and configure.c */
+
+void md5hash(struct pkginfo *pkg, char *hashbuf, const char *fn);
+void enqueue_package(struct pkginfo *pkg);
+void enqueue_package_mark_seen(struct pkginfo *pkg);
+void process_queue(void);
+int packages(const char *const *argv);
+void removal_bulk(struct pkginfo *pkg);
+int conffderef(struct pkginfo *pkg, struct varbuf *result, const char *in);
+
+enum dep_check {
+ DEP_CHECK_HALT = 0,
+ DEP_CHECK_DEFER = 1,
+ DEP_CHECK_OK = 2,
+};
+
+enum dep_check dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
+ struct varbuf *aemsgs);
+enum dep_check breakses_ok(struct pkginfo *pkg, struct varbuf *aemsgs);
+
+void deferred_remove(struct pkginfo *pkg);
+void deferred_configure(struct pkginfo *pkg);
+
+/*
+ * During the packages queue processing, the algorithm for deciding what to
+ * configure first is as follows:
+ *
+ * Loop through all packages doing a ‘try 1’ until we've been round and
+ * nothing has been done, then do ‘try 2’, and subsequent ones likewise.
+ * The incrementing of ‘dependtry’ is done by process_queue().
+ *
+ * Try 1:
+ * Are all dependencies of this package done? If so, do it.
+ * Are any of the dependencies missing or the wrong version?
+ * If so, abort (unless --force-depends, in which case defer).
+ * Will we need to configure a package we weren't given as an
+ * argument? If so, abort ─ except if --force-configure-any,
+ * in which case we add the package to the argument list.
+ * If none of the above, defer the package.
+ *
+ * Try 2:
+ * Find a cycle and break it (see above).
+ * Do as for try 1.
+ *
+ * Try 3:
+ * Start processing triggers if necessary.
+ * Do as for try 2.
+ *
+ * Try 4:
+ * Same as for try 3, but check trigger cycles even when deferring
+ * processing due to unsatisfiable dependencies.
+ *
+ * Try 5 (only if --force-depends-version):
+ * Same as for try 2, but don't mind version number in dependencies.
+ *
+ * Try 6 (only if --force-depends):
+ * Do anyway.
+ */
+enum dependtry {
+ DEPEND_TRY_NORMAL = 1,
+ DEPEND_TRY_CYCLES = 2,
+ DEPEND_TRY_TRIGGERS = 3,
+ DEPEND_TRY_TRIGGERS_CYCLES = 4,
+ DEPEND_TRY_FORCE_DEPENDS_VERSION = 5,
+ DEPEND_TRY_FORCE_DEPENDS = 6,
+ DEPEND_TRY_LAST,
+};
+
+extern enum dependtry dependtry;
+extern int sincenothing;
+
+/* from cleanup.c (most of these are declared in archives.h) */
+
+void cu_prermremove(int argc, void **argv);
+
+/* from errors.c */
+
+void print_error_perpackage(const char *emsg, const void *data);
+void print_error_perarchive(const char *emsg, const void *data);
+int reportbroken_retexitstatus(int ret);
+bool skip_due_to_hold(struct pkginfo *pkg);
+
+/* from help.c */
+
+struct stat;
+
+bool ignore_depends(const struct pkginfo *pkg);
+bool force_breaks(struct deppossi *possi);
+bool force_depends(struct deppossi *possi);
+bool force_conflicts(struct deppossi *possi);
+void
+conffile_mark_obsolete(struct pkginfo *pkg, struct fsys_namenode *namenode);
+void pkg_conffiles_mark_old(struct pkginfo *pkg);
+void checkpath(void);
+
+struct fsys_namenode *
+namenodetouse(struct fsys_namenode *namenode,
+ struct pkginfo *pkg, struct pkgbin *pkgbin);
+
+int maintscript_installed(struct pkginfo *pkg, const char *scriptname,
+ const char *desc, ...) DPKG_ATTR_SENTINEL;
+int maintscript_new(struct pkginfo *pkg,
+ const char *scriptname, const char *desc,
+ const char *cidir, char *cidirrest, ...)
+ DPKG_ATTR_SENTINEL;
+int maintscript_fallback(struct pkginfo *pkg,
+ const char *scriptname, const char *desc,
+ const char *cidir, char *cidirrest,
+ const char *ifok, const char *iffallback);
+
+/* Callers wanting to run the postinst use these two as they want to postpone
+ * trigger incorporation until after updating the package status. The effect
+ * is that a package can trigger itself. */
+int maintscript_postinst(struct pkginfo *pkg, ...) DPKG_ATTR_SENTINEL;
+void post_postinst_tasks(struct pkginfo *pkg, enum pkgstatus new_status);
+
+void clear_istobes(void);
+bool
+dir_is_used_by_others(struct fsys_namenode *namenode, struct pkginfo *pkg);
+bool
+dir_is_used_by_pkg(struct fsys_namenode *namenode, struct pkginfo *pkg,
+ struct fsys_namenode_list *list);
+bool
+dir_has_conffiles(struct fsys_namenode *namenode, struct pkginfo *pkg);
+
+void log_action(const char *action, struct pkginfo *pkg, struct pkgbin *pkgbin);
+
+/* from trigproc.c */
+
+enum trigproc_type {
+ /** Opportunistic deferred trigger processing. */
+ TRIGPROC_TRY_DEFERRED,
+ /** Opportunistic queued trigger processing. */
+ TRIGPROC_TRY_QUEUED,
+ /** Required trigger processing. */
+ TRIGPROC_REQUIRED,
+};
+
+void trigproc_install_hooks(void);
+void trigproc_populate_deferred(void);
+void trigproc_run_deferred(void);
+void trigproc_reset_cycle(void);
+
+void trigproc(struct pkginfo *pkg, enum trigproc_type type);
+
+void trig_activate_packageprocessing(struct pkginfo *pkg);
+
+/* from depcon.c */
+
+enum which_pkgbin {
+ wpb_installed,
+ wpb_available,
+ wpb_by_istobe,
+};
+
+struct deppossi_pkg_iterator;
+
+struct deppossi_pkg_iterator *
+deppossi_pkg_iter_new(struct deppossi *possi, enum which_pkgbin wpb);
+struct pkginfo *
+deppossi_pkg_iter_next(struct deppossi_pkg_iterator *iter);
+void
+deppossi_pkg_iter_free(struct deppossi_pkg_iterator *iter);
+
+bool depisok(struct dependency *dep, struct varbuf *whynot,
+ struct pkginfo **fixbyrm, struct pkginfo **fixbytrigaw,
+ bool allowunconfigd);
+struct cyclesofarlink;
+bool findbreakcycle(struct pkginfo *pkg);
+void describedepcon(struct varbuf *addto, struct dependency *dep);
+
+#endif /* MAIN_H */
diff --git a/src/main/packages.c b/src/main/packages.c
new file mode 100644
index 0000000..aba9ba7
--- /dev/null
+++ b/src/main/packages.c
@@ -0,0 +1,760 @@
+/*
+ * dpkg - main program for package management
+ * packages.c - common to actions that process packages
+ *
+ * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-2014 Guillem Jover <guillem@debian.org>
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <string.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-list.h>
+#include <dpkg/pkg-queue.h>
+#include <dpkg/string.h>
+#include <dpkg/options.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+static struct pkginfo *progress_bytrigproc;
+static struct pkg_queue queue = PKG_QUEUE_INIT;
+
+enum dependtry dependtry = DEPEND_TRY_NORMAL;
+int sincenothing = 0;
+
+void
+enqueue_package(struct pkginfo *pkg)
+{
+ ensure_package_clientdata(pkg);
+ if (pkg->clientdata->enqueued)
+ return;
+ pkg->clientdata->enqueued = true;
+ pkg_queue_push(&queue, pkg);
+}
+
+void
+enqueue_package_mark_seen(struct pkginfo *pkg)
+{
+ enqueue_package(pkg);
+ pkg->clientdata->cmdline_seen++;
+}
+
+static void
+enqueue_pending(void)
+{
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg;
+
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter)) != NULL) {
+ switch (cipaction->arg_int) {
+ case act_configure:
+ if (!(pkg->status == PKG_STAT_UNPACKED ||
+ pkg->status == PKG_STAT_HALFCONFIGURED ||
+ pkg->trigpend_head))
+ continue;
+ if (pkg->want != PKG_WANT_INSTALL &&
+ pkg->want != PKG_WANT_HOLD)
+ continue;
+ break;
+ case act_triggers:
+ if (!pkg->trigpend_head)
+ continue;
+ if (pkg->want != PKG_WANT_INSTALL &&
+ pkg->want != PKG_WANT_HOLD)
+ continue;
+ break;
+ case act_remove:
+ case act_purge:
+ if (pkg->want != PKG_WANT_PURGE) {
+ if (pkg->want != PKG_WANT_DEINSTALL)
+ continue;
+ if (pkg->status == PKG_STAT_CONFIGFILES)
+ continue;
+ }
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ continue;
+ break;
+ default:
+ internerr("unknown action '%d'", cipaction->arg_int);
+ }
+ enqueue_package(pkg);
+ }
+ pkg_hash_iter_free(iter);
+}
+
+static void
+enqueue_specified(const char *const *argv)
+{
+ const char *thisarg;
+
+ while ((thisarg = *argv++) != NULL) {
+ struct pkginfo *pkg;
+
+ pkg = dpkg_options_parse_pkgname(cipaction, thisarg);
+ if (pkg->status == PKG_STAT_NOTINSTALLED &&
+ str_match_end(pkg->set->name, DEBEXT)) {
+ badusage(_("you must specify packages by their own names, "
+ "not by quoting the names of the files they come in"));
+ }
+ enqueue_package_mark_seen(pkg);
+ }
+
+ if (cipaction->arg_int == act_configure)
+ trigproc_populate_deferred();
+}
+
+int
+packages(const char *const *argv)
+{
+ trigproc_install_hooks();
+
+ modstatdb_open(f_noact ? msdbrw_readonly :
+ in_force(FORCE_NON_ROOT) ? msdbrw_write :
+ msdbrw_needsuperuser);
+ checkpath();
+ pkg_infodb_upgrade();
+
+ log_message("startup packages %s", cipaction->olong);
+
+ if (f_pending) {
+ if (*argv)
+ badusage(_("--%s --pending does not take any non-option arguments"),cipaction->olong);
+
+ enqueue_pending();
+ } else {
+ if (!*argv)
+ badusage(_("--%s (without --pending) needs at least one package name argument"),
+ cipaction->olong);
+
+ enqueue_specified(argv);
+ }
+
+ ensure_diversions();
+
+ process_queue();
+ trigproc_run_deferred();
+
+ modstatdb_shutdown();
+
+ return 0;
+}
+
+void process_queue(void) {
+ struct pkg_list *rundown;
+ volatile enum action action_todo;
+ jmp_buf ejbuf;
+ enum pkg_istobe istobe = PKG_ISTOBE_NORMAL;
+
+ if (abort_processing)
+ return;
+
+ clear_istobes();
+
+ switch (cipaction->arg_int) {
+ case act_triggers:
+ case act_configure:
+ case act_install:
+ istobe = PKG_ISTOBE_INSTALLNEW;
+ break;
+ case act_remove:
+ case act_purge:
+ istobe = PKG_ISTOBE_REMOVE;
+ break;
+ default:
+ internerr("unknown action '%d'", cipaction->arg_int);
+ }
+ for (rundown = queue.head; rundown; rundown = rundown->next) {
+ ensure_package_clientdata(rundown->pkg);
+
+ /* We have processed this package more than once. There are no duplicates
+ * as we make sure of that when enqueuing them. */
+ if (rundown->pkg->clientdata->cmdline_seen > 1) {
+ switch (cipaction->arg_int) {
+ case act_triggers:
+ case act_configure: case act_remove: case act_purge:
+ printf(_("Package %s listed more than once, only processing once.\n"),
+ pkg_name(rundown->pkg, pnaw_nonambig));
+ break;
+ case act_install:
+ printf(_("More than one copy of package %s has been unpacked\n"
+ " in this run ! Only configuring it once.\n"),
+ pkg_name(rundown->pkg, pnaw_nonambig));
+ break;
+ default:
+ internerr("unknown action '%d'", cipaction->arg_int);
+ }
+ }
+ rundown->pkg->clientdata->istobe = istobe;
+ }
+
+ while (!pkg_queue_is_empty(&queue)) {
+ struct pkginfo *volatile pkg;
+
+ pkg = pkg_queue_pop(&queue);
+ if (!pkg)
+ continue; /* Duplicate, which we removed earlier. */
+
+ ensure_package_clientdata(pkg);
+ pkg->clientdata->enqueued = false;
+
+ action_todo = cipaction->arg_int;
+
+ if (sincenothing++ > queue.length * 3 + 2) {
+ /* Make sure that even if we have exceeded the queue since not having
+ * made any progress, we are not getting stuck trying to progress by
+ * trigger processing, w/o jumping into the next dependtry. */
+ dependtry++;
+ sincenothing = 0;
+ if (dependtry >= DEPEND_TRY_LAST)
+ internerr("exceeded dependtry %d (sincenothing=%d; queue.length=%d)",
+ dependtry, sincenothing, queue.length);
+ } else if (sincenothing > queue.length * 2 + 2) {
+ if (dependtry >= DEPEND_TRY_TRIGGERS &&
+ progress_bytrigproc && progress_bytrigproc->trigpend_head) {
+ enqueue_package(pkg);
+ pkg = progress_bytrigproc;
+ progress_bytrigproc = NULL;
+ action_todo = act_configure;
+ } else {
+ dependtry++;
+ sincenothing = 0;
+ if (dependtry >= DEPEND_TRY_LAST)
+ internerr("exceeded dependtry %d (sincenothing=%d, queue.length=%d)",
+ dependtry, sincenothing, queue.length);
+ }
+ }
+
+ debug(dbg_general, "process queue pkg %s queue.len %d progress %d, try %d",
+ pkg_name(pkg, pnaw_always), queue.length, sincenothing, dependtry);
+
+ if (pkg->status > PKG_STAT_INSTALLED)
+ internerr("package %s status %d is out-of-bounds",
+ pkg_name(pkg, pnaw_always), pkg->status);
+
+ if (setjmp(ejbuf)) {
+ /* Give up on it from the point of view of other packages, i.e. reset
+ * istobe. */
+ pkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+
+ pop_error_context(ehflag_bombout);
+ if (abort_processing)
+ return;
+ continue;
+ }
+ push_error_context_jump(&ejbuf, print_error_perpackage,
+ pkg_name(pkg, pnaw_nonambig));
+
+ switch (action_todo) {
+ case act_triggers:
+ if (!pkg->trigpend_head)
+ ohshit(_("package %.250s is not ready for trigger processing\n"
+ " (current status '%.250s' with no pending triggers)"),
+ pkg_name(pkg, pnaw_nonambig), pkg_status_name(pkg));
+ /* Fall through. */
+ case act_install:
+ /* Don't try to configure pkgs that we've just disappeared. */
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ break;
+ /* Fall through. */
+ case act_configure:
+ /* Do whatever is most needed. */
+ if (pkg->trigpend_head)
+ trigproc(pkg, TRIGPROC_TRY_QUEUED);
+ else
+ deferred_configure(pkg);
+ break;
+ case act_remove: case act_purge:
+ deferred_remove(pkg);
+ break;
+ default:
+ internerr("unknown action '%d'", cipaction->arg_int);
+ }
+ m_output(stdout, _("<standard output>"));
+ m_output(stderr, _("<standard error>"));
+
+ pop_error_context(ehflag_normaltidy);
+ }
+
+ if (queue.length)
+ internerr("finished package processing with non-empty queue length %d",
+ queue.length);
+}
+
+/*** Dependency processing - common to --configure and --remove. ***/
+
+/*
+ * The algorithm for deciding what to configure or remove first is as
+ * follows:
+ *
+ * Loop through all packages doing a ‘try 1’ until we've been round and
+ * nothing has been done, then do ‘try 2’ and ‘try 3’ likewise.
+ *
+ * When configuring, in each try we check to see whether all
+ * dependencies of this package are done. If so we do it. If some of
+ * the dependencies aren't done yet but will be later we defer the
+ * package, otherwise it is an error.
+ *
+ * When removing, in each try we check to see whether there are any
+ * packages that would have dependencies missing if we removed this
+ * one. If not we remove it now. If some of these packages are
+ * themselves scheduled for removal we defer the package until they
+ * have been done.
+ *
+ * The criteria for satisfying a dependency vary with the various
+ * tries. In try 1 we treat the dependencies as absolute. In try 2 we
+ * check break any cycles in the dependency graph involving the package
+ * we are trying to process before trying to process the package
+ * normally. In try 3 (which should only be reached if
+ * --force-depends-version is set) we ignore version number clauses in
+ * Depends lines. In try 4 (only reached if --force-depends is set) we
+ * say "ok" regardless.
+ *
+ * If we are configuring and one of the packages we depend on is
+ * awaiting configuration but wasn't specified in the argument list we
+ * will add it to the argument list if --configure-any is specified.
+ * In this case we note this as having "done something" so that we
+ * don't needlessly escalate to higher levels of dependency checking
+ * and breaking.
+ */
+
+enum found_status {
+ FOUND_NONE = 0,
+ FOUND_DEFER = 1,
+ FOUND_FORCED = 2,
+ FOUND_OK = 3,
+};
+
+static enum found_status
+found_forced_on(enum dependtry dependtry_forced)
+{
+ if (dependtry >= dependtry_forced)
+ return FOUND_FORCED;
+ else
+ return FOUND_DEFER;
+}
+
+/*
+ * Return values:
+ * 0: cannot be satisfied.
+ * 1: defer: may be satisfied later, when other packages are better or
+ * at higher dependtry due to --force
+ * will set *fixbytrig to package whose trigger processing would help
+ * if applicable (and leave it alone otherwise).
+ * 2: not satisfied but forcing
+ * (*interestingwarnings >= 0 on exit? caller is to print oemsgs).
+ * 3: satisfied now.
+ */
+static enum found_status
+deppossi_ok_found(struct pkginfo *possdependee, struct pkginfo *requiredby,
+ struct pkginfo *removing, struct deppossi *provider,
+ struct pkginfo **fixbytrig,
+ bool *matched, struct deppossi *checkversion,
+ int *interestingwarnings, struct varbuf *oemsgs)
+{
+ enum found_status thisf;
+
+ if (ignore_depends(possdependee)) {
+ debug(dbg_depcondetail," ignoring depended package so ok and found");
+ return FOUND_OK;
+ }
+ thisf = FOUND_NONE;
+ if (possdependee == removing) {
+ if (provider) {
+ varbuf_printf(oemsgs,
+ _(" Package %s which provides %s is to be removed.\n"),
+ pkg_name(possdependee, pnaw_nonambig),
+ provider->ed->name);
+ } else {
+ varbuf_printf(oemsgs, _(" Package %s is to be removed.\n"),
+ pkg_name(possdependee, pnaw_nonambig));
+ }
+
+ *matched = true;
+ debug(dbg_depcondetail," removing possdependee, returning %d",thisf);
+ return thisf;
+ }
+ switch (possdependee->status) {
+ case PKG_STAT_UNPACKED:
+ case PKG_STAT_HALFCONFIGURED:
+ case PKG_STAT_TRIGGERSAWAITED:
+ case PKG_STAT_TRIGGERSPENDING:
+ case PKG_STAT_INSTALLED:
+ if (checkversion) {
+ if (provider) {
+ debug(dbg_depcondetail, " checking package %s provided by pkg %s",
+ checkversion->ed->name, pkg_name(possdependee, pnaw_always));
+ if (!pkg_virtual_deppossi_satisfied(checkversion, provider)) {
+ varbuf_printf(oemsgs,
+ _(" Version of %s on system, provided by %s, is %s.\n"),
+ checkversion->ed->name,
+ pkg_name(possdependee, pnaw_always),
+ versiondescribe(&provider->version, vdew_nonambig));
+ if (in_force(FORCE_DEPENDS_VERSION))
+ thisf = found_forced_on(DEPEND_TRY_FORCE_DEPENDS_VERSION);
+ debug(dbg_depcondetail, " bad version");
+ goto unsuitable;
+ }
+ } else {
+ debug(dbg_depcondetail, " checking non-provided pkg %s",
+ pkg_name(possdependee, pnaw_always));
+ if (!versionsatisfied(&possdependee->installed, checkversion)) {
+ varbuf_printf(oemsgs, _(" Version of %s on system is %s.\n"),
+ pkg_name(possdependee, pnaw_nonambig),
+ versiondescribe(&possdependee->installed.version,
+ vdew_nonambig));
+ if (in_force(FORCE_DEPENDS_VERSION))
+ thisf = found_forced_on(DEPEND_TRY_FORCE_DEPENDS_VERSION);
+ debug(dbg_depcondetail, " bad version");
+ goto unsuitable;
+ }
+ }
+ }
+ if (possdependee->status == PKG_STAT_INSTALLED ||
+ possdependee->status == PKG_STAT_TRIGGERSPENDING) {
+ debug(dbg_depcondetail," is installed, ok and found");
+ return FOUND_OK;
+ }
+ if (possdependee->status == PKG_STAT_TRIGGERSAWAITED) {
+ if (possdependee->trigaw.head == NULL)
+ internerr("package %s in state %s, has no awaited triggers",
+ pkg_name(possdependee, pnaw_always),
+ pkg_status_name(possdependee));
+
+ if (removing ||
+ !(f_triggers ||
+ (possdependee->clientdata &&
+ possdependee->clientdata->istobe == PKG_ISTOBE_INSTALLNEW))) {
+ if (provider) {
+ varbuf_printf(oemsgs,
+ _(" Package %s which provides %s awaits trigger processing.\n"),
+ pkg_name(possdependee, pnaw_nonambig),
+ provider->ed->name);
+ } else {
+ varbuf_printf(oemsgs,
+ _(" Package %s awaits trigger processing.\n"),
+ pkg_name(possdependee, pnaw_nonambig));
+ }
+ debug(dbg_depcondetail, " triggers-awaited, no fixbytrig");
+ goto unsuitable;
+ }
+ /* We don't check the status of trigaw.head->pend here, just in case
+ * we get into the pathological situation where Triggers-Awaited but
+ * the named package doesn't actually have any pending triggers. In
+ * that case we queue the non-pending package for trigger processing
+ * anyway, and that trigger processing will be a noop except for
+ * sorting out all of the packages which name it in Triggers-Awaited.
+ *
+ * (This situation can only arise if modstatdb_note success in
+ * clearing the triggers-pending status of the pending package
+ * but then fails to go on to update the awaiters.) */
+ *fixbytrig = possdependee->trigaw.head->pend;
+ debug(dbg_depcondetail,
+ " triggers-awaited, fixbytrig '%s', defer",
+ pkg_name(*fixbytrig, pnaw_always));
+ return FOUND_DEFER;
+ }
+ if (possdependee->clientdata &&
+ possdependee->clientdata->istobe == PKG_ISTOBE_INSTALLNEW) {
+ debug(dbg_depcondetail," unpacked/halfconfigured, defer");
+ return FOUND_DEFER;
+ } else if (!removing && in_force(FORCE_CONFIGURE_ANY) &&
+ !skip_due_to_hold(possdependee) &&
+ !(possdependee->status == PKG_STAT_HALFCONFIGURED)) {
+ notice(_("also configuring '%s' (required by '%s')"),
+ pkg_name(possdependee, pnaw_nonambig),
+ pkg_name(requiredby, pnaw_nonambig));
+ enqueue_package(possdependee);
+ sincenothing = 0;
+ return FOUND_DEFER;
+ } else {
+ if (provider) {
+ varbuf_printf(oemsgs,
+ _(" Package %s which provides %s is not configured yet.\n"),
+ pkg_name(possdependee, pnaw_nonambig),
+ provider->ed->name);
+ } else {
+ varbuf_printf(oemsgs, _(" Package %s is not configured yet.\n"),
+ pkg_name(possdependee, pnaw_nonambig));
+ }
+
+ debug(dbg_depcondetail, " not configured/able");
+ goto unsuitable;
+ }
+
+ default:
+ if (provider) {
+ varbuf_printf(oemsgs,
+ _(" Package %s which provides %s is not installed.\n"),
+ pkg_name(possdependee, pnaw_nonambig),
+ provider->ed->name);
+ } else {
+ varbuf_printf(oemsgs, _(" Package %s is not installed.\n"),
+ pkg_name(possdependee, pnaw_nonambig));
+ }
+
+ debug(dbg_depcondetail, " not installed");
+ goto unsuitable;
+ }
+
+unsuitable:
+ debug(dbg_depcondetail, " returning %d", thisf);
+ (*interestingwarnings)++;
+
+ return thisf;
+}
+
+static void
+breaks_check_one(struct varbuf *aemsgs, enum dep_check *ok,
+ struct deppossi *breaks, struct pkginfo *broken,
+ struct pkginfo *breaker, struct deppossi *virtbroken)
+{
+ struct varbuf depmsg = VARBUF_INIT;
+
+ debug(dbg_depcondetail, " checking breaker %s virtbroken %s",
+ pkg_name(breaker, pnaw_always),
+ virtbroken ? virtbroken->ed->name : "<none>");
+
+ if (breaker->status == PKG_STAT_NOTINSTALLED ||
+ breaker->status == PKG_STAT_CONFIGFILES)
+ return;
+ if (broken == breaker) return;
+ if (!versionsatisfied(&broken->installed, breaks)) return;
+ /* The test below can only trigger if dep_breaks start having
+ * arch qualifiers different from “any”. */
+ if (!archsatisfied(&broken->installed, breaks))
+ return;
+ if (ignore_depends(breaker)) return;
+ if (virtbroken && ignore_depends(&virtbroken->ed->pkg))
+ return;
+ if (virtbroken && !pkg_virtual_deppossi_satisfied(breaks, virtbroken))
+ return;
+
+ varbufdependency(&depmsg, breaks->up);
+ varbuf_end_str(&depmsg);
+ varbuf_printf(aemsgs, _(" %s (%s) breaks %s and is %s.\n"),
+ pkg_name(breaker, pnaw_nonambig),
+ versiondescribe(&breaker->installed.version, vdew_nonambig),
+ depmsg.buf, gettext(statusstrings[breaker->status]));
+ varbuf_destroy(&depmsg);
+
+ if (virtbroken) {
+ varbuf_printf(aemsgs, _(" %s (%s) provides %s.\n"),
+ pkg_name(broken, pnaw_nonambig),
+ versiondescribe(&broken->installed.version, vdew_nonambig),
+ virtbroken->ed->name);
+ } else if (breaks->verrel != DPKG_RELATION_NONE) {
+ varbuf_printf(aemsgs, _(" Version of %s to be configured is %s.\n"),
+ pkg_name(broken, pnaw_nonambig),
+ versiondescribe(&broken->installed.version, vdew_nonambig));
+ if (in_force(FORCE_DEPENDS_VERSION))
+ return;
+ }
+ if (force_breaks(breaks)) return;
+ *ok = DEP_CHECK_HALT;
+}
+
+static void
+breaks_check_target(struct varbuf *aemsgs, enum dep_check *ok,
+ struct pkginfo *broken, struct pkgset *target,
+ struct deppossi *virtbroken)
+{
+ struct deppossi *possi;
+
+ for (possi = target->depended.installed; possi; possi = possi->rev_next) {
+ if (possi->up->type != dep_breaks) continue;
+ breaks_check_one(aemsgs, ok, possi, broken, possi->up->up, virtbroken);
+ }
+}
+
+enum dep_check
+breakses_ok(struct pkginfo *pkg, struct varbuf *aemsgs)
+{
+ struct dependency *dep;
+ struct deppossi *virtbroken;
+ enum dep_check ok = DEP_CHECK_OK;
+
+ debug(dbg_depcon, " checking Breaks");
+
+ breaks_check_target(aemsgs, &ok, pkg, pkg->set, NULL);
+
+ for (dep= pkg->installed.depends; dep; dep= dep->next) {
+ if (dep->type != dep_provides) continue;
+ virtbroken = dep->list;
+ debug(dbg_depcondetail, " checking virtbroken %s", virtbroken->ed->name);
+ breaks_check_target(aemsgs, &ok, pkg, virtbroken->ed, virtbroken);
+ }
+ return ok;
+}
+
+/*
+ * Checks [Pre]-Depends only.
+ */
+enum dep_check
+dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
+ struct varbuf *aemsgs)
+{
+ /* Valid values: 2 = ok, 1 = defer, 0 = halt. */
+ enum dep_check ok;
+ /* Valid values: 0 = none, 1 = defer, 2 = withwarning, 3 = ok. */
+ enum found_status found, thisf;
+ int interestingwarnings;
+ bool matched, anycannotfixbytrig;
+ struct varbuf oemsgs = VARBUF_INIT;
+ struct dependency *dep;
+ struct deppossi *possi, *provider;
+ struct pkginfo *possfixbytrig, *canfixbytrig;
+
+ ok = DEP_CHECK_OK;
+ debug(dbg_depcon,"checking dependencies of %s (- %s)",
+ pkg_name(pkg, pnaw_always),
+ removing ? pkg_name(removing, pnaw_always) : "<none>");
+
+ anycannotfixbytrig = false;
+ canfixbytrig = NULL;
+ for (dep= pkg->installed.depends; dep; dep= dep->next) {
+ if (dep->type != dep_depends && dep->type != dep_predepends) continue;
+ debug(dbg_depcondetail," checking group ...");
+ matched = false;
+ interestingwarnings = 0;
+ varbuf_reset(&oemsgs);
+ found = FOUND_NONE;
+ possfixbytrig = NULL;
+ for (possi = dep->list; found != FOUND_OK && possi; possi = possi->next) {
+ struct deppossi_pkg_iterator *possi_iter;
+ struct pkginfo *pkg_pos;
+
+ debug(dbg_depcondetail," checking possibility -> %s",possi->ed->name);
+ if (possi->cyclebreak) {
+ debug(dbg_depcondetail," break cycle so ok and found");
+ found = FOUND_OK;
+ break;
+ }
+
+ thisf = FOUND_NONE;
+ possi_iter = deppossi_pkg_iter_new(possi, wpb_installed);
+ while ((pkg_pos = deppossi_pkg_iter_next(possi_iter))) {
+ thisf = deppossi_ok_found(pkg_pos, pkg, removing, NULL,
+ &possfixbytrig, &matched, possi,
+ &interestingwarnings, &oemsgs);
+ if (thisf > found)
+ found = thisf;
+ if (found == FOUND_OK)
+ break;
+ }
+ deppossi_pkg_iter_free(possi_iter);
+
+ if (found != FOUND_OK) {
+ for (provider = possi->ed->depended.installed;
+ found != FOUND_OK && provider;
+ provider = provider->rev_next) {
+ if (provider->up->type != dep_provides)
+ continue;
+ debug(dbg_depcondetail, " checking provider %s",
+ pkg_name(provider->up->up, pnaw_always));
+ if (!deparchsatisfied(&provider->up->up->installed, provider->arch,
+ possi)) {
+ debug(dbg_depcondetail, " provider does not satisfy arch");
+ continue;
+ }
+ thisf = deppossi_ok_found(provider->up->up, pkg, removing, provider,
+ &possfixbytrig, &matched, possi,
+ &interestingwarnings, &oemsgs);
+ if (thisf == FOUND_DEFER && provider->up->up == pkg && !removing) {
+ /* IOW, if the pkg satisfies its own dep (via a provide), then
+ * we let it pass, even if it isn't configured yet (as we're
+ * installing it). */
+ thisf = FOUND_OK;
+ }
+ if (thisf > found)
+ found = thisf;
+ }
+ }
+ debug(dbg_depcondetail," found %d",found);
+ if (thisf > found) found= thisf;
+ }
+ if (in_force(FORCE_DEPENDS)) {
+ thisf = found_forced_on(DEPEND_TRY_FORCE_DEPENDS);
+ if (thisf > found) {
+ found = thisf;
+ debug(dbg_depcondetail, " rescued by force-depends, found %d", found);
+ }
+ }
+ debug(dbg_depcondetail, " found %d matched %d possfixbytrig %s",
+ found, matched,
+ possfixbytrig ? pkg_name(possfixbytrig, pnaw_always) : "-");
+ if (removing && !matched) continue;
+ switch (found) {
+ case FOUND_NONE:
+ anycannotfixbytrig = true;
+ ok = DEP_CHECK_HALT;
+ /* Fall through. */
+ case FOUND_FORCED:
+ varbuf_add_str(aemsgs, " ");
+ varbuf_add_pkgbin_name(aemsgs, pkg, &pkg->installed, pnaw_nonambig);
+ varbuf_add_str(aemsgs, _(" depends on "));
+ varbufdependency(aemsgs, dep);
+ if (interestingwarnings) {
+ /* Don't print the line about the package to be removed if
+ * that's the only line. */
+ varbuf_end_str(&oemsgs);
+ varbuf_add_str(aemsgs, _("; however:\n"));
+ varbuf_add_varbuf(aemsgs, &oemsgs);
+ } else {
+ varbuf_add_str(aemsgs, ".\n");
+ }
+ break;
+ case FOUND_DEFER:
+ if (possfixbytrig)
+ canfixbytrig = possfixbytrig;
+ else
+ anycannotfixbytrig = true;
+ if (ok > DEP_CHECK_DEFER)
+ ok = DEP_CHECK_DEFER;
+ break;
+ case FOUND_OK:
+ break;
+ default:
+ internerr("unknown value for found '%d'", found);
+ }
+ }
+ if (ok == DEP_CHECK_HALT &&
+ (pkg->clientdata && pkg->clientdata->istobe == PKG_ISTOBE_REMOVE))
+ ok = DEP_CHECK_DEFER;
+ if (!anycannotfixbytrig && canfixbytrig)
+ progress_bytrigproc = canfixbytrig;
+
+ varbuf_destroy(&oemsgs);
+ debug(dbg_depcon,"ok %d msgs >>%.*s<<", ok, (int)aemsgs->used, aemsgs->buf);
+ return ok;
+}
diff --git a/src/main/perpkgstate.c b/src/main/perpkgstate.c
new file mode 100644
index 0000000..7542112
--- /dev/null
+++ b/src/main/perpkgstate.c
@@ -0,0 +1,43 @@
+/*
+ * dpkg - main program for package management
+ * perpkgstate.c - struct perpackagestate and function handling
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2000,2001 Wichert Akkerman <wakkerma@debian.org>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+
+#include "main.h"
+
+void
+ensure_package_clientdata(struct pkginfo *pkg)
+{
+ if (pkg->clientdata)
+ return;
+ pkg->clientdata = nfmalloc(sizeof(*pkg->clientdata));
+ pkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ pkg->clientdata->color = PKG_CYCLE_WHITE;
+ pkg->clientdata->enqueued = false;
+ pkg->clientdata->replacingfilesandsaid = 0;
+ pkg->clientdata->cmdline_seen = 0;
+ pkg->clientdata->trigprocdeferred = NULL;
+}
diff --git a/src/main/remove.c b/src/main/remove.c
new file mode 100644
index 0000000..88c01a2
--- /dev/null
+++ b/src/main/remove.c
@@ -0,0 +1,686 @@
+/*
+ * dpkg - main program for package management
+ * remove.c - functionality for removing packages
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2007-2015 Guillem Jover <guillem@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/path.h>
+#include <dpkg/dir.h>
+#include <dpkg/options.h>
+#include <dpkg/triglib.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+/*
+ * pkgdepcheck may be a virtual pkg.
+ */
+static void checkforremoval(struct pkginfo *pkgtoremove,
+ struct pkgset *pkgdepcheck,
+ enum dep_check *rokp, struct varbuf *raemsgs)
+{
+ struct deppossi *possi;
+ struct pkginfo *depender;
+ enum dep_check ok;
+ struct varbuf_state raemsgs_state;
+
+ for (possi = pkgdepcheck->depended.installed; possi; possi = possi->rev_next) {
+ if (possi->up->type != dep_depends && possi->up->type != dep_predepends) continue;
+ depender= possi->up->up;
+ debug(dbg_depcon, "checking depending package '%s'",
+ pkg_name(depender, pnaw_always));
+ if (depender->status < PKG_STAT_UNPACKED)
+ continue;
+ if (ignore_depends(depender)) {
+ debug(dbg_depcon, "ignoring depending package '%s'",
+ pkg_name(depender, pnaw_always));
+ continue;
+ }
+ if (dependtry >= DEPEND_TRY_CYCLES) {
+ if (findbreakcycle(pkgtoremove))
+ sincenothing = 0;
+ }
+ varbuf_snapshot(raemsgs, &raemsgs_state);
+ ok= dependencies_ok(depender,pkgtoremove,raemsgs);
+ if (ok == DEP_CHECK_HALT &&
+ depender->clientdata &&
+ depender->clientdata->istobe == PKG_ISTOBE_REMOVE)
+ ok = DEP_CHECK_DEFER;
+ if (ok == DEP_CHECK_DEFER)
+ /* Don't burble about reasons for deferral. */
+ varbuf_rollback(&raemsgs_state);
+ if (ok < *rokp) *rokp= ok;
+ }
+}
+
+void deferred_remove(struct pkginfo *pkg) {
+ struct varbuf raemsgs = VARBUF_INIT;
+ struct dependency *dep;
+ enum dep_check rok;
+
+ debug(dbg_general, "deferred_remove package %s",
+ pkg_name(pkg, pnaw_always));
+
+ if (!f_pending && pkg->want != PKG_WANT_UNKNOWN) {
+ if (cipaction->arg_int == act_purge)
+ pkg_set_want(pkg, PKG_WANT_PURGE);
+ else
+ pkg_set_want(pkg, PKG_WANT_DEINSTALL);
+
+ if (!f_noact)
+ modstatdb_note(pkg);
+ }
+
+ ensure_package_clientdata(pkg);
+
+ if (pkg->status == PKG_STAT_NOTINSTALLED) {
+ sincenothing = 0;
+ warning(_("ignoring request to remove %.250s which isn't installed"),
+ pkg_name(pkg, pnaw_nonambig));
+ pkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ return;
+ } else if (!f_pending &&
+ pkg->status == PKG_STAT_CONFIGFILES &&
+ cipaction->arg_int != act_purge) {
+ sincenothing = 0;
+ warning(_("ignoring request to remove %.250s, only the config\n"
+ " files of which are on the system; use --purge to remove them too"),
+ pkg_name(pkg, pnaw_nonambig));
+ pkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ return;
+ }
+
+ if (pkg->status != PKG_STAT_CONFIGFILES) {
+ if (pkg->installed.essential)
+ forcibleerr(FORCE_REMOVE_ESSENTIAL,
+ _("this is an essential package; it should not be removed"));
+ if (pkg->installed.is_protected)
+ forcibleerr(FORCE_REMOVE_PROTECTED,
+ _("this is a protected package; it should not be removed"));
+ }
+
+ debug(dbg_general, "checking dependencies for remove '%s'",
+ pkg_name(pkg, pnaw_always));
+ rok = DEP_CHECK_OK;
+ checkforremoval(pkg, pkg->set, &rok, &raemsgs);
+ for (dep= pkg->installed.depends; dep; dep= dep->next) {
+ if (dep->type != dep_provides) continue;
+ debug(dbg_depcon, "checking virtual package '%s'", dep->list->ed->name);
+ checkforremoval(pkg, dep->list->ed, &rok, &raemsgs);
+ }
+
+ if (rok == DEP_CHECK_DEFER) {
+ varbuf_destroy(&raemsgs);
+ pkg->clientdata->istobe = PKG_ISTOBE_REMOVE;
+ enqueue_package(pkg);
+ return;
+ } else if (rok == DEP_CHECK_HALT) {
+ sincenothing= 0;
+ varbuf_end_str(&raemsgs);
+ notice(_("dependency problems prevent removal of %s:\n%s"),
+ pkg_name(pkg, pnaw_nonambig), raemsgs.buf);
+ ohshit(_("dependency problems - not removing"));
+ } else if (raemsgs.used) {
+ varbuf_end_str(&raemsgs);
+ notice(_("%s: dependency problems, but removing anyway as you requested:\n%s"),
+ pkg_name(pkg, pnaw_nonambig), raemsgs.buf);
+ }
+ varbuf_destroy(&raemsgs);
+ 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 a removal"));
+
+ ensure_allinstfiles_available();
+ fsys_hash_init();
+
+ if (f_noact) {
+ printf(_("Would remove or purge %s (%s) ...\n"),
+ pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig));
+ pkg_set_status(pkg, PKG_STAT_NOTINSTALLED);
+ pkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ return;
+ }
+
+ pkg_conffiles_mark_old(pkg);
+
+ /* Only print and log removal action once. This avoids duplication when
+ * using --remove and --purge in sequence. */
+ if (pkg->status > PKG_STAT_CONFIGFILES) {
+ printf(_("Removing %s (%s) ...\n"), pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig));
+ log_action("remove", pkg, &pkg->installed);
+ }
+
+ trig_activate_packageprocessing(pkg);
+ if (pkg->status >= PKG_STAT_HALFCONFIGURED) {
+ static enum pkgstatus oldpkgstatus;
+
+ oldpkgstatus= pkg->status;
+ pkg_set_status(pkg, PKG_STAT_HALFCONFIGURED);
+ modstatdb_note(pkg);
+ push_cleanup(cu_prermremove, ~ehflag_normaltidy, 2,
+ (void *)pkg, (void *)&oldpkgstatus);
+ maintscript_installed(pkg, PRERMFILE, "pre-removal", "remove", NULL);
+
+ /* Will turn into ‘half-installed’ soon ... */
+ pkg_set_status(pkg, PKG_STAT_UNPACKED);
+ }
+
+ removal_bulk(pkg);
+}
+
+static void
+push_leftover(struct fsys_namenode_list **leftoverp,
+ struct fsys_namenode *namenode)
+{
+ struct fsys_namenode_list *newentry;
+
+ newentry = nfmalloc(sizeof(*newentry));
+ newentry->next= *leftoverp;
+ newentry->namenode= namenode;
+ *leftoverp= newentry;
+}
+
+static void
+removal_bulk_remove_file(const char *filename, const char *filetype)
+{
+ /* We need the postrm and list files for --purge. */
+ if (strcmp(filetype, LISTFILE) == 0 ||
+ strcmp(filetype, POSTRMFILE) == 0)
+ return;
+
+ debug(dbg_stupidlyverbose, "removal_bulk info not postrm or list");
+
+ if (unlink(filename))
+ ohshite(_("unable to delete control info file '%.250s'"), filename);
+
+ debug(dbg_scripts, "removal_bulk info unlinked %s", filename);
+}
+
+static bool
+removal_bulk_file_is_shared(struct pkginfo *pkg, struct fsys_namenode *namenode)
+{
+ struct fsys_node_pkgs_iter *iter;
+ struct pkginfo *otherpkg;
+ bool shared = false;
+
+ if (pkgset_installed_instances(pkg->set) <= 1)
+ return false;
+
+ iter = fsys_node_pkgs_iter_new(namenode);
+ while ((otherpkg = fsys_node_pkgs_iter_next(iter))) {
+ if (otherpkg == pkg)
+ continue;
+ if (otherpkg->set != pkg->set)
+ continue;
+
+ debug(dbg_eachfiledetail, "removal_bulk file shared with %s, skipping",
+ pkg_name(otherpkg, pnaw_always));
+ shared = true;
+ break;
+ }
+ fsys_node_pkgs_iter_free(iter);
+
+ return shared;
+}
+
+static void
+removal_bulk_remove_files(struct pkginfo *pkg)
+{
+ struct fsys_hash_rev_iter rev_iter;
+ struct fsys_namenode_list *leftover;
+ struct fsys_namenode *namenode;
+ static struct varbuf fnvb;
+ struct varbuf_state fnvb_state;
+ struct stat stab;
+
+ pkg_set_status(pkg, PKG_STAT_HALFINSTALLED);
+ modstatdb_note(pkg);
+ push_checkpoint(~ehflag_bombout, ehflag_normaltidy);
+
+ fsys_hash_rev_iter_init(&rev_iter, pkg->files);
+ leftover = NULL;
+ while ((namenode = fsys_hash_rev_iter_next(&rev_iter))) {
+ struct fsys_namenode *usenode;
+ bool is_dir;
+
+ debug(dbg_eachfile, "removal_bulk '%s' flags=%o",
+ namenode->name, namenode->flags);
+
+ usenode = namenodetouse(namenode, pkg, &pkg->installed);
+
+ varbuf_set_str(&fnvb, dpkg_fsys_get_dir());
+ varbuf_add_str(&fnvb, usenode->name);
+ varbuf_end_str(&fnvb);
+ varbuf_snapshot(&fnvb, &fnvb_state);
+
+ is_dir = stat(fnvb.buf, &stab) == 0 && S_ISDIR(stab.st_mode);
+
+ /* A pkgset can share files between its instances that we
+ * don't want to remove, we just want to forget them. This
+ * applies to shared conffiles too. */
+ if (!is_dir && removal_bulk_file_is_shared(pkg, namenode))
+ continue;
+
+ /* Non-shared conffiles are kept. */
+ if (namenode->flags & FNNF_OLD_CONFF) {
+ push_leftover(&leftover, namenode);
+ continue;
+ }
+
+ if (is_dir) {
+ debug(dbg_eachfiledetail, "removal_bulk is a directory");
+ /* Only delete a directory or a link to one if we're the only
+ * package which uses it. Other files should only be listed
+ * in this package (but we don't check). */
+ if (dir_has_conffiles(namenode, pkg)) {
+ push_leftover(&leftover,namenode);
+ continue;
+ }
+ if (dir_is_used_by_pkg(namenode, pkg, leftover)) {
+ push_leftover(&leftover, namenode);
+ continue;
+ }
+ if (dir_is_used_by_others(namenode, pkg))
+ continue;
+
+ if (strcmp(usenode->name, "/.") == 0) {
+ debug(dbg_eachfiledetail,
+ "removal_bulk '%s' root directory, cannot remove", fnvb.buf);
+ push_leftover(&leftover, namenode);
+ continue;
+ }
+ }
+
+ trig_path_activate(usenode, pkg);
+
+ varbuf_rollback(&fnvb_state);
+ varbuf_add_str(&fnvb, DPKGTEMPEXT);
+ varbuf_end_str(&fnvb);
+ debug(dbg_eachfiledetail, "removal_bulk cleaning temp '%s'", fnvb.buf);
+ path_remove_tree(fnvb.buf);
+
+ varbuf_rollback(&fnvb_state);
+ varbuf_add_str(&fnvb, DPKGNEWEXT);
+ varbuf_end_str(&fnvb);
+ debug(dbg_eachfiledetail, "removal_bulk cleaning new '%s'", fnvb.buf);
+ path_remove_tree(fnvb.buf);
+
+ varbuf_rollback(&fnvb_state);
+ varbuf_end_str(&fnvb);
+
+ debug(dbg_eachfiledetail, "removal_bulk removing '%s'", fnvb.buf);
+ if (!rmdir(fnvb.buf) || errno == ENOENT || errno == ELOOP) continue;
+ if (errno == ENOTEMPTY || errno == EEXIST) {
+ debug(dbg_eachfiledetail,
+ "removal_bulk '%s' was not empty, will try again later",
+ fnvb.buf);
+ push_leftover(&leftover,namenode);
+ continue;
+ } else if (errno == EBUSY || errno == EPERM) {
+ warning(_("while removing %.250s, unable to remove directory '%.250s': "
+ "%s - directory may be a mount point?"),
+ pkg_name(pkg, pnaw_nonambig), namenode->name, strerror(errno));
+ push_leftover(&leftover,namenode);
+ continue;
+ }
+ if (errno != ENOTDIR)
+ ohshite(_("cannot remove '%.250s'"), fnvb.buf);
+ debug(dbg_eachfiledetail, "removal_bulk unlinking '%s'", fnvb.buf);
+ if (secure_unlink(fnvb.buf))
+ ohshite(_("unable to securely remove '%.250s'"), fnvb.buf);
+ }
+ write_filelist_except(pkg, &pkg->installed, leftover, 0);
+ maintscript_installed(pkg, POSTRMFILE, "post-removal", "remove", NULL);
+
+ trig_parse_ci(pkg_infodb_get_file(pkg, &pkg->installed, TRIGGERSCIFILE),
+ trig_cicb_interest_delete, NULL, pkg, &pkg->installed);
+ trig_file_interests_save();
+
+ debug(dbg_general, "removal_bulk cleaning info directory");
+ pkg_infodb_foreach(pkg, &pkg->installed, removal_bulk_remove_file);
+ dir_sync_path(pkg_infodb_get_dir());
+
+ pkg_set_status(pkg, PKG_STAT_CONFIGFILES);
+ pkg->installed.essential = false;
+ pkg->installed.is_protected = false;
+ modstatdb_note(pkg);
+ push_checkpoint(~ehflag_bombout, ehflag_normaltidy);
+}
+
+static void removal_bulk_remove_leftover_dirs(struct pkginfo *pkg) {
+ struct fsys_hash_rev_iter rev_iter;
+ struct fsys_namenode_list *leftover;
+ struct fsys_namenode *namenode;
+ static struct varbuf fnvb;
+ struct stat stab;
+
+ /* We may have modified this previously. */
+ ensure_packagefiles_available(pkg);
+
+ modstatdb_note(pkg);
+ push_checkpoint(~ehflag_bombout, ehflag_normaltidy);
+
+ fsys_hash_rev_iter_init(&rev_iter, pkg->files);
+ leftover = NULL;
+ while ((namenode = fsys_hash_rev_iter_next(&rev_iter))) {
+ struct fsys_namenode *usenode;
+
+ debug(dbg_eachfile, "removal_bulk '%s' flags=%o",
+ namenode->name, namenode->flags);
+ if (namenode->flags & FNNF_OLD_CONFF) {
+ /* This can only happen if removal_bulk_remove_configfiles() got
+ * interrupted half way. */
+ debug(dbg_eachfiledetail, "removal_bulk expecting only left over dirs, "
+ "ignoring conffile '%s'", namenode->name);
+ continue;
+ }
+
+ usenode = namenodetouse(namenode, pkg, &pkg->installed);
+
+ varbuf_set_str(&fnvb, dpkg_fsys_get_dir());
+ varbuf_add_str(&fnvb, usenode->name);
+ varbuf_end_str(&fnvb);
+
+ if (!stat(fnvb.buf,&stab) && S_ISDIR(stab.st_mode)) {
+ debug(dbg_eachfiledetail, "removal_bulk is a directory");
+ /* Only delete a directory or a link to one if we're the only
+ * package which uses it. Other files should only be listed
+ * in this package (but we don't check). */
+ if (dir_is_used_by_pkg(namenode, pkg, leftover)) {
+ push_leftover(&leftover, namenode);
+ continue;
+ }
+ if (dir_is_used_by_others(namenode, pkg))
+ continue;
+
+ if (strcmp(usenode->name, "/.") == 0) {
+ debug(dbg_eachfiledetail,
+ "removal_bulk '%s' root directory, cannot remove", fnvb.buf);
+ push_leftover(&leftover, namenode);
+ continue;
+ }
+ }
+
+ trig_path_activate(usenode, pkg);
+
+ debug(dbg_eachfiledetail, "removal_bulk removing '%s'", fnvb.buf);
+ if (!rmdir(fnvb.buf) || errno == ENOENT || errno == ELOOP) continue;
+ if (errno == ENOTEMPTY || errno == EEXIST) {
+ warning(_("while removing %.250s, directory '%.250s' not empty so not removed"),
+ pkg_name(pkg, pnaw_nonambig), namenode->name);
+ push_leftover(&leftover,namenode);
+ continue;
+ } else if (errno == EBUSY || errno == EPERM) {
+ warning(_("while removing %.250s, unable to remove directory '%.250s': "
+ "%s - directory may be a mount point?"),
+ pkg_name(pkg, pnaw_nonambig), namenode->name, strerror(errno));
+ push_leftover(&leftover,namenode);
+ continue;
+ }
+ if (errno != ENOTDIR)
+ ohshite(_("cannot remove '%.250s'"), fnvb.buf);
+
+ if (lstat(fnvb.buf, &stab) == 0 && S_ISLNK(stab.st_mode)) {
+ debug(dbg_eachfiledetail, "removal_bulk is a symlink to a directory");
+
+ if (unlink(fnvb.buf))
+ ohshite(_("cannot remove '%.250s'"), fnvb.buf);
+
+ continue;
+ }
+
+ push_leftover(&leftover,namenode);
+ }
+ write_filelist_except(pkg, &pkg->installed, leftover, 0);
+
+ modstatdb_note(pkg);
+ push_checkpoint(~ehflag_bombout, ehflag_normaltidy);
+}
+
+static void removal_bulk_remove_configfiles(struct pkginfo *pkg) {
+ static const char *const removeconffexts[] = { REMOVECONFFEXTS, NULL };
+ int rc;
+ int conffnameused, conffbasenamelen;
+ char *conffbasename;
+ struct conffile *conff, **lconffp;
+ struct fsys_namenode_list *searchfile;
+ DIR *dsd;
+ struct dirent *de;
+ char *p;
+ const char *const *ext;
+
+ printf(_("Purging configuration files for %s (%s) ...\n"),
+ pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig));
+ log_action("purge", pkg, &pkg->installed);
+ trig_activate_packageprocessing(pkg);
+
+ /* We may have modified this above. */
+ ensure_packagefiles_available(pkg);
+
+ /* We're about to remove the configuration, so remove the note
+ * about which version it was ... */
+ dpkg_version_blank(&pkg->configversion);
+ modstatdb_note(pkg);
+
+ /* Remove from our list any conffiles that aren't ours any more or
+ * are involved in diversions, except if we are the package doing the
+ * diverting. */
+ for (lconffp = &pkg->installed.conffiles; (conff = *lconffp) != NULL; ) {
+ for (searchfile = pkg->files;
+ searchfile && strcmp(searchfile->namenode->name,conff->name);
+ searchfile= searchfile->next);
+ if (!searchfile) {
+ debug(dbg_conff, "removal_bulk conffile not ours any more '%s'",
+ conff->name);
+ *lconffp= conff->next;
+ } else if (searchfile->namenode->divert &&
+ (searchfile->namenode->divert->camefrom ||
+ (searchfile->namenode->divert->useinstead &&
+ searchfile->namenode->divert->pkgset != pkg->set))) {
+ debug(dbg_conff, "removal_bulk conffile '%s' ignored due to diversion",
+ conff->name);
+ *lconffp= conff->next;
+ } else {
+ debug(dbg_conffdetail, "removal_bulk set to new conffile '%s'",
+ conff->name);
+ conff->hash = NEWCONFFILEFLAG;
+ lconffp= &conff->next;
+ }
+ }
+ modstatdb_note(pkg);
+
+ for (conff= pkg->installed.conffiles; conff; conff= conff->next) {
+ struct fsys_namenode *namenode, *usenode;
+ static struct varbuf fnvb, removevb;
+ struct varbuf_state removevb_state;
+
+ if (conff->obsolete) {
+ debug(dbg_conffdetail, "removal_bulk conffile obsolete %s",
+ conff->name);
+ }
+ varbuf_reset(&fnvb);
+ rc = conffderef(pkg, &fnvb, conff->name);
+ debug(dbg_conffdetail, "removal_bulk conffile '%s' (= '%s')",
+ conff->name, rc == -1 ? "<rc == -1>" : fnvb.buf);
+ if (rc == -1)
+ continue;
+
+ namenode = fsys_hash_find_node(conff->name, FHFF_NONE);
+ usenode = namenodetouse(namenode, pkg, &pkg->installed);
+
+ trig_path_activate(usenode, pkg);
+
+ conffnameused = fnvb.used;
+ if (unlink(fnvb.buf) && errno != ENOENT && errno != ENOTDIR)
+ ohshite(_("cannot remove old config file '%.250s' (= '%.250s')"),
+ conff->name, fnvb.buf);
+ p= strrchr(fnvb.buf,'/'); if (!p) continue;
+ *p = '\0';
+ varbuf_reset(&removevb);
+ varbuf_add_dir(&removevb, fnvb.buf);
+ varbuf_end_str(&removevb);
+ varbuf_snapshot(&removevb, &removevb_state);
+
+ dsd= opendir(removevb.buf);
+ if (!dsd) {
+ int e=errno;
+ debug(dbg_conffdetail, "removal_bulk conffile no dsd %s %s",
+ fnvb.buf, strerror(e)); errno= e;
+ if (errno == ENOENT || errno == ENOTDIR) continue;
+ ohshite(_("cannot read config file directory '%.250s' (from '%.250s')"),
+ fnvb.buf, conff->name);
+ }
+ debug(dbg_conffdetail, "removal_bulk conffile cleaning dsd %s", fnvb.buf);
+ push_cleanup(cu_closedir, ~0, 1, (void *)dsd);
+ *p= '/';
+ conffbasenamelen= strlen(++p);
+ conffbasename= fnvb.buf+conffnameused-conffbasenamelen;
+ while ((de = readdir(dsd)) != NULL) {
+ debug(dbg_stupidlyverbose, "removal_bulk conffile dsd entry='%s'"
+ " conffbasename='%s' conffnameused=%d conffbasenamelen=%d",
+ de->d_name, conffbasename, conffnameused, conffbasenamelen);
+ if (strncmp(de->d_name, conffbasename, conffbasenamelen) == 0) {
+ debug(dbg_stupidlyverbose, "removal_bulk conffile dsd entry starts right");
+ for (ext= removeconffexts; *ext; ext++)
+ if (strcmp(*ext, de->d_name + conffbasenamelen) == 0)
+ goto yes_remove;
+ p= de->d_name+conffbasenamelen;
+ if (*p++ == '~') {
+ while (*p && c_isdigit(*p))
+ p++;
+ if (*p == '~' && !*++p) goto yes_remove;
+ }
+ }
+ debug(dbg_stupidlyverbose, "removal_bulk conffile dsd entry starts wrong");
+ if (de->d_name[0] == '#' &&
+ strncmp(de->d_name + 1, conffbasename, conffbasenamelen) == 0 &&
+ strcmp(de->d_name + 1 + conffbasenamelen, "#") == 0)
+ goto yes_remove;
+ debug(dbg_stupidlyverbose, "removal_bulk conffile dsd entry not it");
+ continue;
+ yes_remove:
+ varbuf_rollback(&removevb_state);
+ varbuf_add_str(&removevb, de->d_name);
+ varbuf_end_str(&removevb);
+ debug(dbg_conffdetail, "removal_bulk conffile dsd entry removing '%s'",
+ removevb.buf);
+ if (unlink(removevb.buf) && errno != ENOENT && errno != ENOTDIR)
+ ohshite(_("cannot remove old backup config file '%.250s' (of '%.250s')"),
+ removevb.buf, conff->name);
+ }
+ pop_cleanup(ehflag_normaltidy); /* closedir */
+ }
+
+ /* Remove the conffiles from the file list file. */
+ write_filelist_except(pkg, &pkg->installed, pkg->files,
+ FNNF_OLD_CONFF);
+
+ pkg->installed.conffiles = NULL;
+ modstatdb_note(pkg);
+
+ maintscript_installed(pkg, POSTRMFILE, "post-removal", "purge", NULL);
+}
+
+/*
+ * This is used both by deferred_remove() in this file, and at the end of
+ * process_archive() in archives.c if it needs to finish removing a
+ * conflicting package.
+ */
+void removal_bulk(struct pkginfo *pkg) {
+ bool foundpostrm;
+
+ debug(dbg_general, "removal_bulk package %s", pkg_name(pkg, pnaw_always));
+
+ if (pkg->status == PKG_STAT_HALFINSTALLED ||
+ pkg->status == PKG_STAT_UNPACKED) {
+ removal_bulk_remove_files(pkg);
+ }
+
+ foundpostrm = pkg_infodb_has_file(pkg, &pkg->installed, POSTRMFILE);
+
+ debug(dbg_general, "removal_bulk purging? foundpostrm=%d",foundpostrm);
+
+ if (!foundpostrm && !pkg->installed.conffiles) {
+ /* If there are no config files and no postrm script then we
+ * go straight into ‘purge’. */
+ debug(dbg_general, "removal_bulk no postrm, no conffiles, purging");
+
+ pkg_set_want(pkg, PKG_WANT_PURGE);
+ dpkg_version_blank(&pkg->configversion);
+ } else if (pkg->want == PKG_WANT_PURGE) {
+ removal_bulk_remove_configfiles(pkg);
+ }
+
+ /* I.e., either of the two branches above. */
+ if (pkg->want == PKG_WANT_PURGE) {
+ const char *filename;
+
+ /* Retry empty directories, and warn on any leftovers that aren't. */
+ removal_bulk_remove_leftover_dirs(pkg);
+
+ filename = pkg_infodb_get_file(pkg, &pkg->installed, LISTFILE);
+ debug(dbg_general, "removal_bulk purge done, removing list '%s'",
+ filename);
+ if (unlink(filename) && errno != ENOENT)
+ ohshite(_("cannot remove old files list"));
+
+ filename = pkg_infodb_get_file(pkg, &pkg->installed, POSTRMFILE);
+ debug(dbg_general, "removal_bulk purge done, removing postrm '%s'",
+ filename);
+ if (unlink(filename) && errno != ENOENT)
+ ohshite(_("can't remove old postrm script"));
+
+ pkg_set_status(pkg, PKG_STAT_NOTINSTALLED);
+ pkg_set_want(pkg, PKG_WANT_UNKNOWN);
+
+ /* This will mess up reverse links, but if we follow them
+ * we won't go back because pkg->status is PKG_STAT_NOTINSTALLED. */
+ pkgbin_blank(&pkg->installed);
+ }
+
+ pkg_reset_eflags(pkg);
+ modstatdb_note(pkg);
+
+ debug(dbg_general, "removal done");
+}
diff --git a/src/main/script.c b/src/main/script.c
new file mode 100644
index 0000000..b4f369d
--- /dev/null
+++ b/src/main/script.c
@@ -0,0 +1,399 @@
+/*
+ * dpkg - main program for package management
+ * script.c - maintainer script routines
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2007-2014 Guillem Jover <guillem@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#ifdef WITH_LIBSELINUX
+#include <selinux/selinux.h>
+#endif
+
+#include <dpkg/i18n.h>
+#include <dpkg/debug.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/subproc.h>
+#include <dpkg/command.h>
+#include <dpkg/triglib.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+void
+post_postinst_tasks(struct pkginfo *pkg, enum pkgstatus new_status)
+{
+ if (new_status < PKG_STAT_TRIGGERSAWAITED)
+ pkg_set_status(pkg, new_status);
+ else if (pkg->trigaw.head)
+ pkg_set_status(pkg, PKG_STAT_TRIGGERSAWAITED);
+ else if (pkg->trigpend_head)
+ pkg_set_status(pkg, PKG_STAT_TRIGGERSPENDING);
+ else
+ pkg_set_status(pkg, PKG_STAT_INSTALLED);
+ modstatdb_note(pkg);
+
+ debug(dbg_triggersdetail, "post_postinst_tasks - trig_incorporate");
+ trig_incorporate(modstatdb_get_status());
+}
+
+static void
+post_script_tasks(void)
+{
+ debug(dbg_triggersdetail, "post_script_tasks - ensure_diversions");
+ ensure_diversions();
+
+ debug(dbg_triggersdetail, "post_script_tasks - trig_incorporate");
+ trig_incorporate(modstatdb_get_status());
+}
+
+static void
+cu_post_script_tasks(int argc, void **argv)
+{
+ post_script_tasks();
+}
+
+static void
+setexecute(const char *path, struct stat *stab)
+{
+ if ((stab->st_mode & 0555) == 0555)
+ return;
+ if (!chmod(path, 0755))
+ return;
+ ohshite(_("unable to set execute permissions on '%.250s'"), path);
+}
+
+/**
+ * Returns the path to the script inside the chroot.
+ */
+static const char *
+maintscript_pre_exec(struct command *cmd)
+{
+ const char *instdir = dpkg_fsys_get_dir();
+ const char *admindir = dpkg_db_get_dir();
+ const char *changedir;
+ size_t instdirlen = strlen(instdir);
+
+ if (instdirlen > 0 && in_force(FORCE_SCRIPT_CHROOTLESS))
+ changedir = instdir;
+ else
+ changedir = "/";
+
+ if (instdirlen > 0 && !in_force(FORCE_SCRIPT_CHROOTLESS)) {
+ int rc;
+
+ if (strncmp(admindir, instdir, instdirlen) != 0)
+ ohshit(_("admindir must be inside instdir for dpkg to work properly"));
+ if (setenv("DPKG_ADMINDIR", admindir + instdirlen, 1) < 0)
+ ohshite(_("unable to setenv for subprocesses"));
+ if (setenv("DPKG_ROOT", "", 1) < 0)
+ ohshite(_("unable to setenv for subprocesses"));
+
+ rc = chroot(instdir);
+ if (rc && in_force(FORCE_NON_ROOT) && errno == EPERM)
+ ohshit(_("not enough privileges to change root "
+ "directory with --force-not-root, consider "
+ "using --force-script-chrootless?"));
+ else if (rc)
+ ohshite(_("failed to chroot to '%.250s'"), instdir);
+ }
+ /* Switch to a known good directory to give the maintainer script
+ * a saner environment, also needed after the chroot(). */
+ if (chdir(changedir))
+ ohshite(_("failed to chdir to '%.255s'"), changedir);
+ if (debug_has_flag(dbg_scripts)) {
+ struct varbuf args = VARBUF_INIT;
+ const char **argv = cmd->argv;
+
+ while (*++argv) {
+ varbuf_add_char(&args, ' ');
+ varbuf_add_str(&args, *argv);
+ }
+ varbuf_end_str(&args);
+ debug(dbg_scripts, "fork/exec %s (%s )", cmd->filename,
+ args.buf);
+ varbuf_destroy(&args);
+ }
+ if (instdirlen == 0 || in_force(FORCE_SCRIPT_CHROOTLESS))
+ return cmd->filename;
+
+ if (strlen(cmd->filename) < instdirlen)
+ internerr("maintscript name '%s' length < instdir length %zd",
+ cmd->filename, instdirlen);
+
+ return cmd->filename + instdirlen;
+}
+
+/**
+ * Set a new security execution context for the maintainer script.
+ *
+ * Try to create a new execution context based on the current one and the
+ * specific maintainer script filename. If it's the same as the current
+ * one, use the given fallback.
+ */
+static int
+maintscript_set_exec_context(struct command *cmd)
+{
+#ifdef WITH_LIBSELINUX
+ return setexecfilecon(cmd->filename, "dpkg_script_t");
+#else
+ return 0;
+#endif
+}
+
+static int
+maintscript_exec(struct pkginfo *pkg, struct pkgbin *pkgbin,
+ struct command *cmd, struct stat *stab, int warn)
+{
+ pid_t pid;
+ int rc;
+
+ setexecute(cmd->filename, stab);
+
+ push_cleanup(cu_post_script_tasks, ehflag_bombout, 0);
+
+ pid = subproc_fork();
+ if (pid == 0) {
+ char *pkg_count;
+ const char *maintscript_debug;
+
+ pkg_count = str_fmt("%d", pkgset_installed_instances(pkg->set));
+
+ maintscript_debug = debug_has_flag(dbg_scripts) ? "1" : "0";
+
+ if (setenv("DPKG_MAINTSCRIPT_PACKAGE", pkg->set->name, 1) ||
+ setenv("DPKG_MAINTSCRIPT_PACKAGE_REFCOUNT", pkg_count, 1) ||
+ setenv("DPKG_MAINTSCRIPT_ARCH", pkgbin->arch->name, 1) ||
+ setenv("DPKG_MAINTSCRIPT_NAME", cmd->argv[0], 1) ||
+ setenv("DPKG_MAINTSCRIPT_DEBUG", maintscript_debug, 1) ||
+ setenv("DPKG_RUNNING_VERSION", PACKAGE_VERSION, 1))
+ ohshite(_("unable to setenv for maintainer script"));
+
+ cmd->filename = cmd->argv[0] = maintscript_pre_exec(cmd);
+
+ if (maintscript_set_exec_context(cmd) < 0)
+ ohshite(_("cannot set security execution context for "
+ "maintainer script"));
+
+ command_exec(cmd);
+ }
+ subproc_signals_ignore(cmd->name);
+ rc = subproc_reap(pid, cmd->name, warn);
+ subproc_signals_restore();
+
+ pop_cleanup(ehflag_normaltidy);
+
+ return rc;
+}
+
+static int
+vmaintscript_installed(struct pkginfo *pkg, const char *scriptname,
+ const char *desc, va_list args)
+{
+ struct command cmd;
+ const char *scriptpath;
+ struct stat stab;
+ char *buf;
+
+ scriptpath = pkg_infodb_get_file(pkg, &pkg->installed, scriptname);
+ m_asprintf(&buf, _("installed %s package %s script"),
+ pkg_name(pkg, pnaw_nonambig), desc);
+
+ command_init(&cmd, scriptpath, buf);
+ command_add_arg(&cmd, scriptname);
+ command_add_argv(&cmd, args);
+
+ if (stat(scriptpath, &stab)) {
+ command_destroy(&cmd);
+
+ if (errno == ENOENT) {
+ debug(dbg_scripts,
+ "vmaintscript_installed nonexistent %s",
+ scriptname);
+ free(buf);
+ return 0;
+ }
+ ohshite(_("unable to stat %s '%.250s'"), buf, scriptpath);
+ }
+ maintscript_exec(pkg, &pkg->installed, &cmd, &stab, 0);
+
+ command_destroy(&cmd);
+ free(buf);
+
+ return 1;
+}
+
+/*
+ * All ...'s in maintscript_* are const char *'s.
+ */
+
+int
+maintscript_installed(struct pkginfo *pkg, const char *scriptname,
+ const char *desc, ...)
+{
+ va_list args;
+ int rc;
+
+ va_start(args, desc);
+ rc = vmaintscript_installed(pkg, scriptname, desc, args);
+ va_end(args);
+
+ if (rc)
+ post_script_tasks();
+
+ return rc;
+}
+
+int
+maintscript_postinst(struct pkginfo *pkg, ...)
+{
+ va_list args;
+ int rc;
+
+ va_start(args, pkg);
+ rc = vmaintscript_installed(pkg, POSTINSTFILE, "post-installation", args);
+ va_end(args);
+
+ if (rc)
+ ensure_diversions();
+
+ return rc;
+}
+
+int
+maintscript_new(struct pkginfo *pkg, const char *scriptname,
+ const char *desc, const char *cidir, char *cidirrest, ...)
+{
+ struct command cmd;
+ struct stat stab;
+ va_list args;
+ char *buf;
+
+ strcpy(cidirrest, scriptname);
+ m_asprintf(&buf, _("new %s package %s script"),
+ pkg_name(pkg, pnaw_nonambig), desc);
+
+ va_start(args, cidirrest);
+ command_init(&cmd, cidir, buf);
+ command_add_arg(&cmd, scriptname);
+ command_add_argv(&cmd, args);
+ va_end(args);
+
+ if (stat(cidir, &stab)) {
+ command_destroy(&cmd);
+
+ if (errno == ENOENT) {
+ debug(dbg_scripts,
+ "maintscript_new nonexistent %s '%s'",
+ scriptname, cidir);
+ free(buf);
+ return 0;
+ }
+ ohshite(_("unable to stat %s '%.250s'"), buf, cidir);
+ }
+ maintscript_exec(pkg, &pkg->available, &cmd, &stab, 0);
+
+ command_destroy(&cmd);
+ free(buf);
+ post_script_tasks();
+
+ return 1;
+}
+
+int
+maintscript_fallback(struct pkginfo *pkg,
+ const char *scriptname, const char *desc,
+ const char *cidir, char *cidirrest,
+ const char *ifok, const char *iffallback)
+{
+ struct command cmd;
+ const char *oldscriptpath;
+ struct stat stab;
+ char *buf;
+
+ oldscriptpath = pkg_infodb_get_file(pkg, &pkg->installed, scriptname);
+ m_asprintf(&buf, _("old %s package %s script"),
+ pkg_name(pkg, pnaw_nonambig), desc);
+
+ command_init(&cmd, oldscriptpath, buf);
+ command_add_args(&cmd, scriptname, ifok,
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ NULL);
+
+ if (stat(oldscriptpath, &stab)) {
+ if (errno == ENOENT) {
+ debug(dbg_scripts,
+ "maintscript_fallback nonexistent %s '%s'",
+ scriptname, oldscriptpath);
+ command_destroy(&cmd);
+ free(buf);
+ return 0;
+ }
+ warning(_("unable to stat %s '%.250s': %s"),
+ cmd.name, oldscriptpath, strerror(errno));
+ } else {
+ if (!maintscript_exec(pkg, &pkg->installed, &cmd, &stab, SUBPROC_WARN)) {
+ command_destroy(&cmd);
+ free(buf);
+ post_script_tasks();
+ return 1;
+ }
+ }
+ notice(_("trying script from the new package instead ..."));
+
+ strcpy(cidirrest, scriptname);
+ m_asprintf(&buf, _("new %s package %s script"),
+ pkg_name(pkg, pnaw_nonambig), desc);
+
+ command_destroy(&cmd);
+ command_init(&cmd, cidir, buf);
+ command_add_args(&cmd, scriptname, iffallback,
+ versiondescribe(&pkg->installed.version, vdew_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ NULL);
+
+ if (stat(cidir, &stab)) {
+ command_destroy(&cmd);
+
+ if (errno == ENOENT)
+ ohshit(_("there is no script in the new version of the package - giving up"));
+ else
+ ohshite(_("unable to stat %s '%.250s'"), buf, cidir);
+ }
+
+ maintscript_exec(pkg, &pkg->available, &cmd, &stab, 0);
+ notice(_("... it looks like that went OK"));
+
+ command_destroy(&cmd);
+ free(buf);
+ post_script_tasks();
+
+ return 1;
+}
diff --git a/src/main/select.c b/src/main/select.c
new file mode 100644
index 0000000..d71202e
--- /dev/null
+++ b/src/main/select.c
@@ -0,0 +1,244 @@
+/*
+ * dpkg - main program for package management
+ * select.c - by-hand (rather than dselect-based) package selection
+ *
+ * Copyright © 1995,1996 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006, 2008-2015 Guillem Jover <guillem@debian.org>
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <fnmatch.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg-array.h>
+#include <dpkg/pkg-show.h>
+#include <dpkg/pkg-spec.h>
+#include <dpkg/options.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+
+#include "main.h"
+
+static void getsel1package(struct pkginfo *pkg) {
+ const char *pkgname;
+ int l;
+
+ if (pkg->want == PKG_WANT_UNKNOWN)
+ return;
+ pkgname = pkg_name(pkg, pnaw_nonambig);
+ l = strlen(pkgname);
+ l >>= 3;
+ l = 6 - l;
+ if (l < 1)
+ l = 1;
+ printf("%s%.*s%s\n", pkgname, l, "\t\t\t\t\t\t", pkg_want_name(pkg));
+}
+
+int
+getselections(const char *const *argv)
+{
+ struct pkg_array array;
+ struct pkginfo *pkg;
+ int i;
+
+ modstatdb_open(msdbrw_readonly);
+
+ pkg_array_init_from_hash(&array);
+ pkg_array_sort(&array, pkg_sorter_by_nonambig_name_arch);
+
+ if (!*argv) {
+ for (i = 0; i < array.n_pkgs; i++) {
+ pkg = array.pkgs[i];
+ if (pkg->status == PKG_STAT_NOTINSTALLED)
+ continue;
+ getsel1package(pkg);
+ }
+ } else {
+ const char *thisarg;
+
+ while ((thisarg= *argv++)) {
+ struct pkg_spec pkgspec;
+ int found;
+
+ found= 0;
+ pkg_spec_init(&pkgspec, PKG_SPEC_PATTERNS | PKG_SPEC_ARCH_WILDCARD);
+ pkg_spec_parse(&pkgspec, thisarg);
+
+ for (i = 0; i < array.n_pkgs; i++) {
+ pkg = array.pkgs[i];
+ if (!pkg_spec_match_pkg(&pkgspec, pkg, &pkg->installed))
+ continue;
+ getsel1package(pkg); found++;
+ }
+ if (!found)
+ notice(_("no packages found matching %s"), thisarg);
+
+ pkg_spec_destroy(&pkgspec);
+ }
+ }
+
+ m_output(stdout, _("<standard output>"));
+ m_output(stderr, _("<standard error>"));
+
+ pkg_array_destroy(&array);
+
+ modstatdb_shutdown();
+
+ return 0;
+}
+
+int
+setselections(const char *const *argv)
+{
+ enum modstatdb_rw msdbflags;
+ const struct namevalue *nv;
+ struct pkginfo *pkg;
+ int c, lno;
+ struct varbuf namevb = VARBUF_INIT;
+ struct varbuf selvb = VARBUF_INIT;
+ bool db_possibly_outdated = false;
+
+ if (*argv)
+ badusage(_("--%s takes no arguments"), cipaction->olong);
+
+ msdbflags = msdbrw_available_readonly;
+ if (f_noact)
+ msdbflags |= msdbrw_readonly;
+ else
+ msdbflags |= msdbrw_write;
+
+ modstatdb_open(msdbflags);
+ pkg_infodb_upgrade();
+
+ lno= 1;
+ for (;;) {
+ struct dpkg_error err;
+
+ do {
+ c = getchar();
+ if (c == '\n')
+ lno++;
+ } while (c != EOF && c_isspace(c));
+ if (c == EOF) break;
+ if (c == '#') {
+ do { c= getchar(); if (c == '\n') lno++; } while (c != EOF && c != '\n');
+ continue;
+ }
+
+ varbuf_reset(&namevb);
+ while (!c_isspace(c)) {
+ varbuf_add_char(&namevb, c);
+ c= getchar();
+ if (c == EOF)
+ ohshit(_("unexpected end of file in package name at line %d"), lno);
+ if (c == '\n') ohshit(_("unexpected end of line in package name at line %d"),lno);
+ }
+ varbuf_end_str(&namevb);
+
+ while (c != EOF && c_isspace(c)) {
+ c= getchar();
+ if (c == EOF)
+ ohshit(_("unexpected end of file after package name at line %d"), lno);
+ if (c == '\n') ohshit(_("unexpected end of line after package name at line %d"),lno);
+ }
+
+ varbuf_reset(&selvb);
+ while (c != EOF && !c_isspace(c)) {
+ varbuf_add_char(&selvb, c);
+ c= getchar();
+ }
+ varbuf_end_str(&selvb);
+
+ while (c != EOF && c != '\n') {
+ c= getchar();
+ if (!c_isspace(c))
+ ohshit(_("unexpected data after package and selection at line %d"),lno);
+ }
+ pkg = pkg_spec_parse_pkg(namevb.buf, &err);
+ if (pkg == NULL)
+ ohshit(_("illegal package name at line %d: %.250s"), lno, err.str);
+
+ if (!pkg_is_informative(pkg, &pkg->installed) &&
+ !pkg_is_informative(pkg, &pkg->available)) {
+ db_possibly_outdated = true;
+ warning(_("package not in status nor available database at line %d: %.250s"), lno, namevb.buf);
+ lno++;
+ continue;
+ }
+
+ nv = namevalue_find_by_name(wantinfos, selvb.buf);
+ if (nv == NULL)
+ ohshit(_("unknown wanted status at line %d: %.250s"), lno, selvb.buf);
+
+ pkg_set_want(pkg, nv->value);
+ if (c == EOF) break;
+ lno++;
+ }
+ if (ferror(stdin)) ohshite(_("read error on standard input"));
+ modstatdb_shutdown();
+ varbuf_destroy(&namevb);
+ varbuf_destroy(&selvb);
+
+ if (db_possibly_outdated)
+ warning(_("found unknown packages; this might mean the available database\n"
+ "is outdated, and needs to be updated through a frontend method;\n"
+ "please see the FAQ <https://wiki.debian.org/Teams/Dpkg/FAQ#set-selections>"));
+
+ return 0;
+}
+
+int
+clearselections(const char *const *argv)
+{
+ enum modstatdb_rw msdbflags;
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg;
+
+ if (*argv)
+ badusage(_("--%s takes no arguments"), cipaction->olong);
+
+ if (f_noact)
+ msdbflags = msdbrw_readonly;
+ else
+ msdbflags = msdbrw_write;
+
+ modstatdb_open(msdbflags);
+ pkg_infodb_upgrade();
+
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ if (!pkg->installed.essential &&
+ !pkg->installed.is_protected &&
+ pkg->want != PKG_WANT_UNKNOWN)
+ pkg_set_want(pkg, PKG_WANT_DEINSTALL);
+ }
+ pkg_hash_iter_free(iter);
+
+ modstatdb_shutdown();
+
+ return 0;
+}
+
diff --git a/src/main/trigproc.c b/src/main/trigproc.c
new file mode 100644
index 0000000..125eb02
--- /dev/null
+++ b/src/main/trigproc.c
@@ -0,0 +1,576 @@
+/*
+ * dpkg - main program for package management
+ * trigproc.c - trigger processing
+ *
+ * Copyright © 2007 Canonical Ltd
+ * written by Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/pkg-queue.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+#include <dpkg/triglib.h>
+
+#include "main.h"
+
+/*
+ * Trigger processing algorithms:
+ *
+ *
+ * There is a separate queue (‘deferred trigproc list’) for triggers
+ * ‘relevant’ to what we just did; when we find something triggered ‘now’
+ * we add it to that queue (unless --no-triggers).
+ *
+ *
+ * We want to prefer configuring packages where possible to doing trigger
+ * processing, although it would be better to prefer trigger processing
+ * to cycle-breaking we need to do the latter first or we might generate
+ * artificial trigger cycles, but we always want to prefer trigger
+ * processing to dependency forcing. This is achieved as follows:
+ *
+ * Each time during configure processing where a package D is blocked by
+ * only (ie Depends unsatisfied but would be satisfied by) a t-awaiter W
+ * we make a note of (one of) W's t-pending, T. (Only the last such T.)
+ * (If --no-triggers and nonempty argument list and package isn't in
+ * argument list then we don't do this.)
+ *
+ * Each time in process_queue() where we increment dependtry, we instead
+ * see if we have encountered such a t-pending T. If we do and are in
+ * a trigger processing try, we trigproc T instead of incrementing
+ * dependtry and this counts as having done something so we reset
+ * sincenothing.
+ *
+ *
+ * For --triggers-only and --configure, we go through each thing in the
+ * argument queue (the enqueue_package queue) and check what its state is
+ * and if appropriate we trigproc it. If we didn't have a queue (or had
+ * just --pending) we search all triggers-pending packages and add them
+ * to the deferred trigproc list.
+ *
+ *
+ * Before quitting from most operations, we trigproc each package in the
+ * deferred trigproc list. This may (if not --no-triggers) of course add
+ * new things to the deferred trigproc list.
+ *
+ *
+ * Note that ‘we trigproc T’ must involve trigger cycle detection and
+ * also automatic setting of t-awaiters to t-pending or installed. In
+ * particular, we do cycle detection even for trigger processing in the
+ * configure dependtry loop (and it is OK to do it for explicitly
+ * specified packages from the command line arguments; duplicates are
+ * removed by packages.c:process_queue).
+ */
+
+/*========== Deferred trigger queue. ==========*/
+
+static struct pkg_queue deferred = PKG_QUEUE_INIT;
+
+static void
+trigproc_enqueue_deferred(struct pkginfo *pend)
+{
+ if (f_triggers < 0)
+ return;
+ ensure_package_clientdata(pend);
+ if (pend->clientdata->trigprocdeferred)
+ return;
+ pend->clientdata->trigprocdeferred = pkg_queue_push(&deferred, pend);
+ debug(dbg_triggers, "trigproc_enqueue_deferred pend=%s",
+ pkg_name(pend, pnaw_always));
+}
+
+/**
+ * Populate the deferred trigger queue.
+ *
+ * When dpkg is called with a specific set of packages to act on, we might
+ * have packages pending trigger processing. But because there are frontends
+ * that do not perform a final «dpkg --configure --pending» call (i.e. apt),
+ * the system is left in a state with packages not fully installed.
+ *
+ * We have to populate the deferred trigger queue from the entire package
+ * database, so that we might try to do opportunistic trigger processing
+ * when going through the deferred trigger queue, because a fixed apt will
+ * not request the necessary processing anyway.
+ *
+ * XXX: This can be removed once apt is fixed in the next stable release.
+ */
+void
+trigproc_populate_deferred(void)
+{
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg;
+
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ if (!pkg->trigpend_head)
+ continue;
+
+ if (pkg->status != PKG_STAT_TRIGGERSAWAITED &&
+ pkg->status != PKG_STAT_TRIGGERSPENDING)
+ continue;
+
+ if (pkg->want != PKG_WANT_INSTALL &&
+ pkg->want != PKG_WANT_HOLD)
+ continue;
+
+ trigproc_enqueue_deferred(pkg);
+ }
+ pkg_hash_iter_free(iter);
+}
+
+void
+trigproc_run_deferred(void)
+{
+ jmp_buf ejbuf;
+
+ debug(dbg_triggers, "trigproc_run_deferred");
+ while (!pkg_queue_is_empty(&deferred)) {
+ struct pkginfo *pkg;
+
+ pkg = pkg_queue_pop(&deferred);
+ if (!pkg)
+ continue;
+
+ if (setjmp(ejbuf)) {
+ pop_error_context(ehflag_bombout);
+ continue;
+ }
+ push_error_context_jump(&ejbuf, print_error_perpackage,
+ pkg_name(pkg, pnaw_nonambig));
+
+ ensure_package_clientdata(pkg);
+ pkg->clientdata->trigprocdeferred = NULL;
+ trigproc(pkg, TRIGPROC_TRY_DEFERRED);
+
+ pop_error_context(ehflag_normaltidy);
+ }
+}
+
+/*
+ * Called by modstatdb_note.
+ */
+void
+trig_activate_packageprocessing(struct pkginfo *pkg)
+{
+ debug(dbg_triggersdetail, "trigproc_activate_packageprocessing pkg=%s",
+ pkg_name(pkg, pnaw_always));
+
+ trig_parse_ci(pkg_infodb_get_file(pkg, &pkg->installed, TRIGGERSCIFILE),
+ NULL, trig_cicb_statuschange_activate,
+ pkg, &pkg->installed);
+}
+
+/*========== Actual trigger processing. ==========*/
+
+struct trigcyclenode {
+ struct trigcyclenode *next;
+ struct trigcycleperpkg *pkgs;
+ struct pkginfo *then_processed;
+};
+
+struct trigcycleperpkg {
+ struct trigcycleperpkg *next;
+ struct pkginfo *pkg;
+ struct trigpend *then_trigs;
+};
+
+static bool tortoise_advance;
+static struct trigcyclenode *tortoise, *hare;
+
+void
+trigproc_reset_cycle(void)
+{
+ tortoise_advance = false;
+ tortoise = hare = NULL;
+}
+
+static bool
+tortoise_in_hare(struct pkginfo *processing_now,
+ struct trigcycleperpkg *tortoise_pkg)
+{
+ const char *processing_now_name, *tortoise_name;
+ struct trigpend *hare_trig, *tortoise_trig;
+
+ processing_now_name = pkg_name(processing_now, pnaw_nonambig);
+ tortoise_name = pkg_name(tortoise_pkg->pkg, pnaw_nonambig);
+
+ debug(dbg_triggersdetail, "%s pnow=%s tortoise=%s", __func__,
+ processing_now_name, tortoise_name);
+ for (tortoise_trig = tortoise_pkg->then_trigs;
+ tortoise_trig;
+ tortoise_trig = tortoise_trig->next) {
+ debug(dbg_triggersdetail,
+ "%s pnow=%s tortoise=%s tortoisetrig=%s", __func__,
+ processing_now_name, tortoise_name, tortoise_trig->name);
+
+ /* hare is now so we can just look up in the actual data. */
+ for (hare_trig = tortoise_pkg->pkg->trigpend_head;
+ hare_trig;
+ hare_trig = hare_trig->next) {
+ debug(dbg_triggersstupid, "%s pnow=%s tortoise=%s"
+ " tortoisetrig=%s haretrig=%s", __func__,
+ processing_now_name, tortoise_name,
+ tortoise_trig->name, hare_trig->name);
+ if (strcmp(hare_trig->name, tortoise_trig->name) == 0)
+ break;
+ }
+
+ if (hare_trig == NULL) {
+ /* Not found in hare, yay! */
+ debug(dbg_triggersdetail, "%s pnow=%s tortoise=%s OK",
+ __func__, processing_now_name, tortoise_name);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static struct trigcyclenode *
+trigproc_new_cyclenode(struct pkginfo *processing_now)
+{
+ struct trigcyclenode *tcn;
+ struct trigcycleperpkg *tcpp;
+ struct pkginfo *pkg;
+ struct pkg_hash_iter *iter;
+
+ tcn = nfmalloc(sizeof(*tcn));
+ tcn->pkgs = NULL;
+ tcn->next = NULL;
+ tcn->then_processed = processing_now;
+
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ if (!pkg->trigpend_head)
+ continue;
+ tcpp = nfmalloc(sizeof(*tcpp));
+ tcpp->pkg = pkg;
+ tcpp->then_trigs = pkg->trigpend_head;
+ tcpp->next = tcn->pkgs;
+ tcn->pkgs = tcpp;
+ }
+ pkg_hash_iter_free(iter);
+
+ return tcn;
+}
+
+/*
+ * Returns package we are to give up on.
+ */
+static struct pkginfo *
+check_trigger_cycle(struct pkginfo *processing_now)
+{
+ struct trigcyclenode *tcn;
+ struct trigcycleperpkg *tortoise_pkg;
+ struct trigpend *tortoise_trig;
+ struct pkginfo *giveup;
+ const char *sep;
+
+ debug(dbg_triggers, "check_triggers_cycle pnow=%s",
+ pkg_name(processing_now, pnaw_always));
+
+ tcn = trigproc_new_cyclenode(processing_now);
+
+ if (!hare) {
+ debug(dbg_triggersdetail, "check_triggers_cycle pnow=%s first",
+ pkg_name(processing_now, pnaw_always));
+ hare = tortoise = tcn;
+ return NULL;
+ }
+
+ hare->next = tcn;
+ hare = hare->next;
+ if (tortoise_advance)
+ tortoise = tortoise->next;
+ tortoise_advance = !tortoise_advance;
+
+ /* Now we compare hare to tortoise.
+ * We want to find a trigger pending in tortoise which is not in hare
+ * if we find such a thing we have proved that hare isn't a superset
+ * of tortoise and so that we haven't found a loop (yet). */
+ for (tortoise_pkg = tortoise->pkgs;
+ tortoise_pkg;
+ tortoise_pkg = tortoise_pkg->next) {
+ if (!tortoise_in_hare(processing_now, tortoise_pkg))
+ return NULL;
+ }
+ /* Oh dear. hare is a superset of tortoise. We are making no
+ * progress. */
+ notice(_("cycle found while processing triggers:\n"
+ " chain of packages whose triggers are or may be responsible:"));
+ sep = " ";
+ for (tcn = tortoise; tcn; tcn = tcn->next) {
+ fprintf(stderr, "%s%s", sep,
+ pkg_name(tcn->then_processed, pnaw_nonambig));
+ sep = " -> ";
+ }
+ fprintf(stderr, _("\n" " packages' pending triggers which are"
+ " or may be unresolvable:\n"));
+ for (tortoise_pkg = tortoise->pkgs;
+ tortoise_pkg;
+ tortoise_pkg = tortoise_pkg->next) {
+ fprintf(stderr, " %s",
+ pkg_name(tortoise_pkg->pkg, pnaw_nonambig));
+ sep = ": ";
+ for (tortoise_trig = tortoise_pkg->then_trigs;
+ tortoise_trig;
+ tortoise_trig = tortoise_trig->next) {
+ fprintf(stderr, "%s%s", sep, tortoise_trig->name);
+ }
+ fprintf(stderr, "\n");
+ }
+
+ /* We give up on the _earliest_ package involved. */
+ giveup = tortoise->pkgs->pkg;
+ debug(dbg_triggers, "check_triggers_cycle pnow=%s giveup=%s",
+ pkg_name(processing_now, pnaw_always),
+ pkg_name(giveup, pnaw_always));
+ if (giveup->status != PKG_STAT_TRIGGERSAWAITED &&
+ giveup->status != PKG_STAT_TRIGGERSPENDING)
+ internerr("package %s in non-trigger state %s",
+ pkg_name(giveup, pnaw_always),
+ pkg_status_name(giveup));
+ giveup->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ pkg_set_status(giveup, PKG_STAT_HALFCONFIGURED);
+ modstatdb_note(giveup);
+ print_error_perpackage(_("triggers looping, abandoned"),
+ pkg_name(giveup, pnaw_nonambig));
+
+ return giveup;
+}
+
+/*
+ * Does cycle checking. Doesn't mind if pkg has no triggers pending - in
+ * that case does nothing but fix up any stale awaiters.
+ */
+void
+trigproc(struct pkginfo *pkg, enum trigproc_type type)
+{
+ static struct varbuf namesarg;
+
+ struct varbuf depwhynot = VARBUF_INIT;
+
+ debug(dbg_triggers, "trigproc %s", pkg_name(pkg, pnaw_always));
+
+ ensure_package_clientdata(pkg);
+ if (pkg->clientdata->trigprocdeferred)
+ pkg->clientdata->trigprocdeferred->pkg = NULL;
+ pkg->clientdata->trigprocdeferred = NULL;
+
+ if (pkg->trigpend_head) {
+ struct pkginfo *gaveup;
+ struct trigpend *tp;
+ enum dep_check ok;
+
+ if (pkg->status != PKG_STAT_TRIGGERSPENDING &&
+ pkg->status != PKG_STAT_TRIGGERSAWAITED)
+ internerr("package %s in non-trigger state %s",
+ pkg_name(pkg, pnaw_always),
+ pkg_status_name(pkg));
+
+ if (dependtry < DEPEND_TRY_TRIGGERS &&
+ type == TRIGPROC_TRY_QUEUED) {
+ /* We are not yet in a triggers run, so postpone this
+ * package completely. */
+ enqueue_package(pkg);
+ return;
+ }
+
+ if (dependtry >= DEPEND_TRY_CYCLES) {
+ if (findbreakcycle(pkg))
+ sincenothing = 0;
+ }
+
+ ok = dependencies_ok(pkg, NULL, &depwhynot);
+ if (ok == DEP_CHECK_DEFER) {
+ if (dependtry >= DEPEND_TRY_TRIGGERS_CYCLES) {
+ gaveup = check_trigger_cycle(pkg);
+ if (gaveup == pkg)
+ return;
+ }
+
+ varbuf_destroy(&depwhynot);
+ enqueue_package(pkg);
+ return;
+ } else if (ok == DEP_CHECK_HALT) {
+ /* When doing opportunistic deferred trigger processing,
+ * nothing requires us to be able to make progress;
+ * skip the package and silently ignore the error due
+ * to unsatisfiable dependencies. And because we can
+ * end up here repeatedly, if this package is required
+ * to make progress for other packages, we need to
+ * reset the trigger cycle tracking to avoid detecting
+ * bogus cycles*/
+ if (type == TRIGPROC_TRY_DEFERRED) {
+ trigproc_reset_cycle();
+
+ varbuf_destroy(&depwhynot);
+ return;
+ }
+
+ sincenothing = 0;
+ varbuf_end_str(&depwhynot);
+ notice(_("dependency problems prevent processing "
+ "triggers for %s:\n%s"),
+ pkg_name(pkg, pnaw_nonambig), depwhynot.buf);
+ varbuf_destroy(&depwhynot);
+ ohshit(_("dependency problems - leaving triggers unprocessed"));
+ } else if (depwhynot.used) {
+ varbuf_end_str(&depwhynot);
+ notice(_("%s: dependency problems, but processing "
+ "triggers anyway as you requested:\n%s"),
+ pkg_name(pkg, pnaw_nonambig), depwhynot.buf);
+ varbuf_destroy(&depwhynot);
+ }
+
+ gaveup = check_trigger_cycle(pkg);
+ if (gaveup == pkg)
+ return;
+
+ printf(_("Processing triggers for %s (%s) ...\n"),
+ pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig));
+ log_action("trigproc", pkg, &pkg->installed);
+
+ varbuf_reset(&namesarg);
+ for (tp = pkg->trigpend_head; tp; tp = tp->next) {
+ varbuf_add_char(&namesarg, ' ');
+ varbuf_add_str(&namesarg, tp->name);
+ }
+ varbuf_end_str(&namesarg);
+
+ /* Setting the status to half-configured
+ * causes modstatdb_note to clear pending triggers. */
+ pkg_set_status(pkg, PKG_STAT_HALFCONFIGURED);
+ modstatdb_note(pkg);
+
+ if (!f_noact) {
+ sincenothing = 0;
+ maintscript_postinst(pkg, "triggered",
+ namesarg.buf + 1, NULL);
+ }
+
+ post_postinst_tasks(pkg, PKG_STAT_INSTALLED);
+ } else {
+ /* In other branch is done by modstatdb_note(), from inside
+ * post_postinst_tasks(). */
+ trig_clear_awaiters(pkg);
+ }
+}
+
+/*========== Transitional global activation. ==========*/
+
+static void
+transitional_interest_callback_ro(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, enum trig_options opts)
+{
+ struct pkginfo *pend = pkg;
+ struct pkgbin *pendbin = pkgbin;
+
+ debug(dbg_triggersdetail,
+ "trig_transitional_interest_callback trig=%s pend=%s",
+ trig, pkgbin_name(pend, pendbin, pnaw_always));
+ if (pend->status >= PKG_STAT_TRIGGERSAWAITED)
+ trig_note_pend(pend, nfstrsave(trig));
+}
+
+static void
+transitional_interest_callback(const char *trig, struct pkginfo *pkg,
+ struct pkgbin *pkgbin, enum trig_options opts)
+{
+ struct pkginfo *pend = pkg;
+ struct pkgbin *pendbin = pkgbin;
+
+ trig_cicb_interest_add(trig, pend, pendbin, opts);
+ transitional_interest_callback_ro(trig, pend, pendbin, opts);
+}
+
+/*
+ * cstatus might be msdbrw_readonly if we're in --no-act mode, in which
+ * case we don't write out all of the interest files etc. but we do
+ * invent all of the activations for our own benefit.
+ */
+static void
+trig_transitional_activate(enum modstatdb_rw cstatus)
+{
+ struct pkg_hash_iter *iter;
+ struct pkginfo *pkg;
+
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter))) {
+ if (pkg->status <= PKG_STAT_HALFINSTALLED)
+ continue;
+ debug(dbg_triggersdetail, "trig_transitional_activate %s %s",
+ pkg_name(pkg, pnaw_always),
+ pkg_status_name(pkg));
+ pkg->trigpend_head = NULL;
+ trig_parse_ci(pkg_infodb_get_file(pkg, &pkg->installed,
+ TRIGGERSCIFILE),
+ cstatus >= msdbrw_write ?
+ transitional_interest_callback :
+ transitional_interest_callback_ro, NULL,
+ pkg, &pkg->installed);
+ /* Ensure we're not creating incoherent data that can't
+ * be written down. This should never happen in theory but
+ * can happen if you restore an old status file that is
+ * not in sync with the infodb files. */
+ if (pkg->status < PKG_STAT_TRIGGERSAWAITED)
+ continue;
+
+ if (pkg->trigaw.head)
+ pkg_set_status(pkg, PKG_STAT_TRIGGERSAWAITED);
+ else if (pkg->trigpend_head)
+ pkg_set_status(pkg, PKG_STAT_TRIGGERSPENDING);
+ else
+ pkg_set_status(pkg, PKG_STAT_INSTALLED);
+ }
+ pkg_hash_iter_free(iter);
+
+ if (cstatus >= msdbrw_write) {
+ modstatdb_checkpoint();
+ trig_file_interests_save();
+ }
+}
+
+/*========== Hook setup. ==========*/
+
+TRIGHOOKS_DEFINE_NAMENODE_ACCESSORS
+
+static const struct trig_hooks trig_our_hooks = {
+ .enqueue_deferred = trigproc_enqueue_deferred,
+ .transitional_activate = trig_transitional_activate,
+ .namenode_find = th_nn_find,
+ .namenode_interested = th_nn_interested,
+ .namenode_name = th_nn_name,
+};
+
+void
+trigproc_install_hooks(void)
+{
+ trig_override_hooks(&trig_our_hooks);
+}
diff --git a/src/main/unpack.c b/src/main/unpack.c
new file mode 100644
index 0000000..02c2681
--- /dev/null
+++ b/src/main/unpack.c
@@ -0,0 +1,1734 @@
+/*
+ * dpkg - main program for package management
+ * unpack.c - .deb archive unpacking
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ * Copyright © 2006-2015 Guillem Jover <guillem@debian.org>
+ * Copyright © 2011 Linaro Limited
+ * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/c-ctype.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/pkg.h>
+#include <dpkg/pkg-queue.h>
+#include <dpkg/path.h>
+#include <dpkg/command.h>
+#include <dpkg/buffer.h>
+#include <dpkg/subproc.h>
+#include <dpkg/dir.h>
+#include <dpkg/tarfn.h>
+#include <dpkg/options.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+#include <dpkg/triglib.h>
+
+#include "file-match.h"
+#include "main.h"
+#include "archives.h"
+
+static const char *
+summarize_filename(const char *filename)
+{
+ const char *pfilename;
+ char *pfilenamebuf;
+
+ for (pfilename = filename;
+ pfilename && strlen(pfilename) > 30 && strchr(pfilename, '/') != NULL;
+ pfilename++)
+ pfilename = strchr(pfilename, '/');
+
+ if (pfilename && pfilename != filename) {
+ pfilenamebuf = nfmalloc(strlen(pfilename) + 5);
+ sprintf(pfilenamebuf, _(".../%s"), pfilename);
+ pfilename = pfilenamebuf;
+ } else {
+ pfilename = filename;
+ }
+
+ return pfilename;
+}
+
+static bool
+deb_reassemble(const char **filename, const char **pfilename)
+{
+ static char *reasmbuf = NULL;
+ struct stat stab;
+ int status;
+ pid_t pid;
+
+ if (!reasmbuf)
+ reasmbuf = dpkg_db_get_path(REASSEMBLETMP);
+ if (unlink(reasmbuf) && errno != ENOENT)
+ ohshite(_("error ensuring '%.250s' doesn't exist"), reasmbuf);
+
+ push_cleanup(cu_pathname, ~0, 1, (void *)reasmbuf);
+
+ pid = subproc_fork();
+ if (!pid) {
+ execlp(SPLITTER, SPLITTER, "-Qao", reasmbuf, *filename, NULL);
+ ohshite(_("unable to execute %s (%s)"),
+ _("split package reassembly"), SPLITTER);
+ }
+ status = subproc_reap(pid, SPLITTER, SUBPROC_RETERROR);
+ switch (status) {
+ case 0:
+ /* It was a part - is it complete? */
+ if (!stat(reasmbuf, &stab)) {
+ /* Yes. */
+ *filename = reasmbuf;
+ *pfilename = _("reassembled package file");
+ break;
+ } else if (errno == ENOENT) {
+ /* No. That's it, we skip it. */
+ return false;
+ }
+ case 1:
+ /* No, it wasn't a part. */
+ break;
+ default:
+ ohshit(_("subprocess %s returned error exit status %d"), SPLITTER, status);
+ }
+
+ return true;
+}
+
+static void
+deb_verify(const char *filename)
+{
+ pid_t pid;
+
+ /* We have to check on every unpack, in case the debsig-verify package
+ * gets installed or removed. */
+ if (!command_in_path(DEBSIGVERIFY))
+ return;
+
+ printf(_("Authenticating %s ...\n"), filename);
+ fflush(stdout);
+ pid = subproc_fork();
+ if (!pid) {
+ execlp(DEBSIGVERIFY, DEBSIGVERIFY, "-q", filename, NULL);
+ ohshite(_("unable to execute %s (%s)"),
+ _("package signature verification"), DEBSIGVERIFY);
+ } else {
+ int status;
+
+ status = subproc_reap(pid, "debsig-verify", SUBPROC_NOCHECK);
+ if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
+ if (!in_force(FORCE_BAD_VERIFY))
+ ohshit(_("verification on package %s failed!"), filename);
+ else
+ notice(_("verification on package %s failed; "
+ "but installing anyway as you requested"), filename);
+ } else {
+ printf(_("passed\n"));
+ }
+ }
+}
+
+static char *
+get_control_dir(char *cidir)
+{
+ if (f_noact) {
+ char *tmpdir;
+
+ tmpdir = mkdtemp(path_make_temp_template("dpkg"));
+ if (tmpdir == NULL)
+ ohshite(_("unable to create temporary directory"));
+
+ cidir = m_realloc(cidir, strlen(tmpdir) + MAXCONTROLFILENAME + 10);
+
+ strcpy(cidir, tmpdir);
+
+ free(tmpdir);
+ } else {
+ const char *admindir;
+
+ admindir = dpkg_db_get_dir();
+
+ /* The admindir length is always constant on a dpkg execution run. */
+ if (cidir == NULL)
+ cidir = m_malloc(strlen(admindir) + sizeof(CONTROLDIRTMP) +
+ MAXCONTROLFILENAME + 10);
+
+ /* We want it to be on the same filesystem so that we can
+ * use rename(2) to install the postinst &c. */
+ strcpy(cidir, admindir);
+ strcat(cidir, "/" CONTROLDIRTMP);
+
+ /* Make sure the control information directory is empty. */
+ path_remove_tree(cidir);
+ }
+
+ strcat(cidir, "/");
+
+ return cidir;
+}
+
+static void
+pkg_check_depcon(struct pkginfo *pkg, const char *pfilename)
+{
+ struct dependency *dsearch;
+ struct deppossi *psearch;
+ struct pkginfo *fixbytrigaw;
+ static struct varbuf depprobwhy;
+
+ /* Check if anything is installed that we conflict with, or not installed
+ * that we need. */
+ ensure_package_clientdata(pkg);
+ pkg->clientdata->istobe = PKG_ISTOBE_INSTALLNEW;
+
+ for (dsearch = pkg->available.depends; dsearch; dsearch = dsearch->next) {
+ switch (dsearch->type) {
+ case dep_conflicts:
+ /* Look for things we conflict with. */
+ check_conflict(dsearch, pkg, pfilename);
+ break;
+ case dep_breaks:
+ /* Look for things we break. */
+ check_breaks(dsearch, pkg, pfilename);
+ break;
+ case dep_provides:
+ /* Look for things that conflict with what we provide. */
+ for (psearch = dsearch->list->ed->depended.installed;
+ psearch;
+ psearch = psearch->rev_next) {
+ if (psearch->up->type != dep_conflicts)
+ continue;
+ check_conflict(psearch->up, pkg, pfilename);
+ }
+ break;
+ case dep_suggests:
+ case dep_recommends:
+ case dep_depends:
+ case dep_replaces:
+ case dep_enhances:
+ /* Ignore these here. */
+ break;
+ case dep_predepends:
+ if (!depisok(dsearch, &depprobwhy, NULL, &fixbytrigaw, true)) {
+ if (fixbytrigaw) {
+ while (fixbytrigaw->trigaw.head)
+ trigproc(fixbytrigaw->trigaw.head->pend, TRIGPROC_REQUIRED);
+ } else {
+ varbuf_end_str(&depprobwhy);
+ notice(_("regarding %s containing %s, pre-dependency problem:\n%s"),
+ pfilename, pkgbin_name(pkg, &pkg->available, pnaw_nonambig),
+ depprobwhy.buf);
+ if (!force_depends(dsearch->list))
+ ohshit(_("pre-dependency problem - not installing %.250s"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig));
+ warning(_("ignoring pre-dependency problem!"));
+ }
+ }
+ }
+ }
+
+ /* Look for things that conflict with us. */
+ for (psearch = pkg->set->depended.installed; psearch; psearch = psearch->rev_next) {
+ if (psearch->up->type != dep_conflicts)
+ continue;
+
+ check_conflict(psearch->up, pkg, pfilename);
+ }
+}
+
+static void
+pkg_deconfigure_others(struct pkginfo *pkg)
+{
+ struct pkg_deconf_list *deconpil;
+
+ for (deconpil = deconfigure; deconpil; deconpil = deconpil->next) {
+ struct pkginfo *removing = deconpil->pkg_removal;
+
+ if (deconpil->reason == PKG_WANT_DEINSTALL) {
+ printf(_("De-configuring %s (%s), to allow removal of %s (%s) ...\n"),
+ pkg_name(deconpil->pkg, pnaw_nonambig),
+ versiondescribe(&deconpil->pkg->installed.version, vdew_nonambig),
+ pkg_name(removing, pnaw_nonambig),
+ versiondescribe(&removing->installed.version, vdew_nonambig));
+ } else if (deconpil->reason == PKG_WANT_INSTALL) {
+ printf(_("De-configuring %s (%s), to allow installation of %s (%s) ...\n"),
+ pkg_name(deconpil->pkg, pnaw_nonambig),
+ versiondescribe(&deconpil->pkg->installed.version, vdew_nonambig),
+ pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig));
+ } else {
+ printf(_("De-configuring %s (%s), to allow configuration of %s (%s) ...\n"),
+ pkg_name(deconpil->pkg, pnaw_nonambig),
+ versiondescribe(&deconpil->pkg->installed.version, vdew_nonambig),
+ pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig));
+ }
+
+ trig_activate_packageprocessing(deconpil->pkg);
+ pkg_set_status(deconpil->pkg, PKG_STAT_HALFCONFIGURED);
+ modstatdb_note(deconpil->pkg);
+
+ /* This means that we *either* go and run postinst abort-deconfigure,
+ * *or* queue the package for later configure processing, depending
+ * on which error cleanup route gets taken. */
+ push_cleanup_fallback(cu_prermdeconfigure, ~ehflag_normaltidy,
+ ok_prermdeconfigure, ehflag_normaltidy,
+ 3, (void *)deconpil->pkg, (void *)removing, (void *)pkg);
+
+ if (removing) {
+ maintscript_installed(deconpil->pkg, PRERMFILE, "pre-removal",
+ "deconfigure", "in-favour",
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig),
+ versiondescribe(&pkg->available.version,
+ vdew_nonambig),
+ "removing",
+ pkg_name(removing, pnaw_nonambig),
+ versiondescribe(&removing->installed.version,
+ vdew_nonambig),
+ NULL);
+ } else {
+ maintscript_installed(deconpil->pkg, PRERMFILE, "pre-removal",
+ "deconfigure", "in-favour",
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig),
+ versiondescribe(&pkg->available.version,
+ vdew_nonambig),
+ NULL);
+ }
+ }
+}
+
+/**
+ * Read the conffiles, and copy the hashes across.
+ */
+static void
+deb_parse_conffiles(const struct pkginfo *pkg, const char *control_conffiles,
+ struct fsys_namenode_queue *newconffiles)
+{
+ FILE *conff;
+ char conffilenamebuf[MAXCONFFILENAME];
+
+ conff = fopen(control_conffiles, "r");
+ if (conff == NULL) {
+ if (errno == ENOENT)
+ return;
+ ohshite(_("error trying to open %.250s"), control_conffiles);
+ }
+
+ push_cleanup(cu_closestream, ehflag_bombout, 1, conff);
+
+ while (fgets(conffilenamebuf, MAXCONFFILENAME - 2, conff)) {
+ struct pkginfo *otherpkg;
+ struct fsys_node_pkgs_iter *iter;
+ struct fsys_namenode *namenode;
+ struct fsys_namenode_list *newconff;
+ struct conffile *searchconff;
+ char *conffilename = conffilenamebuf;
+ char *p;
+ enum fsys_namenode_flags confflags = FNNF_NEW_CONFF;
+
+ p = conffilename + strlen(conffilename);
+ if (p == conffilename)
+ ohshit(_("conffile file contains an empty line"));
+ if (p[-1] != '\n')
+ ohshit(_("conffile name '%s' is too long, or missing final newline"),
+ conffilename);
+ p = str_rtrim_spaces(conffilename, p);
+ if (p == conffilename)
+ continue;
+
+ /* Check for conffile flags. */
+ if (conffilename[0] != '/') {
+ char *flag = conffilename;
+ char *flag_end = strchr(flag, ' ');
+
+ if (flag_end)
+ conffilename = flag_end + 1;
+
+ /* If no flag separator is found, assume a missing leading slash. */
+ if (flag_end == NULL || (conffilename[0] && conffilename[0] != '/'))
+ ohshit(_("conffile name '%s' is not an absolute pathname"), conffilename);
+
+ flag_end[0] = '\0';
+
+ /* Otherwise assume a missing filename after the flag separator. */
+ if (conffilename[0] == '\0')
+ ohshit(_("conffile name missing after flag '%s'"), flag);
+
+ if (strcmp(flag, "remove-on-upgrade") == 0) {
+ confflags |= FNNF_RM_CONFF_ON_UPGRADE;
+ confflags &= ~FNNF_NEW_CONFF;
+ } else {
+ if (c_isspace(flag[0]))
+ warning(_("line with conffile filename '%s' has leading white spaces"),
+ conffilename);
+ ohshit(_("unknown flag '%s' for conffile '%s'"), flag, conffilename);
+ }
+ }
+
+ namenode = fsys_hash_find_node(conffilename, FHFF_NONE);
+ namenode->oldhash = NEWCONFFILEFLAG;
+ newconff = tar_fsys_namenode_queue_push(newconffiles, namenode);
+
+ /*
+ * Let's see if any packages have this file.
+ *
+ * If they do we check to see if they listed it as a conffile,
+ * and if they did we copy the hash across. Since (for plain
+ * file conffiles, which is the only kind we are supposed to
+ * have) there will only be one package which ‘has’ the file,
+ * this will usually mean we only look in the package which
+ * we are installing now.
+ *
+ * The ‘conffiles’ data in the status file is ignored when a
+ * package is not also listed in the file ownership database as
+ * having that file. If several packages are listed as owning
+ * the file we pick one at random.
+ */
+ searchconff = NULL;
+
+ iter = fsys_node_pkgs_iter_new(newconff->namenode);
+ while ((otherpkg = fsys_node_pkgs_iter_next(iter))) {
+ debug(dbg_conffdetail,
+ "process_archive conffile '%s' in package %s - conff ?",
+ newconff->namenode->name, pkg_name(otherpkg, pnaw_always));
+ for (searchconff = otherpkg->installed.conffiles;
+ searchconff && strcmp(newconff->namenode->name, searchconff->name);
+ searchconff = searchconff->next)
+ debug(dbg_conffdetail,
+ "process_archive conffile '%s' in package %s - conff ? not '%s'",
+ newconff->namenode->name, pkg_name(otherpkg, pnaw_always),
+ searchconff->name);
+ if (searchconff) {
+ debug(dbg_conff,
+ "process_archive conffile '%s' package=%s %s hash=%s",
+ newconff->namenode->name, pkg_name(otherpkg, pnaw_always),
+ otherpkg == pkg ? "same" : "different!",
+ searchconff->hash);
+ if (otherpkg == pkg)
+ break;
+ }
+ }
+ fsys_node_pkgs_iter_free(iter);
+
+ if (searchconff) {
+ /* We don't copy ‘obsolete’; it's not obsolete in the new package. */
+ newconff->namenode->oldhash = searchconff->hash;
+ } else {
+ debug(dbg_conff, "process_archive conffile '%s' no package, no hash",
+ newconff->namenode->name);
+ }
+ newconff->namenode->flags |= confflags;
+ }
+
+ if (ferror(conff))
+ ohshite(_("read error in %.250s"), control_conffiles);
+ pop_cleanup(ehflag_normaltidy); /* conff = fopen() */
+ if (fclose(conff))
+ ohshite(_("error closing %.250s"), control_conffiles);
+}
+
+static struct pkg_queue conflictors = PKG_QUEUE_INIT;
+
+void
+enqueue_conflictor(struct pkginfo *pkg)
+{
+ pkg_queue_push(&conflictors, pkg);
+}
+
+static void
+pkg_infodb_remove_file(const char *filename, const char *filetype)
+{
+ if (unlink(filename))
+ ohshite(_("unable to delete control info file '%.250s'"), filename);
+
+ debug(dbg_scripts, "removal_bulk info unlinked %s", filename);
+}
+
+static struct match_node *match_head = NULL;
+
+static void
+pkg_infodb_update_file(const char *filename, const char *filetype)
+{
+ if (strlen(filetype) > MAXCONTROLFILENAME)
+ ohshit(_("old version of package has overly-long info file name starting '%.250s'"),
+ filename);
+
+ /* We do the list separately. */
+ if (strcmp(filetype, LISTFILE) == 0)
+ return;
+
+ /* We keep files to rename in a list as doing the rename immediately
+ * might influence the current readdir(), the just renamed file might
+ * be returned a second time as it's actually a new file from the
+ * point of view of the filesystem. */
+ match_head = match_node_new(filename, filetype, match_head);
+}
+
+static void
+pkg_infodb_update(struct pkginfo *pkg, char *cidir, char *cidirrest)
+{
+ struct match_node *match_node;
+ DIR *dsd;
+ struct dirent *de;
+
+ /* Deallocate the match list in case we aborted previously. */
+ while ((match_node = match_head)) {
+ match_head = match_node->next;
+ match_node_free(match_node);
+ }
+
+ pkg_infodb_foreach(pkg, &pkg->available, pkg_infodb_update_file);
+
+ while ((match_node = match_head)) {
+ strcpy(cidirrest, match_node->filetype);
+
+ if (!rename(cidir, match_node->filename)) {
+ debug(dbg_scripts, "process_archive info installed %s as %s",
+ cidir, match_node->filename);
+ } else if (errno == ENOENT) {
+ /* Right, no new version. */
+ if (unlink(match_node->filename))
+ ohshite(_("unable to remove obsolete info file '%.250s'"),
+ match_node->filename);
+ debug(dbg_scripts, "process_archive info unlinked %s",
+ match_node->filename);
+ } else {
+ ohshite(_("unable to install (supposed) new info file '%.250s'"), cidir);
+ }
+ match_head = match_node->next;
+ match_node_free(match_node);
+ }
+
+ /* The control directory itself. */
+ cidirrest[0] = '\0';
+ dsd = opendir(cidir);
+ if (!dsd)
+ ohshite(_("unable to open temp control directory"));
+ push_cleanup(cu_closedir, ~0, 1, (void *)dsd);
+ while ((de = readdir(dsd))) {
+ const char *newinfofilename;
+
+ if (strchr(de->d_name, '.')) {
+ debug(dbg_scripts, "process_archive tmp.ci script/file '%s' contains dot",
+ de->d_name);
+ continue;
+ }
+ if (strlen(de->d_name) > MAXCONTROLFILENAME)
+ ohshit(_("package contains overly-long control info file name (starting '%.50s')"),
+ de->d_name);
+
+ strcpy(cidirrest, de->d_name);
+
+ /* First we check it's not a directory. */
+ if (rmdir(cidir) == 0)
+ ohshit(_("package control info contained directory '%.250s'"), cidir);
+ else if (errno != ENOTDIR)
+ ohshite(_("package control info rmdir of '%.250s' didn't say not a dir"),
+ de->d_name);
+
+ /* Ignore the control file. */
+ if (strcmp(de->d_name, CONTROLFILE) == 0) {
+ debug(dbg_scripts, "process_archive tmp.ci script/file '%s' is control",
+ cidir);
+ continue;
+ }
+ if (strcmp(de->d_name, LISTFILE) == 0) {
+ warning(_("package %s contained list as info file"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig));
+ continue;
+ }
+
+ /* Right, install it */
+ newinfofilename = pkg_infodb_get_file(pkg, &pkg->available, de->d_name);
+ if (rename(cidir, newinfofilename))
+ ohshite(_("unable to install new info file '%.250s' as '%.250s'"),
+ cidir, newinfofilename);
+
+ debug(dbg_scripts,
+ "process_archive tmp.ci script/file '%s' installed as '%s'",
+ cidir, newinfofilename);
+ }
+ pop_cleanup(ehflag_normaltidy); /* closedir */
+
+ /* If the old and new versions use a different infodb layout, get rid
+ * of the files using the old layout. */
+ if (pkg->installed.multiarch != pkg->available.multiarch &&
+ (pkg->installed.multiarch == PKG_MULTIARCH_SAME ||
+ pkg->available.multiarch == PKG_MULTIARCH_SAME)) {
+ debug(dbg_scripts,
+ "process_archive remove old info files after db layout switch");
+ pkg_infodb_foreach(pkg, &pkg->installed, pkg_infodb_remove_file);
+ }
+
+ dir_sync_path(pkg_infodb_get_dir());
+}
+
+static void
+pkg_remove_conffile_on_upgrade(struct pkginfo *pkg, struct fsys_namenode *namenode)
+{
+ struct pkginfo *otherpkg;
+ struct fsys_namenode *usenode;
+ struct fsys_node_pkgs_iter *iter;
+ struct varbuf cdr = VARBUF_INIT;
+ struct varbuf cdrext = VARBUF_INIT;
+ struct varbuf_state cdrext_state;
+ char currenthash[MD5HASHLEN + 1];
+ int rc;
+
+ usenode = namenodetouse(namenode, pkg, &pkg->installed);
+
+ rc = conffderef(pkg, &cdr, usenode->name);
+ if (rc == -1) {
+ debug(dbg_conffdetail, "%s: '%s' conffile dereference error: %s", __func__,
+ namenode->name, strerror(errno));
+ namenode->oldhash = EMPTYHASHFLAG;
+ return;
+ }
+
+ varbuf_set_varbuf(&cdrext, &cdr);
+ varbuf_snapshot(&cdrext, &cdrext_state);
+
+ iter = fsys_node_pkgs_iter_new(namenode);
+ while ((otherpkg = fsys_node_pkgs_iter_next(iter))) {
+ debug(dbg_conffdetail, "%s: namenode '%s' owned by other %s?",
+ __func__, namenode->name, pkg_name(otherpkg, pnaw_always));
+
+ if (otherpkg == pkg)
+ continue;
+
+ debug(dbg_conff, "%s: namenode '%s' owned by other %s, remove-on-upgrade ignored",
+ __func__, namenode->name, pkg_name(otherpkg, pnaw_always));
+ fsys_node_pkgs_iter_free(iter);
+ return;
+ }
+ fsys_node_pkgs_iter_free(iter);
+
+ /* Remove DPKGDISTEXT variant if still present. */
+ varbuf_rollback(&cdrext_state);
+ varbuf_add_str(&cdrext, DPKGDISTEXT);
+ varbuf_end_str(&cdrext);
+
+ if (unlink(cdrext.buf) < 0 && errno != ENOENT)
+ warning(_("%s: failed to remove '%.250s': %s"),
+ pkg_name(pkg, pnaw_nonambig), cdrext.buf,
+ strerror(errno));
+
+ md5hash(pkg, currenthash, cdr.buf);
+
+ /* Has it been already removed (e.g. by local admin)? */
+ if (strcmp(currenthash, NONEXISTENTFLAG) == 0)
+ return;
+
+ /* For unmodified conffiles, we just remove them. */
+ if (strcmp(currenthash, namenode->oldhash) == 0) {
+ printf(_("Removing obsolete conffile %s ...\n"), cdr.buf);
+ if (unlink(cdr.buf) < 0 && errno != ENOENT)
+ warning(_("%s: failed to remove '%.250s': %s"),
+ pkg_name(pkg, pnaw_nonambig), cdr.buf, strerror(errno));
+ return;
+ }
+
+ /* Otherwise, preserve the modified conffile. */
+ varbuf_rollback(&cdrext_state);
+ varbuf_add_str(&cdrext, DPKGOLDEXT);
+ varbuf_end_str(&cdrext);
+
+ printf(_("Obsolete conffile '%s' has been modified by you.\n"), cdr.buf);
+ printf(_("Saving as %s ...\n"), cdrext.buf);
+ if (rename(cdr.buf, cdrext.buf) < 0)
+ warning(_("%s: cannot rename obsolete conffile '%s' to '%s': %s"),
+ pkg_name(pkg, pnaw_nonambig),
+ cdr.buf, cdrext.buf, strerror(errno));
+}
+
+static void
+pkg_remove_old_files(struct pkginfo *pkg,
+ struct fsys_namenode_queue *newfiles_queue,
+ struct fsys_namenode_queue *newconffiles)
+{
+ struct fsys_hash_rev_iter rev_iter;
+ struct fsys_namenode_list *cfile;
+ struct fsys_namenode *namenode;
+ struct stat stab, oldfs;
+
+ /* Before removing any old files, we try to remove obsolete conffiles that
+ * have been requested to be removed during upgrade. These conffiles are
+ * not tracked as part of the package file lists, so removing them here
+ * means we will get their parent directories removed if not present in the
+ * new package without needing to do anything special ourselves. */
+ for (cfile = newconffiles->head; cfile; cfile = cfile->next) {
+ debug(dbg_conffdetail, "%s: removing conffile '%s' for %s?", __func__,
+ cfile->namenode->name, pkg_name(pkg, pnaw_always));
+
+ if (!(cfile->namenode->flags & FNNF_RM_CONFF_ON_UPGRADE))
+ continue;
+
+ pkg_remove_conffile_on_upgrade(pkg, cfile->namenode);
+ }
+
+ fsys_hash_rev_iter_init(&rev_iter, pkg->files);
+
+ while ((namenode = fsys_hash_rev_iter_next(&rev_iter))) {
+ struct fsys_namenode *usenode;
+
+ if ((namenode->flags & FNNF_NEW_CONFF) ||
+ (namenode->flags & FNNF_RM_CONFF_ON_UPGRADE) ||
+ (namenode->flags & FNNF_NEW_INARCHIVE))
+ continue;
+
+ usenode = namenodetouse(namenode, pkg, &pkg->installed);
+
+ varbuf_rollback(&fname_state);
+ varbuf_add_str(&fnamevb, usenode->name);
+ varbuf_end_str(&fnamevb);
+
+ if (!stat(fnamevb.buf, &stab) && S_ISDIR(stab.st_mode)) {
+ debug(dbg_eachfiledetail, "process_archive: %s is a directory",
+ fnamevb.buf);
+ if (dir_is_used_by_others(namenode, pkg))
+ continue;
+ }
+
+ if (lstat(fnamevb.buf, &oldfs)) {
+ if (!(errno == ENOENT || errno == ELOOP || errno == ENOTDIR))
+ warning(_("could not stat old file '%.250s' so not deleting it: %s"),
+ fnamevb.buf, strerror(errno));
+ continue;
+ }
+ if (S_ISDIR(oldfs.st_mode)) {
+ trig_path_activate(usenode, pkg);
+
+ /* Do not try to remove the root directory. */
+ if (strcmp(usenode->name, "/.") == 0)
+ continue;
+
+ if (rmdir(fnamevb.buf)) {
+ warning(_("unable to delete old directory '%.250s': %s"),
+ namenode->name, strerror(errno));
+ } else if ((namenode->flags & FNNF_OLD_CONFF)) {
+ warning(_("old conffile '%.250s' was an empty directory "
+ "(and has now been deleted)"), namenode->name);
+ }
+ } else {
+ struct fsys_namenode_list *sameas = NULL;
+ static struct file_ondisk_id empty_ondisk_id;
+ struct varbuf cfilename = VARBUF_INIT;
+
+ /*
+ * Ok, it's an old file, but is it really not in the new package?
+ * It might be known by a different name because of symlinks.
+ *
+ * We need to check to make sure, so we stat the file, then compare
+ * it to the new list. If we find a dev/inode match, we assume they
+ * are the same file, and leave it alone. NOTE: we don't check in
+ * other packages for sanity reasons (we don't want to stat _all_
+ * the files on the system).
+ *
+ * We run down the list of _new_ files in this package. This keeps
+ * the process a little leaner. We are only worried about new ones
+ * since ones that stayed the same don't really apply here.
+ */
+
+ /* If we can't stat the old or new file, or it's a directory,
+ * we leave it up to the normal code. */
+ debug(dbg_eachfile, "process_archive: checking %s for same files on "
+ "upgrade/downgrade", fnamevb.buf);
+
+ for (cfile = newfiles_queue->head; cfile; cfile = cfile->next) {
+ /* If the file has been filtered then treat it as if it didn't exist
+ * on the file system. */
+ if (cfile->namenode->flags & FNNF_FILTERED)
+ continue;
+
+ if (cfile->namenode->file_ondisk_id == NULL) {
+ struct stat tmp_stat;
+
+ varbuf_set_str(&cfilename, dpkg_fsys_get_dir());
+ varbuf_add_str(&cfilename, cfile->namenode->name);
+ varbuf_end_str(&cfilename);
+
+ if (lstat(cfilename.buf, &tmp_stat) == 0) {
+ struct file_ondisk_id *file_ondisk_id;
+
+ file_ondisk_id = nfmalloc(sizeof(*file_ondisk_id));
+ file_ondisk_id->id_dev = tmp_stat.st_dev;
+ file_ondisk_id->id_ino = tmp_stat.st_ino;
+ cfile->namenode->file_ondisk_id = file_ondisk_id;
+ } else {
+ if (!(errno == ENOENT || errno == ELOOP || errno == ENOTDIR))
+ ohshite(_("unable to stat other new file '%.250s'"),
+ cfile->namenode->name);
+ cfile->namenode->file_ondisk_id = &empty_ondisk_id;
+ continue;
+ }
+ }
+
+ if (cfile->namenode->file_ondisk_id == &empty_ondisk_id)
+ continue;
+
+ if (oldfs.st_dev == cfile->namenode->file_ondisk_id->id_dev &&
+ oldfs.st_ino == cfile->namenode->file_ondisk_id->id_ino) {
+ if (sameas)
+ warning(_("old file '%.250s' is the same as several new files! "
+ "(both '%.250s' and '%.250s')"), fnamevb.buf,
+ sameas->namenode->name, cfile->namenode->name);
+ sameas = cfile;
+ debug(dbg_eachfile, "process_archive: not removing %s, "
+ "since it matches %s", fnamevb.buf, cfile->namenode->name);
+ }
+ }
+
+ varbuf_destroy(&cfilename);
+
+ if ((namenode->flags & FNNF_OLD_CONFF)) {
+ if (sameas) {
+ if (sameas->namenode->flags & FNNF_NEW_CONFF) {
+ if (strcmp(sameas->namenode->oldhash, NEWCONFFILEFLAG) == 0) {
+ sameas->namenode->oldhash = namenode->oldhash;
+ debug(dbg_eachfile, "process_archive: old conff %s "
+ "is same as new conff %s, copying hash",
+ namenode->name, sameas->namenode->name);
+ } else {
+ debug(dbg_eachfile, "process_archive: old conff %s "
+ "is same as new conff %s but latter already has hash",
+ namenode->name, sameas->namenode->name);
+ }
+ }
+ } else {
+ debug(dbg_eachfile, "process_archive: old conff %s "
+ "is disappearing", namenode->name);
+ namenode->flags |= FNNF_OBS_CONFF;
+ tar_fsys_namenode_queue_push(newconffiles, namenode);
+ tar_fsys_namenode_queue_push(newfiles_queue, namenode);
+ }
+ continue;
+ }
+
+ if (sameas)
+ continue;
+
+ trig_path_activate(usenode, pkg);
+
+ if (secure_unlink_statted(fnamevb.buf, &oldfs)) {
+ warning(_("unable to securely remove old file '%.250s': %s"),
+ namenode->name, strerror(errno));
+ }
+ } /* !S_ISDIR */
+ }
+}
+
+static void
+pkg_update_fields(struct pkginfo *pkg, struct fsys_namenode_queue *newconffiles)
+{
+ struct dependency *newdeplist, **newdeplistlastp;
+ struct dependency *dep;
+ struct conffile **iconffileslastp, *newiconff;
+ struct fsys_namenode_list *cfile;
+
+ /* The dependencies are the most difficult. We have to build
+ * a whole new forward dependency tree. At least the reverse
+ * links (linking our deppossi's into the reverse chains)
+ * can be done by copy_dependency_links. */
+ newdeplist = NULL;
+ newdeplistlastp = &newdeplist;
+ for (dep = pkg->available.depends; dep; dep = dep->next) {
+ struct deppossi **newpossilastp, *possi;
+ struct dependency *newdep;
+
+ newdep = nfmalloc(sizeof(*newdep));
+ newdep->up = pkg;
+ newdep->next = NULL;
+ newdep->list = NULL;
+ newpossilastp = &newdep->list;
+
+ for (possi = dep->list; possi; possi = possi->next) {
+ struct deppossi *newpossi;
+
+ newpossi = nfmalloc(sizeof(*newpossi));
+ newpossi->up = newdep;
+ newpossi->ed = possi->ed;
+ newpossi->next = NULL;
+ newpossi->rev_next = newpossi->rev_prev = NULL;
+ newpossi->arch_is_implicit = possi->arch_is_implicit;
+ newpossi->arch = possi->arch;
+ newpossi->verrel = possi->verrel;
+ if (possi->verrel != DPKG_RELATION_NONE)
+ newpossi->version = possi->version;
+ else
+ dpkg_version_blank(&newpossi->version);
+ newpossi->cyclebreak = false;
+ *newpossilastp = newpossi;
+ newpossilastp = &newpossi->next;
+ }
+ newdep->type = dep->type;
+ *newdeplistlastp = newdep;
+ newdeplistlastp = &newdep->next;
+ }
+
+ /* Right, now we've replicated the forward tree, we
+ * get copy_dependency_links to remove all the old dependency
+ * structures from the reverse links and add the new dependency
+ * structures in instead. It also copies the new dependency
+ * structure pointer for this package into the right field. */
+ copy_dependency_links(pkg, &pkg->installed.depends, newdeplist, 0);
+
+ /* We copy the text fields. */
+ pkg->installed.essential = pkg->available.essential;
+ pkg->installed.is_protected = pkg->available.is_protected;
+ pkg->installed.multiarch = pkg->available.multiarch;
+ pkg->installed.description = pkg->available.description;
+ pkg->installed.maintainer = pkg->available.maintainer;
+ pkg->installed.source = pkg->available.source;
+ pkg->installed.arch = pkg->available.arch;
+ pkg->installed.pkgname_archqual = pkg->available.pkgname_archqual;
+ pkg->installed.installedsize = pkg->available.installedsize;
+ pkg->installed.version = pkg->available.version;
+ pkg->installed.origin = pkg->available.origin;
+ pkg->installed.bugs = pkg->available.bugs;
+
+ /* We have to generate our own conffiles structure. */
+ pkg->installed.conffiles = NULL;
+ iconffileslastp = &pkg->installed.conffiles;
+ for (cfile = newconffiles->head; cfile; cfile = cfile->next) {
+ newiconff = nfmalloc(sizeof(*newiconff));
+ newiconff->next = NULL;
+ newiconff->name = nfstrsave(cfile->namenode->name);
+ newiconff->hash = nfstrsave(cfile->namenode->oldhash);
+ newiconff->obsolete = !!(cfile->namenode->flags & FNNF_OBS_CONFF);
+ newiconff->remove_on_upgrade = !!(
+ cfile->namenode->flags & FNNF_RM_CONFF_ON_UPGRADE);
+ *iconffileslastp = newiconff;
+ iconffileslastp = &newiconff->next;
+ }
+
+ /* We can just copy the arbitrary fields list, because it is
+ * never even rearranged. Phew! */
+ pkg->installed.arbs = pkg->available.arbs;
+}
+
+static void
+pkg_disappear(struct pkginfo *pkg, struct pkginfo *infavour)
+{
+ printf(_("(Noting disappearance of %s, which has been completely replaced.)\n"),
+ pkg_name(pkg, pnaw_nonambig));
+ log_action("disappear", pkg, &pkg->installed);
+ debug(dbg_general, "pkg_disappear disappearing %s",
+ pkg_name(pkg, pnaw_always));
+
+ trig_activate_packageprocessing(pkg);
+ maintscript_installed(pkg, POSTRMFILE,
+ "post-removal script (for disappearance)",
+ "disappear",
+ pkgbin_name(infavour, &infavour->available,
+ pnaw_nonambig),
+ versiondescribe(&infavour->available.version,
+ vdew_nonambig),
+ NULL);
+
+ /* OK, now we delete all the stuff in the ‘info’ directory ... */
+ debug(dbg_general, "pkg_disappear cleaning info directory");
+ pkg_infodb_foreach(pkg, &pkg->installed, pkg_infodb_remove_file);
+ dir_sync_path(pkg_infodb_get_dir());
+
+ pkg_set_status(pkg, PKG_STAT_NOTINSTALLED);
+ pkg_set_want(pkg, PKG_WANT_UNKNOWN);
+ pkg_reset_eflags(pkg);
+
+ dpkg_version_blank(&pkg->configversion);
+ pkgbin_blank(&pkg->installed);
+
+ pkg->files_list_valid = false;
+
+ modstatdb_note(pkg);
+}
+
+static void
+pkg_disappear_others(struct pkginfo *pkg)
+{
+ struct pkg_hash_iter *iter;
+ struct pkginfo *otherpkg;
+ struct fsys_namenode_list *cfile;
+ struct deppossi *pdep;
+ struct dependency *providecheck;
+ struct varbuf depprobwhy = VARBUF_INIT;
+
+ iter = pkg_hash_iter_new();
+ while ((otherpkg = pkg_hash_iter_next_pkg(iter)) != NULL) {
+ ensure_package_clientdata(otherpkg);
+
+ if (otherpkg == pkg ||
+ otherpkg->status == PKG_STAT_NOTINSTALLED ||
+ otherpkg->status == PKG_STAT_CONFIGFILES ||
+ otherpkg->clientdata->istobe == PKG_ISTOBE_REMOVE ||
+ !otherpkg->files)
+ continue;
+
+ /* Do not try to disappear other packages from the same set
+ * if they are Multi-Arch: same */
+ if (pkg->installed.multiarch == PKG_MULTIARCH_SAME &&
+ otherpkg->installed.multiarch == PKG_MULTIARCH_SAME &&
+ otherpkg->set == pkg->set)
+ continue;
+
+ debug(dbg_veryverbose, "process_archive checking disappearance %s",
+ pkg_name(otherpkg, pnaw_always));
+
+ if (otherpkg->clientdata->istobe != PKG_ISTOBE_NORMAL &&
+ otherpkg->clientdata->istobe != PKG_ISTOBE_DECONFIGURE)
+ internerr("disappearing package %s is not to be normal or deconfigure, "
+ "is to be %d",
+ pkg_name(otherpkg, pnaw_always), otherpkg->clientdata->istobe);
+
+ for (cfile = otherpkg->files;
+ cfile && strcmp(cfile->namenode->name, "/.") == 0;
+ cfile = cfile->next);
+ if (!cfile) {
+ debug(dbg_stupidlyverbose, "process_archive no non-root, no disappear");
+ continue;
+ }
+ for (cfile = otherpkg->files;
+ cfile && !filesavespackage(cfile, otherpkg, pkg);
+ cfile = cfile->next);
+ if (cfile)
+ continue;
+
+ /* So dependency things will give right answers ... */
+ otherpkg->clientdata->istobe = PKG_ISTOBE_REMOVE;
+ debug(dbg_veryverbose, "process_archive disappear checking dependencies");
+ for (pdep = otherpkg->set->depended.installed;
+ pdep;
+ pdep = pdep->rev_next) {
+ if (pdep->up->type != dep_depends &&
+ pdep->up->type != dep_predepends &&
+ pdep->up->type != dep_recommends)
+ continue;
+
+ if (depisok(pdep->up, &depprobwhy, NULL, NULL, false))
+ continue;
+
+ varbuf_end_str(&depprobwhy);
+ debug(dbg_veryverbose,"process_archive cannot disappear: %s",
+ depprobwhy.buf);
+ break;
+ }
+ if (!pdep) {
+ /* If we haven't found a reason not to yet, let's look some more. */
+ for (providecheck = otherpkg->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 &&
+ pdep->up->type != dep_recommends)
+ continue;
+
+ if (depisok(pdep->up, &depprobwhy, NULL, NULL, false))
+ continue;
+
+ varbuf_end_str(&depprobwhy);
+ debug(dbg_veryverbose,
+ "process_archive cannot disappear (provides %s): %s",
+ providecheck->list->ed->name, depprobwhy.buf);
+ goto break_from_both_loops_at_once;
+ }
+ }
+ break_from_both_loops_at_once:;
+ }
+ otherpkg->clientdata->istobe = PKG_ISTOBE_NORMAL;
+ if (pdep)
+ continue;
+
+ /* No, we're disappearing it. This is the wrong time to go and
+ * run maintainer scripts and things, as we can't back out. But
+ * what can we do ? It has to be run this late. */
+ pkg_disappear(otherpkg, pkg);
+ } /* while (otherpkg= ... */
+ pkg_hash_iter_free(iter);
+}
+
+/**
+ * Check if all instances of a pkgset are getting in sync.
+ *
+ * If that's the case, the extraction is going to ensure consistency
+ * of shared files.
+ */
+static bool
+pkgset_getting_in_sync(struct pkginfo *pkg)
+{
+ struct pkginfo *otherpkg;
+
+ for (otherpkg = &pkg->set->pkg; otherpkg; otherpkg = otherpkg->arch_next) {
+ if (otherpkg == pkg)
+ continue;
+ if (otherpkg->status <= PKG_STAT_CONFIGFILES)
+ continue;
+ if (dpkg_version_compare(&pkg->available.version,
+ &otherpkg->installed.version)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void
+pkg_remove_files_from_others(struct pkginfo *pkg, struct fsys_namenode_list *newfileslist)
+{
+ struct fsys_namenode_list *cfile;
+ struct pkginfo *otherpkg;
+
+ for (cfile = newfileslist; cfile; cfile = cfile->next) {
+ struct fsys_node_pkgs_iter *iter;
+ struct pkgset *divpkgset;
+
+ if (!(cfile->namenode->flags & FNNF_ELIDE_OTHER_LISTS))
+ continue;
+
+ if (cfile->namenode->divert && cfile->namenode->divert->useinstead) {
+ divpkgset = cfile->namenode->divert->pkgset;
+ if (divpkgset == pkg->set) {
+ debug(dbg_eachfile,
+ "process_archive not overwriting any '%s' (overriding, '%s')",
+ cfile->namenode->name, cfile->namenode->divert->useinstead->name);
+ continue;
+ } else {
+ debug(dbg_eachfile,
+ "process_archive looking for overwriting '%s' (overridden by %s)",
+ cfile->namenode->name, divpkgset ? divpkgset->name : "<local>");
+ }
+ } else {
+ divpkgset = NULL;
+ debug(dbg_eachfile, "process_archive looking for overwriting '%s'",
+ cfile->namenode->name);
+ }
+
+ iter = fsys_node_pkgs_iter_new(cfile->namenode);
+ while ((otherpkg = fsys_node_pkgs_iter_next(iter))) {
+ debug(dbg_eachfiledetail, "process_archive ... found in %s",
+ pkg_name(otherpkg, pnaw_always));
+
+ /* A pkgset can share files between instances, so there's no point
+ * in rewriting the file that's already in place. */
+ if (otherpkg->set == pkg->set)
+ continue;
+
+ if (otherpkg->set == divpkgset) {
+ debug(dbg_eachfiledetail, "process_archive ... diverted, skipping");
+ continue;
+ }
+
+ if (cfile->namenode->flags & FNNF_NEW_CONFF)
+ conffile_mark_obsolete(otherpkg, cfile->namenode);
+
+ /* If !files_list_valid then it's one of the disappeared packages above
+ * or we have already updated the files list file, and we don't bother
+ * with it here, clearly. */
+ if (!otherpkg->files_list_valid)
+ continue;
+
+ /* Found one. We delete the list entry for this file,
+ * (and any others in the same package) and then mark the package
+ * as requiring a reread. */
+ write_filelist_except(otherpkg, &otherpkg->installed,
+ otherpkg->files, FNNF_ELIDE_OTHER_LISTS);
+ debug(dbg_veryverbose, "process_archive overwrote from %s",
+ pkg_name(otherpkg, pnaw_always));
+ }
+ fsys_node_pkgs_iter_free(iter);
+ }
+}
+
+static void
+pkg_remove_backup_files(struct pkginfo *pkg, struct fsys_namenode_list *newfileslist)
+{
+ struct fsys_namenode_list *cfile;
+
+ for (cfile = newfileslist; cfile; cfile = cfile->next) {
+ struct fsys_namenode *usenode;
+
+ if (cfile->namenode->flags & FNNF_NEW_CONFF)
+ continue;
+
+ usenode = namenodetouse(cfile->namenode, pkg, &pkg->installed);
+
+ /* Do not try to remove backups for the root directory. */
+ if (strcmp(usenode->name, "/.") == 0)
+ continue;
+
+ varbuf_rollback(&fnametmp_state);
+ varbuf_add_str(&fnametmpvb, usenode->name);
+ varbuf_add_str(&fnametmpvb, DPKGTEMPEXT);
+ varbuf_end_str(&fnametmpvb);
+ path_remove_tree(fnametmpvb.buf);
+ }
+}
+
+void process_archive(const char *filename) {
+ static const struct tar_operations tf = {
+ .read = tarfileread,
+ .extract_file = tarobject,
+ .link = tarobject,
+ .symlink = tarobject,
+ .mkdir = tarobject,
+ .mknod = tarobject,
+ };
+
+ /* These need to be static so that we can pass their addresses to
+ * push_cleanup as arguments to the cu_xxx routines; if an error occurs
+ * we unwind the stack before processing the cleanup list, and these
+ * variables had better still exist ... */
+ static int p1[2];
+ static enum pkgstatus oldversionstatus;
+ static struct tarcontext tc;
+
+ struct tar_archive tar;
+ struct dpkg_error err;
+ enum parsedbflags parsedb_flags;
+ int rc;
+ pid_t pid;
+ struct pkginfo *pkg, *otherpkg;
+ struct pkg_list *conflictor_iter;
+ char *cidir = NULL;
+ char *cidirrest;
+ char *psize;
+ const char *pfilename;
+ struct fsys_namenode_queue newconffiles, newfiles_queue;
+ struct stat stab;
+
+ cleanup_pkg_failed= cleanup_conflictor_failed= 0;
+
+ pfilename = summarize_filename(filename);
+
+ if (stat(filename, &stab))
+ ohshite(_("cannot access archive '%s'"), filename);
+
+ /* We can't ‘tentatively-reassemble’ packages. */
+ if (!f_noact) {
+ if (!deb_reassemble(&filename, &pfilename))
+ return;
+ }
+
+ /* Verify the package. */
+ if (!f_nodebsig)
+ deb_verify(filename);
+
+ /* Get the control information directory. */
+ cidir = get_control_dir(cidir);
+ cidirrest = cidir + strlen(cidir);
+ push_cleanup(cu_cidir, ~0, 2, (void *)cidir, (void *)cidirrest);
+
+ pid = subproc_fork();
+ if (pid == 0) {
+ cidirrest[-1] = '\0';
+ execlp(BACKEND, BACKEND, "--control", filename, cidir, NULL);
+ ohshite(_("unable to execute %s (%s)"),
+ _("package control information extraction"), BACKEND);
+ }
+ subproc_reap(pid, BACKEND " --control", 0);
+
+ /* We want to guarantee the extracted files are on the disk, so that the
+ * subsequent renames to the info database do not end up with old or zero
+ * length files in case of a system crash. As neither dpkg-deb nor tar do
+ * explicit fsync()s, we have to do them here.
+ * XXX: This could be avoided by switching to an internal tar extractor. */
+ dir_sync_contents(cidir);
+
+ strcpy(cidirrest,CONTROLFILE);
+
+ if (cipaction->arg_int == act_avail)
+ parsedb_flags = pdb_parse_available;
+ else
+ parsedb_flags = pdb_parse_binary;
+ parsedb_flags |= pdb_ignore_archives;
+ if (in_force(FORCE_BAD_VERSION))
+ parsedb_flags |= pdb_lax_version_parser;
+
+ parsedb(cidir, parsedb_flags, &pkg);
+
+ if (!pkg->archives) {
+ pkg->archives = nfmalloc(sizeof(*pkg->archives));
+ pkg->archives->next = NULL;
+ pkg->archives->name = NULL;
+ pkg->archives->msdosname = NULL;
+ pkg->archives->md5sum = NULL;
+ }
+ /* Always nfmalloc. Otherwise, we may overwrite some other field (like
+ * md5sum). */
+ psize = nfmalloc(30);
+ sprintf(psize, "%jd", (intmax_t)stab.st_size);
+ pkg->archives->size = psize;
+
+ if (cipaction->arg_int == act_avail) {
+ printf(_("Recorded info about %s from %s.\n"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig), pfilename);
+ pop_cleanup(ehflag_normaltidy);
+ return;
+ }
+
+ if (pkg->available.arch->type != DPKG_ARCH_ALL &&
+ pkg->available.arch->type != DPKG_ARCH_NATIVE &&
+ pkg->available.arch->type != DPKG_ARCH_FOREIGN)
+ forcibleerr(FORCE_ARCHITECTURE,
+ _("package architecture (%s) does not match system (%s)"),
+ pkg->available.arch->name,
+ dpkg_arch_get(DPKG_ARCH_NATIVE)->name);
+
+ clear_deconfigure_queue();
+ clear_istobes();
+
+ if (wanttoinstall(pkg)) {
+ pkg_set_want(pkg, PKG_WANT_INSTALL);
+ } else {
+ pop_cleanup(ehflag_normaltidy);
+ return;
+ }
+
+ /* Deconfigure other instances from a pkgset if they are not in sync. */
+ for (otherpkg = &pkg->set->pkg; otherpkg; otherpkg = otherpkg->arch_next) {
+ if (otherpkg == pkg)
+ continue;
+ if (otherpkg->status <= PKG_STAT_HALFCONFIGURED)
+ continue;
+
+ if (dpkg_version_compare(&pkg->available.version,
+ &otherpkg->installed.version))
+ enqueue_deconfigure(otherpkg, NULL, PKG_WANT_UNKNOWN);
+ }
+
+ pkg_check_depcon(pkg, pfilename);
+
+ ensure_allinstfiles_available();
+ fsys_hash_init();
+ trig_file_interests_ensure();
+
+ printf(_("Preparing to unpack %s ...\n"), pfilename);
+
+ if (pkg->status != PKG_STAT_NOTINSTALLED &&
+ pkg->status != PKG_STAT_CONFIGFILES) {
+ log_action("upgrade", pkg, &pkg->installed);
+ } else {
+ log_action("install", pkg, &pkg->available);
+ }
+
+ if (f_noact) {
+ pop_cleanup(ehflag_normaltidy);
+ return;
+ }
+
+ /*
+ * OK, we're going ahead.
+ */
+
+ trig_activate_packageprocessing(pkg);
+ strcpy(cidirrest, TRIGGERSCIFILE);
+ trig_parse_ci(cidir, NULL, trig_cicb_statuschange_activate, pkg, &pkg->available);
+
+ /* Read the conffiles, and copy the hashes across. */
+ newconffiles.head = NULL;
+ newconffiles.tail = &newconffiles.head;
+ push_cleanup(cu_fileslist, ~0, 0);
+ strcpy(cidirrest,CONFFILESFILE);
+ deb_parse_conffiles(pkg, cidir, &newconffiles);
+
+ /* All the old conffiles are marked with a flag, so that we don't delete
+ * them if they seem to disappear completely. */
+ pkg_conffiles_mark_old(pkg);
+ for (conflictor_iter = conflictors.head;
+ conflictor_iter;
+ conflictor_iter = conflictor_iter->next)
+ pkg_conffiles_mark_old(conflictor_iter->pkg);
+
+ oldversionstatus= pkg->status;
+
+ if (oldversionstatus > PKG_STAT_INSTALLED)
+ internerr("package %s state %d is out-of-bounds",
+ pkg_name(pkg, pnaw_always), oldversionstatus);
+
+ debug(dbg_general,"process_archive oldversionstatus=%s",
+ statusstrings[oldversionstatus]);
+
+ if (oldversionstatus == PKG_STAT_HALFCONFIGURED ||
+ oldversionstatus == PKG_STAT_TRIGGERSAWAITED ||
+ oldversionstatus == PKG_STAT_TRIGGERSPENDING ||
+ oldversionstatus == PKG_STAT_INSTALLED) {
+ pkg_set_eflags(pkg, PKG_EFLAG_REINSTREQ);
+ pkg_set_status(pkg, PKG_STAT_HALFCONFIGURED);
+ modstatdb_note(pkg);
+ push_cleanup(cu_prermupgrade, ~ehflag_normaltidy, 1, (void *)pkg);
+ maintscript_fallback(pkg, PRERMFILE, "pre-removal", cidir, cidirrest,
+ "upgrade", "failed-upgrade");
+ pkg_set_status(pkg, PKG_STAT_UNPACKED);
+ oldversionstatus = PKG_STAT_UNPACKED;
+ modstatdb_note(pkg);
+ }
+
+ pkg_deconfigure_others(pkg);
+
+ for (conflictor_iter = conflictors.head;
+ conflictor_iter;
+ conflictor_iter = conflictor_iter->next) {
+ struct pkginfo *conflictor = conflictor_iter->pkg;
+
+ if (!(conflictor->status == PKG_STAT_HALFCONFIGURED ||
+ conflictor->status == PKG_STAT_TRIGGERSAWAITED ||
+ conflictor->status == PKG_STAT_TRIGGERSPENDING ||
+ conflictor->status == PKG_STAT_INSTALLED))
+ continue;
+
+ trig_activate_packageprocessing(conflictor);
+ pkg_set_status(conflictor, PKG_STAT_HALFCONFIGURED);
+ modstatdb_note(conflictor);
+ push_cleanup(cu_prerminfavour, ~ehflag_normaltidy,
+ 2, conflictor, pkg);
+ maintscript_installed(conflictor, PRERMFILE, "pre-removal",
+ "remove", "in-favour",
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig),
+ versiondescribe(&pkg->available.version,
+ vdew_nonambig),
+ NULL);
+ pkg_set_status(conflictor, PKG_STAT_HALFINSTALLED);
+ modstatdb_note(conflictor);
+ }
+
+ pkg_set_eflags(pkg, PKG_EFLAG_REINSTREQ);
+ if (pkg->status == PKG_STAT_NOTINSTALLED) {
+ pkg->installed.version= pkg->available.version;
+ pkg->installed.multiarch = pkg->available.multiarch;
+ }
+ pkg_set_status(pkg, PKG_STAT_HALFINSTALLED);
+ modstatdb_note(pkg);
+ if (oldversionstatus == PKG_STAT_NOTINSTALLED) {
+ push_cleanup(cu_preinstverynew, ~ehflag_normaltidy,
+ 3,(void*)pkg,(void*)cidir,(void*)cidirrest);
+ maintscript_new(pkg, PREINSTFILE, "pre-installation", cidir, cidirrest,
+ "install", NULL);
+ } else if (oldversionstatus == PKG_STAT_CONFIGFILES) {
+ push_cleanup(cu_preinstnew, ~ehflag_normaltidy,
+ 3,(void*)pkg,(void*)cidir,(void*)cidirrest);
+ maintscript_new(pkg, PREINSTFILE, "pre-installation", cidir, cidirrest,
+ "install",
+ versiondescribe(&pkg->installed.version, vdew_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ NULL);
+ } else {
+ push_cleanup(cu_preinstupgrade, ~ehflag_normaltidy,
+ 4,(void*)pkg,(void*)cidir,(void*)cidirrest,(void*)&oldversionstatus);
+ maintscript_new(pkg, PREINSTFILE, "pre-installation", cidir, cidirrest,
+ "upgrade",
+ versiondescribe(&pkg->installed.version, vdew_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ NULL);
+ }
+
+ if (oldversionstatus == PKG_STAT_NOTINSTALLED ||
+ oldversionstatus == PKG_STAT_CONFIGFILES) {
+ printf(_("Unpacking %s (%s) ...\n"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig));
+ } else {
+ printf(_("Unpacking %s (%s) over (%s) ...\n"),
+ pkgbin_name(pkg, &pkg->available, pnaw_nonambig),
+ versiondescribe(&pkg->available.version, vdew_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig));
+ }
+
+ /*
+ * Now we unpack the archive, backing things up as we go.
+ * For each file, we check to see if it already exists.
+ * There are several possibilities:
+ *
+ * + We are trying to install a non-directory ...
+ * - It doesn't exist. In this case we simply extract it.
+ * - It is a plain file, device, symlink, &c. We do an ‘atomic
+ * overwrite’ using link() and rename(), but leave a backup copy.
+ * Later, when we delete the backup, we remove it from any other
+ * packages' lists.
+ * - It is a directory. In this case it depends on whether we're
+ * trying to install a symlink or something else.
+ * = If we're not trying to install a symlink we move the directory
+ * aside and extract the node. Later, when we recursively remove
+ * the backed-up directory, we remove it from any other packages'
+ * lists.
+ * = If we are trying to install a symlink we do nothing - ie,
+ * dpkg will never replace a directory tree with a symlink. This
+ * is to avoid embarrassing effects such as replacing a directory
+ * tree with a link to a link to the original directory tree.
+ * + We are trying to install a directory ...
+ * - It doesn't exist. We create it with the appropriate modes.
+ * - It exists as a directory or a symlink to one. We do nothing.
+ * - It is a plain file or a symlink (other than to a directory).
+ * We move it aside and create the directory. Later, when we
+ * delete the backup, we remove it from any other packages' lists.
+ *
+ * Install non-dir Install symlink Install dir
+ * Exists not X X X
+ * File/node/symlink LXR LXR BXR
+ * Directory BXR - -
+ *
+ * X: extract file/node/link/directory
+ * LX: atomic overwrite leaving backup
+ * B: ordinary backup
+ * R: later remove from other packages' lists
+ * -: do nothing
+ *
+ * After we've done this we go through the remaining things in the
+ * lists of packages we're trying to remove (including the old
+ * version of the current package). This happens in reverse order,
+ * so that we process files before the directories (or symlinks-to-
+ * directories) containing them.
+ *
+ * + If the thing is a conffile then we leave it alone for the purge
+ * operation.
+ * + Otherwise, there are several possibilities too:
+ * - The listed thing does not exist. We ignore it.
+ * - The listed thing is a directory or a symlink to a directory.
+ * We delete it only if it isn't listed in any other package.
+ * - The listed thing is not a directory, but was part of the package
+ * that was upgraded, we check to make sure the files aren't the
+ * same ones from the old package by checking dev/inode
+ * - The listed thing is not a directory or a symlink to one (ie,
+ * it's a plain file, device, pipe, &c, or a symlink to one, or a
+ * dangling symlink). We delete it.
+ *
+ * The removed packages' list becomes empty (of course, the new
+ * version of the package we're installing will have a new list,
+ * which replaces the old version's list).
+ *
+ * If at any stage we remove a file from a package's list, and the
+ * package isn't one we're already processing, and the package's
+ * list becomes empty as a result, we ‘vanish’ the package. This
+ * means that we run its postrm with the ‘disappear’ argument, and
+ * put the package in the ‘not-installed’ state. If it had any
+ * conffiles, their hashes and ownership will have been transferred
+ * already, so we just ignore those and forget about them from the
+ * point of view of the disappearing package.
+ *
+ * NOTE THAT THE OLD POSTRM IS RUN AFTER THE NEW PREINST, since the
+ * files get replaced ‘as we go’.
+ */
+
+ m_pipe(p1);
+ push_cleanup(cu_closepipe, ehflag_bombout, 1, (void *)&p1[0]);
+ pid = subproc_fork();
+ if (pid == 0) {
+ m_dup2(p1[1],1); close(p1[0]); close(p1[1]);
+ execlp(BACKEND, BACKEND, "--fsys-tarfile", filename, NULL);
+ ohshite(_("unable to execute %s (%s)"),
+ _("package filesystem archive extraction"), BACKEND);
+ }
+ close(p1[1]);
+ p1[1] = -1;
+
+ newfiles_queue.head = NULL;
+ newfiles_queue.tail = &newfiles_queue.head;
+ tc.newfiles_queue = &newfiles_queue;
+ push_cleanup(cu_fileslist, ~0, 0);
+ tc.pkg= pkg;
+ tc.backendpipe= p1[0];
+ tc.pkgset_getting_in_sync = pkgset_getting_in_sync(pkg);
+
+ /* Setup the tar archive. */
+ tar.err = DPKG_ERROR_OBJECT;
+ tar.ctx = &tc;
+ tar.ops = &tf;
+
+ rc = tar_extractor(&tar);
+ if (rc)
+ dpkg_error_print(&tar.err,
+ _("corrupted filesystem tarfile in package archive"));
+ if (fd_skip(p1[0], -1, &err) < 0)
+ ohshit(_("cannot zap possible trailing zeros from dpkg-deb: %s"), err.str);
+ close(p1[0]);
+ p1[0] = -1;
+ subproc_reap(pid, BACKEND " --fsys-tarfile", SUBPROC_NOPIPE);
+
+ tar_deferred_extract(newfiles_queue.head, pkg);
+
+ if (oldversionstatus == PKG_STAT_HALFINSTALLED ||
+ oldversionstatus == PKG_STAT_UNPACKED) {
+ /* Packages that were in ‘installed’ and ‘postinstfailed’ have been
+ * reduced to ‘unpacked’ by now, by the running of the prerm script. */
+ pkg_set_status(pkg, PKG_STAT_HALFINSTALLED);
+ modstatdb_note(pkg);
+ push_cleanup(cu_postrmupgrade, ~ehflag_normaltidy, 1, (void *)pkg);
+ maintscript_fallback(pkg, POSTRMFILE, "post-removal", cidir, cidirrest,
+ "upgrade", "failed-upgrade");
+ }
+
+ /* If anything goes wrong while tidying up it's a bit late to do
+ * anything about it. However, we don't install the new status
+ * info yet, so that a future dpkg installation will put everything
+ * right (we hope).
+ *
+ * If something does go wrong later the ‘conflictor’ package will be
+ * left in the ‘removal_failed’ state. Removing or installing it
+ * will be impossible if it was required because of the conflict with
+ * the package we're installing now and (presumably) the dependency
+ * by other packages. This means that the files it contains in
+ * common with this package will hang around until we successfully
+ * get this package installed, after which point we can trust the
+ * conflicting package's file list, which will have been updated to
+ * remove any files in this package. */
+ push_checkpoint(~ehflag_bombout, ehflag_normaltidy);
+
+ /* Now we delete all the files that were in the old version of
+ * the package only, except (old or new) conffiles, which we leave
+ * alone. */
+ pkg_remove_old_files(pkg, &newfiles_queue, &newconffiles);
+
+ /* OK, now we can write the updated files-in-this package list,
+ * since we've done away (hopefully) with all the old junk. */
+ write_filelist_except(pkg, &pkg->available, newfiles_queue.head, 0);
+
+ /* Trigger interests may have changed.
+ * Firstly we go through the old list of interests deleting them.
+ * Then we go through the new list adding them. */
+ strcpy(cidirrest, TRIGGERSCIFILE);
+ trig_parse_ci(pkg_infodb_get_file(pkg, &pkg->installed, TRIGGERSCIFILE),
+ trig_cicb_interest_delete, NULL, pkg, &pkg->installed);
+ trig_parse_ci(cidir, trig_cicb_interest_add, NULL, pkg, &pkg->available);
+ trig_file_interests_save();
+
+ /* We also install the new maintainer scripts, and any other
+ * cruft that may have come along with the package. First
+ * we go through the existing scripts replacing or removing
+ * them as appropriate; then we go through the new scripts
+ * (any that are left) and install them. */
+ debug(dbg_general, "process_archive updating info directory");
+ pkg_infodb_update(pkg, cidir, cidirrest);
+
+ /* We store now the checksums dynamically computed while unpacking. */
+ write_filehash_except(pkg, &pkg->available, newfiles_queue.head, 0);
+
+ /*
+ * Update the status database.
+ *
+ * This involves copying each field across from the ‘available’
+ * to the ‘installed’ half of the pkg structure.
+ * For some of the fields we have to do a complicated construction
+ * operation; for others we can just copy the value.
+ * We tackle the fields in the order they appear, so that
+ * we don't miss any out :-).
+ * At least we don't have to copy any strings that are referred
+ * to, because these are never modified and never freed.
+ */
+ pkg_update_fields(pkg, &newconffiles);
+
+ /* In case this was an architecture cross-grade, the in-core pkgset might
+ * be in an inconsistent state, with two pkginfo entries having the same
+ * architecture, so let's fix that. Note that this does not lose data,
+ * as the pkg.available member parsed from the binary should replace the
+ * to be cleared duplicate in the other instance. */
+ for (otherpkg = &pkg->set->pkg; otherpkg; otherpkg = otherpkg->arch_next) {
+ if (otherpkg == pkg)
+ continue;
+ if (otherpkg->installed.arch != pkg->installed.arch)
+ continue;
+
+ if (otherpkg->status != PKG_STAT_NOTINSTALLED)
+ internerr("other package %s instance in state %s instead of not-installed",
+ pkg_name(otherpkg, pnaw_always), pkg_status_name(otherpkg));
+
+ pkg_blank(otherpkg);
+ }
+
+ /* Check for disappearing packages:
+ * We go through all the packages on the system looking for ones
+ * whose files are entirely part of the one we've just unpacked
+ * (and which actually *have* some files!).
+ *
+ * Any that we find are removed - we run the postrm with ‘disappear’
+ * as an argument, and remove their info/... files and status info.
+ * Conffiles are ignored (the new package had better do something
+ * with them!). */
+ pkg_disappear_others(pkg);
+
+ /* Delete files from any other packages' lists.
+ * We have to do this before we claim this package is in any
+ * sane kind of state, as otherwise we might delete by mistake
+ * a file that we overwrote, when we remove the package which
+ * had the version we overwrote. To prevent this we make
+ * sure that we don't claim this package is OK until we
+ * have claimed ‘ownership’ of all its files. */
+ pkg_remove_files_from_others(pkg, newfiles_queue.head);
+
+ /* Right, the package we've unpacked is now in a reasonable state.
+ * The only thing that we have left to do with it is remove
+ * backup files, and we can leave the user to fix that if and when
+ * it happens (we leave the reinstall required flag, of course). */
+ pkg_set_status(pkg, PKG_STAT_UNPACKED);
+ modstatdb_note(pkg);
+
+ /* Now we delete all the backup files that we made when
+ * extracting the archive - except for files listed as conffiles
+ * in the new package.
+ * This time we count it as an error if something goes wrong.
+ *
+ * Note that we don't ever delete things that were in the old
+ * package as a conffile and don't appear at all in the new.
+ * They stay recorded as obsolete conffiles and will eventually
+ * (if not taken over by another package) be forgotten. */
+ pkg_remove_backup_files(pkg, newfiles_queue.head);
+
+ /* OK, we're now fully done with the main package.
+ * This is quite a nice state, so we don't unwind past here. */
+ pkg_reset_eflags(pkg);
+ modstatdb_note(pkg);
+ push_checkpoint(~ehflag_bombout, ehflag_normaltidy);
+
+ /* Only the removal of the conflictor left to do.
+ * The files list for the conflictor is still a little inconsistent in-core,
+ * as we have not yet updated the filename->packages mappings; however,
+ * the package->filenames mapping is. */
+ while (!pkg_queue_is_empty(&conflictors)) {
+ struct pkginfo *conflictor = pkg_queue_pop(&conflictors);
+
+ /* We need to have the most up-to-date info about which files are
+ * what ... */
+ ensure_allinstfiles_available();
+ printf(_("Removing %s (%s), to allow configuration of %s (%s) ...\n"),
+ pkg_name(conflictor, pnaw_nonambig),
+ versiondescribe(&conflictor->installed.version, vdew_nonambig),
+ pkg_name(pkg, pnaw_nonambig),
+ versiondescribe(&pkg->installed.version, vdew_nonambig));
+ removal_bulk(conflictor);
+ }
+
+ if (cipaction->arg_int == act_install)
+ enqueue_package_mark_seen(pkg);
+}
diff --git a/src/main/update.c b/src/main/update.c
new file mode 100644
index 0000000..4a9a95b
--- /dev/null
+++ b/src/main/update.c
@@ -0,0 +1,124 @@
+/*
+ * dpkg - main program for package management
+ * update.c - options which update the ‘available’ database
+ *
+ * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/options.h>
+
+#include "main.h"
+
+int
+updateavailable(const char *const *argv)
+{
+ const char *sourcefile= argv[0];
+ char *availfile;
+ int count= 0;
+
+ modstatdb_init();
+
+ switch (cipaction->arg_int) {
+ case act_avclear:
+ if (sourcefile) badusage(_("--%s takes no arguments"),cipaction->olong);
+ break;
+ case act_avreplace: case act_avmerge:
+ if (sourcefile == NULL)
+ sourcefile = "-";
+ else if (argv[1])
+ badusage(_("--%s takes at most one Packages-file argument"),
+ cipaction->olong);
+ break;
+ default:
+ internerr("unknown action '%d'", cipaction->arg_int);
+ }
+
+ if (!f_noact) {
+ const char *dbdir = dpkg_db_get_dir();
+
+ if (access(dbdir, W_OK)) {
+ if (errno != EACCES)
+ ohshite(_("unable to access dpkg database directory '%s' for bulk available update"),
+ dbdir);
+ else
+ ohshit(_("required write access to dpkg database directory '%s' for bulk available update"),
+ dbdir);
+ }
+ modstatdb_lock();
+ }
+
+ switch (cipaction->arg_int) {
+ case act_avreplace:
+ printf(_("Replacing available packages info, using %s.\n"),sourcefile);
+ break;
+ case act_avmerge:
+ printf(_("Updating available packages info, using %s.\n"),sourcefile);
+ break;
+ case act_avclear:
+ break;
+ default:
+ internerr("unknown action '%d'", cipaction->arg_int);
+ }
+
+ availfile = dpkg_db_get_path(AVAILFILE);
+
+ if (cipaction->arg_int == act_avmerge)
+ parsedb(availfile, pdb_parse_available, NULL);
+
+ if (cipaction->arg_int != act_avclear)
+ count += parsedb(sourcefile,
+ pdb_parse_available | pdb_ignoreolder | pdb_dash_is_stdin,
+ NULL);
+
+ if (!f_noact) {
+ writedb(availfile, wdb_dump_available);
+ modstatdb_unlock();
+ }
+
+ free(availfile);
+
+ if (cipaction->arg_int != act_avclear)
+ printf(P_("Information about %d package was updated.\n",
+ "Information about %d packages was updated.\n", count), count);
+
+ modstatdb_done();
+
+ return 0;
+}
+
+int
+forgetold(const char *const *argv)
+{
+ if (*argv)
+ badusage(_("--%s takes no arguments"), cipaction->olong);
+
+ warning(_("obsolete '--%s' option; unavailable packages are automatically cleaned up"),
+ cipaction->olong);
+
+ return 0;
+}
diff --git a/src/main/verify.c b/src/main/verify.c
new file mode 100644
index 0000000..6a5d733
--- /dev/null
+++ b/src/main/verify.c
@@ -0,0 +1,243 @@
+/*
+ * dpkg - main program for package management
+ * verify.c - verify package integrity
+ *
+ * Copyright © 2012-2015 Guillem Jover <guillem@debian.org>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <compat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <dpkg/i18n.h>
+#include <dpkg/dpkg.h>
+#include <dpkg/dpkg-db.h>
+#include <dpkg/options.h>
+#include <dpkg/db-ctrl.h>
+#include <dpkg/db-fsys.h>
+#include <dpkg/buffer.h>
+
+#include "main.h"
+
+
+enum verify_result {
+ VERIFY_NONE,
+ VERIFY_PASS,
+ VERIFY_FAIL,
+};
+
+struct verify_checks {
+ int exists_errno;
+ enum verify_result exists;
+ enum verify_result mode;
+ enum verify_result md5sum;
+};
+
+typedef void verify_output_func(struct fsys_namenode *, struct verify_checks *);
+
+static int
+verify_result_rpm(enum verify_result result, int check)
+{
+ switch (result) {
+ case VERIFY_FAIL:
+ return check;
+ case VERIFY_PASS:
+ return '.';
+ case VERIFY_NONE:
+ default:
+ return '?';
+ }
+}
+
+static void
+verify_output_rpm(struct fsys_namenode *namenode, struct verify_checks *checks)
+{
+ char result[9];
+ char *error = NULL;
+ int attr;
+
+ memset(result, '?', sizeof(result));
+
+ if (checks->exists == VERIFY_FAIL) {
+ memcpy(result, "missing ", sizeof(result));
+ if (checks->exists_errno != ENOENT)
+ m_asprintf(&error, " (%s)", strerror(checks->exists_errno));
+ } else {
+ result[1] = verify_result_rpm(checks->mode, 'M');
+ result[2] = verify_result_rpm(checks->md5sum, '5');
+ }
+
+ if (namenode->flags & FNNF_OLD_CONFF)
+ attr = 'c';
+ else
+ attr = ' ';
+
+ printf("%.9s %c %s%s\n", result, attr, namenode->name, error ? error : "");
+
+ free(error);
+}
+
+static verify_output_func *verify_output = verify_output_rpm;
+
+bool
+verify_set_output(const char *name)
+{
+ if (strcmp(name, "rpm") == 0)
+ verify_output = verify_output_rpm;
+ else
+ return false;
+
+ return true;
+}
+
+static int
+verify_digest(const char *filename, struct fsys_namenode *fnn,
+ struct verify_checks *checks)
+{
+ static int fd;
+
+ fd = open(filename, O_RDONLY);
+
+ if (fd >= 0) {
+ struct dpkg_error err;
+ char hash[MD5HASHLEN + 1];
+
+ push_cleanup(cu_closefd, ehflag_bombout, 1, &fd);
+ if (fd_md5(fd, hash, -1, &err) < 0)
+ ohshit(_("cannot compute MD5 digest for file '%s': %s"),
+ filename, err.str);
+ pop_cleanup(ehflag_normaltidy); /* fd = open(cdr.buf) */
+ close(fd);
+
+ if (strcmp(hash, fnn->newhash) == 0) {
+ checks->md5sum = VERIFY_PASS;
+ return 0;
+ } else {
+ checks->md5sum = VERIFY_FAIL;
+ }
+ } else {
+ checks->md5sum = VERIFY_NONE;
+ }
+
+ return -1;
+}
+
+static int
+verify_file(const char *filename, struct fsys_namenode *fnn,
+ struct pkginfo *pkg, struct verify_checks *checks)
+{
+ struct stat st;
+ int failures = 0;
+
+ if (lstat(filename, &st) < 0) {
+ checks->exists_errno = errno;
+ checks->exists = VERIFY_FAIL;
+ return 1;
+ }
+ checks->exists = VERIFY_PASS;
+
+ if (fnn->newhash == NULL && fnn->oldhash != NULL)
+ fnn->newhash = fnn->oldhash;
+
+ if (fnn->newhash != NULL) {
+ /* Mode check heuristic: If we know its digest, the pathname
+ * must be a regular file. */
+ if (!S_ISREG(st.st_mode)) {
+ checks->mode = VERIFY_FAIL;
+ failures++;
+ }
+
+ if (verify_digest(filename, fnn, checks) < 0)
+ failures++;
+ }
+
+ return failures;
+}
+
+static void
+verify_package(struct pkginfo *pkg)
+{
+ struct fsys_namenode_list *file;
+ struct varbuf filename = VARBUF_INIT;
+
+ ensure_packagefiles_available(pkg);
+ parse_filehash(pkg, &pkg->installed);
+ pkg_conffiles_mark_old(pkg);
+
+ for (file = pkg->files; file; file = file->next) {
+ struct verify_checks checks;
+ struct fsys_namenode *fnn;
+
+ fnn = namenodetouse(file->namenode, pkg, &pkg->installed);
+
+ varbuf_set_str(&filename, dpkg_fsys_get_dir());
+ varbuf_add_str(&filename, fnn->name);
+ varbuf_end_str(&filename);
+
+ memset(&checks, 0, sizeof(checks));
+
+ if (verify_file(filename.buf, fnn, pkg, &checks) > 0)
+ verify_output(fnn, &checks);
+ }
+
+ varbuf_destroy(&filename);
+}
+
+int
+verify(const char *const *argv)
+{
+ struct pkginfo *pkg;
+ int rc = 0;
+
+ modstatdb_open(msdbrw_readonly);
+ ensure_diversions();
+
+ if (!*argv) {
+ struct pkg_hash_iter *iter;
+
+ iter = pkg_hash_iter_new();
+ while ((pkg = pkg_hash_iter_next_pkg(iter)))
+ verify_package(pkg);
+ pkg_hash_iter_free(iter);
+ } else {
+ const char *thisarg;
+
+ while ((thisarg = *argv++)) {
+ pkg = dpkg_options_parse_pkgname(cipaction, thisarg);
+ if (pkg->status == PKG_STAT_NOTINSTALLED) {
+ notice(_("package '%s' is not installed"),
+ pkg_name(pkg, pnaw_nonambig));
+ rc = 1;
+ continue;
+ }
+
+ verify_package(pkg);
+ }
+ }
+
+ modstatdb_shutdown();
+
+ m_output(stdout, _("<standard output>"));
+
+ return rc;
+}