blob: a275b55c5a753b81704362134037a4c03b225129 [file] [log] [blame]
/*
* dpkg - main program for package management
* archives.c - actions that process archive files, mainly unpack
*
* Copyright © 1994,1995 Ian Jackson <ian@chiark.greenend.org.uk>
* Copyright © 2000 Wichert Akkerman <wakkerma@debian.org>
* Copyright © 2007-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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <compat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <assert.h>
#include <errno.h>
#include <ctype.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/path.h>
#include <dpkg/fdio.h>
#include <dpkg/buffer.h>
#include <dpkg/subproc.h>
#include <dpkg/command.h>
#include <dpkg/file.h>
#include <dpkg/tarfn.h>
#include <dpkg/options.h>
#include <dpkg/triglib.h>
#ifdef WITH_SELINUX
#include <selinux/selinux.h>
#endif
#include "filesdb.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_obs;
static bool tarobs_init = false;
/**
* Ensure the obstack is properly initialized.
*/
static void ensureobstackinit(void) {
if (!tarobs_init) {
obstack_init(&tar_obs);
tarobs_init = true;
}
}
/**
* Destroy the obstack.
*/
static void destroyobstack(void) {
if (tarobs_init) {
obstack_free(&tar_obs, NULL);
tarobs_init = false;
}
}
/**
* 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 fileinlist *file,
struct pkginfo *pkgtobesaved,
struct pkginfo *pkgbeinginstalled)
{
struct filepackages_iterator *iter;
struct pkginfo *divpkg, *thirdpkg;
debug(dbg_eachfiledetail,"filesavespackage file `%s' package %s",
file->namenode->name,pkgtobesaved->name);
/* 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) {
divpkg= file->namenode->divert->pkg;
if (divpkg == pkgtobesaved || divpkg == pkgbeinginstalled) {
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 = filepackages_iter_new(file->namenode);
while ((thirdpkg = filepackages_iter_next(iter))) {
debug(dbg_eachfiledetail, "filesavespackage ... also in %s",
thirdpkg->name);
/* Is this not the package being installed or the one being
* checked for disappearance? */
if (thirdpkg == pkgbeinginstalled || thirdpkg == pkgtobesaved)
continue;
/* If !fileslistvalid then we've already disappeared this one, so
* we shouldn't try to make it take over this shared directory. */
debug(dbg_eachfiledetail,"filesavespackage ... is 3rd package");
if (!thirdpkg->clientdata->fileslistvalid) {
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");
return false;
}
filepackages_iter_free(iter);
debug(dbg_eachfiledetail, "filesavespackage ... not taken -- save !");
return true;
}
void cu_pathname(int argc, void **argv) {
ensure_pathname_nonexisting((char*)(argv[0]));
}
int tarfileread(void *ud, char *buf, int len) {
struct tarcontext *tc= (struct tarcontext*)ud;
int r;
r = fd_read(tc->backendpipe, buf, len);
if (r < 0)
ohshite(_("error reading from dpkg-deb pipe"));
return r;
}
static void
tarfile_skip_one_forward(struct tarcontext *tc, struct tar_entry *ti)
{
size_t r;
char databuf[TARBLKSZ];
/* 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) {
char fnamebuf[256];
fd_skip(tc->backendpipe, ti->size,
_("skipped unpacking file '%.255s' (replaced or excluded?)"),
path_quote_filename(fnamebuf, ti->name, 256));
r = ti->size % TARBLKSZ;
if (r > 0)
if (fd_read(tc->backendpipe, databuf, TARBLKSZ - r) < 0)
ohshite(_("error reading from dpkg-deb pipe"));
}
}
int fnameidlu;
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 *newpigp, struct pkgbin *newpifp,
struct pkginfo *oldpigp, struct pkgbin *oldpifp)
{
struct dependency *dep;
debug(dbg_depcon,"does_replace new=%s old=%s (%s)",newpigp->name,
oldpigp->name, versiondescribe(&oldpifp->version, vdew_always));
for (dep= newpifp->depends; dep; dep= dep->next) {
if (dep->type != dep_replaces || dep->list->ed != oldpigp) continue;
debug(dbg_depcondetail,"does_replace ... found old, version %s",
versiondescribe(&dep->list->version,vdew_always));
if (!versionsatisfied(oldpifp, dep->list))
continue;
debug(dbg_depcon,"does_replace ... yes");
return true;
}
debug(dbg_depcon,"does_replace ... no");
return false;
}
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)
{
if (te->type == tar_filetype_file)
return; /* Already handled using the file descriptor. */
if (te->type == tar_filetype_symlink) {
if (lchown(path, st->uid, st->gid))
ohshite(_("error setting ownership of symlink `%.255s'"), path);
} else {
if (chown(path, st->uid, st->gid))
ohshite(_("error setting ownership of `%.255s'"), path);
if (chmod(path, st->mode & ~S_IFMT))
ohshite(_("error setting permissions of `%.255s'"), path);
}
}
static void
tarobject_set_se_context(const char *matchpath, const char *path, mode_t mode)
{
#ifdef WITH_SELINUX
static int selinux_enabled = -1;
security_context_t scontext = NULL;
int ret;
/* If there's no file type, just give up. */
if ((mode & S_IFMT) == 0)
return;
/* Set selinux_enabled if it is not already set (singleton). */
if (selinux_enabled < 0)
selinux_enabled = (is_selinux_enabled() > 0);
/* If SE Linux is not enabled just do nothing. */
if (!selinux_enabled)
return;
/* XXX: Well, we could use set_matchpathcon_printf() to redirect the
* errors from the following bit, but that seems too much effort. */
/* Do nothing if we can't figure out what the context is, or if it has
* no context; in which case the default context shall be applied. */
ret = matchpathcon(matchpath, mode & S_IFMT, &scontext);
if (ret == -1 || (ret == 0 && scontext == NULL))
return;
if (strcmp(scontext, "<<none>>") != 0) {
if (lsetfilecon(path, scontext) < 0)
/* XXX: This might need to be fatal instead!? */
perror("Error setting security context for next file object:");
}
freecon(scontext);
#endif /* WITH_SELINUX */
}
void setupfnamevbs(const char *filename) {
varbuf_trunc(&fnamevb, fnameidlu);
varbuf_add_str(&fnamevb, filename);
varbuf_end_str(&fnamevb);
varbuf_trunc(&fnametmpvb, fnameidlu);
varbuf_add_str(&fnametmpvb, filename);
varbuf_add_str(&fnametmpvb, DPKGTEMPEXT);
varbuf_end_str(&fnametmpvb);
varbuf_trunc(&fnamenewvb, fnameidlu);
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);
}
/**
* Securely remove a pathname.
*
* This is a secure version of remove(3) using secure_unlink() instead of
* unlink(2).
*
* @retval 0 On success.
* @retval -1 On failure, just like unlink(2) & rmdir(2).
*/
int
secure_remove(const char *filename)
{
int r, e;
if (!rmdir(filename)) {
debug(dbg_eachfiledetail, "secure_remove '%s' rmdir OK", filename);
return 0;
}
if (errno != ENOTDIR) {
e= errno;
debug(dbg_eachfiledetail, "secure_remove '%s' rmdir %s", filename,
strerror(e));
errno= e; return -1;
}
r = secure_unlink(filename);
e = errno;
debug(dbg_eachfiledetail, "secure_remove '%s' unlink %s",
filename, r ? strerror(e) : "OK");
errno= e; return r;
}
struct fileinlist *addfiletolist(struct tarcontext *tc,
struct filenamenode *namenode) {
struct fileinlist *nifd;
nifd= obstack_alloc(&tar_obs, sizeof(struct fileinlist));
nifd->namenode= namenode;
nifd->next = NULL;
*tc->newfilesp = nifd;
tc->newfilesp = &nifd->next;
return nifd;
}
static void
remove_file_from_list(struct tarcontext *tc, struct tar_entry *ti,
struct fileinlist **oldnifd,
struct fileinlist *nifd)
{
obstack_free(&tar_obs, nifd);
tc->newfilesp = oldnifd;
*oldnifd = NULL;
}
static bool
linktosameexistingdir(const struct tar_entry *ti, const char *fname,
struct varbuf *symlinkfn)
{
struct stat oldstab, newstab;
int statr;
const char *lastslash;
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? */
varbuf_reset(symlinkfn);
if (ti->linkname[0] == '/') {
varbuf_add_str(symlinkfn, instdir);
} else {
lastslash= strrchr(fname, '/');
assert(lastslash);
varbuf_add_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(void *ctx, struct tar_entry *ti)
{
static struct varbuf conffderefn, hardlinkfn, symlinkfn;
static int fd;
const char *usename;
struct filenamenode *usenode;
struct filenamenode *linknode;
struct conffile *conff;
struct tarcontext *tc = ctx;
bool existingdir, keepexisting;
int statr;
ssize_t r;
struct stat stab, stabtmp;
char databuf[TARBLKSZ];
struct file_stat *st;
struct fileinlist *nifd, **oldnifd;
struct pkginfo *divpkg, *otherpkg;
ensureobstackinit();
/* Append to list of files.
* The trailing ‘/’ put on the end of names in tarfiles has already
* been stripped by tar_extractor(). */
oldnifd= tc->newfilesp;
nifd= addfiletolist(tc, findnamenode(ti->name, 0));
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) {
divpkg= nifd->namenode->divert->pkg;
if (divpkg) {
forcibleerr(fc_overwritediverted,
_("trying to overwrite `%.250s', which is the "
"diverted version of `%.250s' (package: %.100s)"),
nifd->namenode->name, nifd->namenode->divert->camefrom->name,
divpkg->name);
} else {
forcibleerr(fc_overwritediverted,
_("trying to overwrite `%.250s', which is the "
"diverted version of `%.250s'"),
nifd->namenode->name, nifd->namenode->divert->camefrom->name);
}
}
if (nifd->namenode->statoverride)
st = nifd->namenode->statoverride;
else
st = &ti->stat;
usenode = namenodetouse(nifd->namenode, tc->pkg);
usename = usenode->name + 1; /* Skip the leading '/'. */
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 I was about to install)"),
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)) {
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;
if (!existingdir) {
struct filepackages_iterator *iter;
iter = filepackages_iter_new(nifd->namenode);
while ((otherpkg = filepackages_iter_next(iter))) {
if (otherpkg == tc->pkg)
continue;
debug(dbg_eachfile, "tarobject ... found in %s", otherpkg->name);
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). */
divpkg = nifd->namenode->divert->pkg;
debug(dbg_eachfile, "tarobject ... diverted, divpkg=%s",
divpkg ? divpkg->name : "<none>");
if (otherpkg == divpkg || tc->pkg == divpkg)
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;
}
/* Nope? Hmm, file conflict, perhaps. Check Replaces. */
switch (otherpkg->clientdata->replacingfilesandsaid) {
case 2:
keepexisting = true;
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 silenty take it
* over. */
if (otherpkg->status == stat_configfiles)
continue;
/* Perhaps we're removing a conflicting package? */
if (otherpkg->clientdata->istobe == itb_remove)
continue;
/* Is the file an obsolete conffile in the other package
* and a conffile in the new package? */
if ((nifd->namenode->flags & fnnf_new_conff) &&
!statr && S_ISREG(stab.st_mode)) {
for (conff = otherpkg->installed.conffiles;
conff;
conff = conff->next) {
if (!conff->obsolete)
continue;
if (stat(conff->name, &stabtmp)) {
if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP)
continue;
else
ohshite(_("cannot stat file '%s'"), conff->name);
}
if (stabtmp.st_dev == stab.st_dev &&
stabtmp.st_ino == stab.st_ino)
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 ...\n"),otherpkg->name);
otherpkg->clientdata->replacingfilesandsaid = 1;
} else if (does_replace(otherpkg, &otherpkg->installed,
tc->pkg, &tc->pkg->available)) {
printf(_("Replaced by files in installed package %s ...\n"),
otherpkg->name);
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(fc_overwritedir,
_("trying to overwrite directory '%.250s' "
"in package %.250s %.250s with nondirectory"),
nifd->namenode->name, otherpkg->name,
versiondescribe(&otherpkg->installed.version,
vdew_nonambig));
} else {
forcibleerr(fc_overwrite,
_("trying to overwrite '%.250s', "
"which is also in package %.250s %.250s"),
nifd->namenode->name, otherpkg->name,
versiondescribe(&otherpkg->installed.version,
vdew_nonambig));
}
}
}
filepackages_iter_free(iter);
}
if (keepexisting) {
remove_file_from_list(tc, ti, oldnifd, nifd);
tarfile_skip_one_forward(tc, ti);
return 0;
}
if (filter_should_skip(ti)) {
nifd->namenode->flags &= ~fnnf_new_inarchive;
nifd->namenode->flags |= fnnf_filtered;
tarfile_skip_one_forward(tc, ti);
return 0;
}
if (existingdir)
return 0;
/* Now, at this stage we want to make sure neither of .dpkg-new and
* .dpkg-tmp are hanging around. */
ensure_pathname_nonexisting(fnamenewvb.buf);
ensure_pathname_nonexisting(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, NULL, 0, 1, (void *)nifd);
/*
* CLEANUP: Now we either have the old file on the disk, or not, in
* its original filename.
*/
/* Extract whatever it is as .dpkg-new ... */
switch (ti->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(fnamenewvb.buf, (O_CREAT|O_EXCL|O_WRONLY), 0);
if (fd < 0)
ohshite(_("unable to create `%.255s' (while processing `%.255s')"),
fnamenewvb.buf, ti->name);
push_cleanup(cu_closefd, ehflag_bombout, NULL, 0, 1, &fd);
debug(dbg_eachfiledetail, "tarobject file open size=%jd",
(intmax_t)ti->size);
{ char fnamebuf[256];
fd_fd_copy(tc->backendpipe, fd, ti->size,
_("backend dpkg-deb during `%.255s'"),
path_quote_filename(fnamebuf, ti->name, 256));
}
r = ti->size % TARBLKSZ;
if (r > 0)
if (fd_read(tc->backendpipe, databuf, TARBLKSZ - r) < 0)
ohshite(_("error reading from dpkg-deb pipe"));
fd_writeback_init(fd);
if (nifd->namenode->statoverride)
debug(dbg_eachfile, "tarobject ... stat override, uid=%d, gid=%d, mode=%04o",
nifd->namenode->statoverride->uid,
nifd->namenode->statoverride->gid,
nifd->namenode->statoverride->mode);
if (fchown(fd, st->uid, st->gid))
ohshite(_("error setting ownership of `%.255s'"), ti->name);
if (fchmod(fd, st->mode & ~S_IFMT))
ohshite(_("error setting permissions of `%.255s'"), ti->name);
/* Postpone the fsync, to try to avoid massive I/O degradation. */
if (!fc_unsafe_io)
nifd->namenode->flags |= fnnf_deferred_fsync;
pop_cleanup(ehflag_normaltidy); /* fd = open(fnamenewvb.buf) */
if (close(fd))
ohshite(_("error closing/writing `%.255s'"), ti->name);
break;
case tar_filetype_fifo:
if (mkfifo(fnamenewvb.buf,0))
ohshite(_("error creating pipe `%.255s'"), ti->name);
debug(dbg_eachfiledetail, "tarobject fifo");
break;
case tar_filetype_chardev:
if (mknod(fnamenewvb.buf, S_IFCHR, ti->dev))
ohshite(_("error creating device `%.255s'"), ti->name);
debug(dbg_eachfiledetail, "tarobject chardev");
break;
case tar_filetype_blockdev:
if (mknod(fnamenewvb.buf, S_IFBLK, ti->dev))
ohshite(_("error creating device `%.255s'"), ti->name);
debug(dbg_eachfiledetail, "tarobject blockdev");
break;
case tar_filetype_hardlink:
varbuf_reset(&hardlinkfn);
varbuf_add_str(&hardlinkfn, instdir);
varbuf_add_char(&hardlinkfn, '/');
linknode = findnamenode(ti->linkname, 0);
varbuf_add_str(&hardlinkfn, namenodetouse(linknode, tc->pkg)->name);
if (linknode->flags & (fnnf_deferred_rename|fnnf_new_conff))
varbuf_add_str(&hardlinkfn, DPKGNEWEXT);
varbuf_end_str(&hardlinkfn);
if (link(hardlinkfn.buf,fnamenewvb.buf))
ohshite(_("error creating hard link `%.255s'"), ti->name);
debug(dbg_eachfiledetail, "tarobject hardlink");
break;
case tar_filetype_symlink:
/* We've already checked for an existing directory. */
if (symlink(ti->linkname, fnamenewvb.buf))
ohshite(_("error creating symbolic link `%.255s'"), ti->name);
debug(dbg_eachfiledetail, "tarobject symlink creating");
break;
case tar_filetype_dir:
/* We've already checked for an existing directory. */
if (mkdir(fnamenewvb.buf,0))
ohshite(_("error creating directory `%.255s'"), ti->name);
debug(dbg_eachfiledetail, "tarobject directory creating");
break;
default:
internerr("unknown tar type '%d', but already checked", ti->type);
}
tarobject_set_perms(ti, fnamenewvb.buf, st);
tarobject_set_mtime(ti, fnamenewvb.buf);
tarobject_set_se_context(fnamevb.buf, fnamenewvb.buf, st->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)) {
/* 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);
r = readlink(fnamevb.buf, symlinkfn.buf, symlinkfn.size);
if (r < 0)
ohshite(_("unable to read link `%.255s'"), ti->name);
else if (r != stab.st_size)
ohshit(_("symbolic link '%.250s' size has changed from %jd to %zd"),
fnamevb.buf, stab.st_size, r);
varbuf_trunc(&symlinkfn, r);
varbuf_end_str(&symlinkfn);
if (symlink(symlinkfn.buf,fnametmpvb.buf))
ohshite(_("unable to make backup symlink for `%.255s'"), ti->name);
if (lchown(fnametmpvb.buf,stab.st_uid,stab.st_gid))
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 fileinlist *files, struct pkginfo *pkg)
{
struct fileinlist *cfile;
for (cfile = files; cfile; cfile = cfile->next) {
struct filenamenode *usenode;
const char *usename;
int fd;
if (!(cfile->namenode->flags & fnnf_deferred_fsync))
continue;
usenode = namenodetouse(cfile->namenode, pkg);
usename = usenode->name + 1; /* Skip the leading '/'. */
setupfnamevbs(usename);
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 fileinlist *files, struct pkginfo *pkg)
{
}
#endif
void
tar_deferred_extract(struct fileinlist *files, struct pkginfo *pkg)
{
struct fileinlist *cfile;
struct filenamenode *usenode;
const char *usename;
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);
usename = usenode->name + 1; /* Skip the leading '/'. */
setupfnamevbs(usename);
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");
}
}
/**
* Try if we can deconfigure the package and queue it if so.
*
* Also checks whether the pdep is forced, first, according to force_p.
* force_p may be NULL in which case nothing is considered forced.
*
* Action is a string describing the action which causes the
* deconfiguration:
*
* "removal of <package>" (due to Conflicts+Depends; removal != NULL)
* "installation of <package>" (due to Breaks; removal == NULL)
*
* @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(bool (*force_p)(struct deppossi *), struct pkginfo *pkg,
struct deppossi *pdep, const char *action,
struct pkginfo *removal, const char *why)
{
struct pkg_deconf_list *newdeconf;
if (force_p && force_p(pdep)) {
warning(_("ignoring dependency problem with %s:\n%s"), action, why);
return 2;
} else if (f_autodeconf) {
if (pkg->installed.essential) {
if (fc_removeessential) {
warning(_("considering deconfiguration of essential\n"
" package %s, to enable %s."), pkg->name, action);
} else {
fprintf(stderr, _("dpkg: no, %s is essential, will not deconfigure\n"
" it in order to enable %s.\n"),
pkg->name, action);
return 0;
}
}
pkg->clientdata->istobe= itb_deconfigure;
newdeconf = m_malloc(sizeof(struct pkg_deconf_list));
newdeconf->next= deconfigure;
newdeconf->pkg= pkg;
newdeconf->pkg_removal = removal;
deconfigure= newdeconf;
return 1;
} else {
fprintf(stderr, _("dpkg: no, cannot proceed with %s (--auto-deconfigure will help):\n%s"),
action, why);
return 0;
}
}
static int try_remove_can(struct deppossi *pdep,
struct pkginfo *fixbyrm,
const char *why) {
char action[512];
sprintf(action, _("removal of %.250s"), fixbyrm->name);
return try_deconfigure_can(force_depends, pdep->up->up, pdep,
action, fixbyrm, why);
}
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) {
char action[512];
ensure_package_clientdata(fixbydeconf);
assert(fixbydeconf->clientdata->istobe == itb_normal);
sprintf(action, _("installation of %.250s"), pkg->name);
fprintf(stderr, _("dpkg: considering deconfiguration of %s,"
" which would be broken by %s ...\n"),
fixbydeconf->name, action);
ok= try_deconfigure_can(force_breaks, fixbydeconf, dep->list,
action, NULL, why.buf);
if (ok == 1) {
fprintf(stderr, _("dpkg: yes, will deconfigure %s (broken by %s).\n"),
fixbydeconf->name, pkg->name);
}
} else {
fprintf(stderr, _("dpkg: regarding %s containing %s:\n%s"),
pfilename, pkg->name, 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)"),
pkg->name, fixbydeconf->name);
} else {
ohshit(_("installing %.250s would break existing software"),
pkg->name);
}
}
void check_conflict(struct dependency *dep, struct pkginfo *pkg,
const char *pfilename) {
struct pkginfo *fixbyrm;
struct deppossi *pdep, flagdeppossi;
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 == itb_installnew) {
fixbyrm= dep->up;
ensure_package_clientdata(fixbyrm);
}
if (((pkg->available.essential && fixbyrm->installed.essential) ||
(((fixbyrm->want != want_install && fixbyrm->want != want_hold) ||
does_replace(pkg, &pkg->available, fixbyrm, &fixbyrm->installed)) &&
(!fixbyrm->installed.essential || fc_removeessential)))) {
assert(fixbyrm->clientdata->istobe == itb_normal || fixbyrm->clientdata->istobe == itb_deconfigure);
fixbyrm->clientdata->istobe= itb_remove;
fprintf(stderr, _("dpkg: considering removing %s in favour of %s ...\n"),
fixbyrm->name, pkg->name);
if (!(fixbyrm->status == stat_installed ||
fixbyrm->status == stat_triggerspending ||
fixbyrm->status == stat_triggersawaited)) {
fprintf(stderr,
_("%s is not properly installed - ignoring any dependencies on it.\n"),
fixbyrm->name);
pdep = NULL;
} else {
for (pdep= fixbyrm->installed.depended;
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->installed.depended;
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);
fprintf(stderr, _("dpkg"
": may have trouble removing %s, as it provides %s ...\n"),
fixbyrm->name, 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 & eflag_reinstreq)) {
if (fc_removereinstreq) {
fprintf(stderr, _("dpkg: package %s requires reinstallation, but will"
" remove anyway as you requested.\n"), fixbyrm->name);
} else {
fprintf(stderr, _("dpkg: package %s requires reinstallation, "
"will not remove.\n"), fixbyrm->name);
pdep= &flagdeppossi;
}
}
if (!pdep) {
/* This conflict is OK - we'll remove the conflictor. */
push_conflictor(pkg, fixbyrm);
varbuf_destroy(&conflictwhy); varbuf_destroy(&removalwhy);
fprintf(stderr, _("dpkg: yes, will remove %s in favour of %s.\n"),
fixbyrm->name, pkg->name);
return;
}
/* Put it back. */
fixbyrm->clientdata->istobe = itb_normal;
}
}
varbuf_end_str(&conflictwhy);
fprintf(stderr, _("dpkg: regarding %s containing %s:\n%s"),
pfilename, pkg->name, conflictwhy.buf);
if (!force_conflicts(dep->list))
ohshit(_("conflicting packages - not installing %.250s"),pkg->name);
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';
ensure_pathname_nonexisting(cidir);
}
void cu_fileslist(int argc, void **argv) {
destroyobstack();
}
int
archivefiles(const char *const *argv)
{
const char *volatile thisarg;
const char *const *volatile argp;
jmp_buf ejbuf;
trigproc_install_hooks();
modstatdb_open(f_noact ? msdbrw_readonly :
(cipaction->arg_int == act_avail ? msdbrw_readonly :
fc_nonroot ? msdbrw_write :
msdbrw_needsuperuser) |
msdbrw_available_write);
checkpath();
log_message("startup archives %s", cipaction->olong);
if (f_recursive) {
int pi[2], nfiles, c, i, r;
pid_t pid;
FILE *pf;
static struct varbuf findoutput;
const char **arglist;
char *p;
if (!*argv)
badusage(_("--%s --recursive needs at least one path argument"),cipaction->olong);
m_pipe(pi);
pid = subproc_fork();
if (pid == 0) {
struct command cmd;
const char *const *ap;
m_dup2(pi[1],1); close(pi[0]); close(pi[1]);
command_init(&cmd, FIND, _("find for dpkg --recursive"));
command_add_args(&cmd, FIND, "-L", NULL);
for (ap = argv; *ap; ap++) {
if (strchr(FIND_EXPRSTARTCHARS,(*ap)[0])) {
char *a;
m_asprintf(&a, "./%s", *ap);
command_add_arg(&cmd, a);
} else {
command_add_arg(&cmd, (const char *)*ap);
}
}
command_add_args(&cmd, "-name", "*.deb", "-type", "f", "-print0", NULL);
command_exec(&cmd);
}
close(pi[1]);
nfiles= 0;
pf= fdopen(pi[0],"r"); if (!pf) ohshite(_("failed to fdopen find's pipe"));
varbuf_reset(&findoutput);
while ((c= fgetc(pf)) != EOF) {
varbuf_add_char(&findoutput, c);
if (!c) nfiles++;
}
if (ferror(pf)) ohshite(_("error reading find's pipe"));
if (fclose(pf)) ohshite(_("error closing find's pipe"));
r = subproc_wait_check(pid, "find", PROCNOERR);
if (r != 0)
ohshit(_("find for --recursive returned unhandled error %i"),r);
if (!nfiles)
ohshit(_("searched, but found no packages (files matching *.deb)"));
arglist= m_malloc(sizeof(char*)*(nfiles+1));
p = findoutput.buf;
for (i = 0; i < nfiles; i++) {
arglist[i] = p;
while (*p++ != '\0') ;
}
arglist[i] = NULL;
argp= arglist;
} else {
if (!*argv) badusage(_("--%s needs at least one package archive file argument"),
cipaction->olong);
argp= argv;
}
currenttime = time(NULL);
/* Initialize fname variables contents. */
varbuf_reset(&fnamevb);
varbuf_reset(&fnametmpvb);
varbuf_reset(&fnamenewvb);
varbuf_add_str(&fnamevb, instdir);
varbuf_add_char(&fnamevb, '/');
varbuf_add_str(&fnametmpvb, instdir);
varbuf_add_char(&fnametmpvb, '/');
varbuf_add_str(&fnamenewvb, instdir);
varbuf_add_char(&fnamenewvb, '/');
fnameidlu= fnamevb.used;
ensure_diversions();
ensure_statoverrides();
while ((thisarg = *argp++) != NULL) {
if (setjmp(ejbuf)) {
pop_error_context(ehflag_bombout);
if (abort_processing)
break;
continue;
}
push_error_context_jump(&ejbuf, print_error_perpackage, thisarg);
process_archive(thisarg);
onerr_abort++;
m_output(stdout, _("<standard output>"));
m_output(stderr, _("<standard error>"));
onerr_abort--;
pop_error_context(ehflag_normaltidy);
}
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 r;
if (pkg->want != want_install && pkg->want != want_hold) {
if (f_alsoselect) {
printf(_("Selecting previously unselected package %s.\n"), pkg->name);
pkg->want = want_install;
return true;
} else {
printf(_("Skipping unselected package %s.\n"), pkg->name);
return false;
}
}
if (pkg->eflag & eflag_reinstreq)
return true;
if (pkg->status < stat_unpacked)
return true;
r = versioncompare(&pkg->available.version, &pkg->installed.version);
if (r > 0) {
return true;
} else if (r == 0) {
/* Same version fully installed. */
if (f_skipsame) {
fprintf(stderr, _("Version %.250s of %.250s already installed, "
"skipping.\n"),
versiondescribe(&pkg->installed.version, vdew_nonambig),
pkg->name);
return false;
} else {
return true;
}
} else {
if (fc_downgrade) {
warning(_("downgrading %.250s from %.250s to %.250s."), pkg->name,
versiondescribe(&pkg->installed.version, vdew_nonambig),
versiondescribe(&pkg->available.version, vdew_nonambig));
return true;
} else {
fprintf(stderr, _("Will not downgrade %.250s from version %.250s "
"to %.250s, skipping.\n"), pkg->name,
versiondescribe(&pkg->installed.version, vdew_nonambig),
versiondescribe(&pkg->available.version, vdew_nonambig));
return false;
}
}
}
struct fileinlist *newconff_append(struct fileinlist ***newconffileslastp_io,
struct filenamenode *namenode) {
struct fileinlist *newconff;
newconff= m_malloc(sizeof(struct fileinlist));
newconff->next = NULL;
newconff->namenode= namenode;
**newconffileslastp_io= newconff;
*newconffileslastp_io= &newconff->next;
return newconff;
}
/* vi: ts=8 sw=2
*/