blob: e9ffd2eb757d7f192a1b0b7795883e2cbec3e76d [file] [log] [blame]
/*
* dpkg - main program for package management
* help.c - various helper routines
*
* Copyright © 1995 Ian Jackson <ian@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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <compat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <dirent.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/subproc.h>
#include <dpkg/command.h>
#include <dpkg/triglib.h>
#include "filesdb.h"
#include "main.h"
const char *const statusstrings[]= {
[stat_notinstalled] = N_("not installed"),
[stat_configfiles] = N_("not installed but configs remain"),
[stat_halfinstalled] = N_("broken due to failed removal or installation"),
[stat_unpacked] = N_("unpacked but not configured"),
[stat_halfconfigured] = N_("broken due to postinst failure"),
[stat_triggersawaited] = N_("awaiting trigger processing by another package"),
[stat_triggerspending] = N_("triggered"),
[stat_installed] = N_("installed")
};
struct filenamenode *namenodetouse(struct filenamenode *namenode, struct pkginfo *pkg) {
struct filenamenode *r;
if (!namenode->divert) {
r = namenode;
return r;
}
debug(dbg_eachfile,"namenodetouse namenode=`%s' pkg=%s",
namenode->name,pkg->name);
r=
(namenode->divert->useinstead && namenode->divert->pkg != pkg)
? 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->pkg ? namenode->divert->pkg->name : "<none>",
r->name);
return r;
}
/**
* Verify that some programs can be found in the PATH.
*/
void checkpath(void) {
static const char *const prog_list[] = {
DEFAULTSHELL,
RM,
TAR,
FIND,
BACKEND,
"ldconfig",
#if WITH_START_STOP_DAEMON
"start-stop-daemon",
#endif
NULL
};
const char *const *prog;
const char *path_list;
struct varbuf filename = VARBUF_INIT;
int warned= 0;
path_list = getenv("PATH");
if (!path_list)
ohshit(_("PATH is not set."));
for (prog = prog_list; *prog; prog++) {
struct stat stab;
const char *path, *path_end;
size_t path_len;
for (path = path_list; path; path = path_end ? path_end + 1 : NULL) {
path_end = strchr(path, ':');
path_len = path_end ? (size_t)(path_end - path) : strlen(path);
varbuf_reset(&filename);
varbuf_add_buf(&filename, path, path_len);
if (path_len)
varbuf_add_char(&filename, '/');
varbuf_add_str(&filename, *prog);
varbuf_end_str(&filename);
if (stat(filename.buf, &stab) == 0 && (stab.st_mode & 0111))
break;
}
if (!path) {
warning(_("'%s' not found in PATH or not executable."), *prog);
warned++;
}
}
varbuf_destroy(&filename);
if (warned)
forcibleerr(fc_badpath,
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(struct pkginfo *pkg)
{
struct pkg_list *id;
for (id= ignoredependss; id; id= id->next)
if (id->pkg == pkg)
return true;
return false;
}
bool
force_depends(struct deppossi *possi)
{
return fc_depends ||
ignore_depends(possi->ed) ||
ignore_depends(possi->up->up);
}
bool
force_breaks(struct deppossi *possi)
{
return fc_breaks ||
ignore_depends(possi->ed) ||
ignore_depends(possi->up->up);
}
bool
force_conflicts(struct deppossi *possi)
{
return fc_conflicts;
}
/**
* Returns the path to the script inside the chroot.
*/
static const char *
preexecscript(struct command *cmd)
{
const char *admindir = dpkg_db_get_dir();
size_t instdirl = strlen(instdir);
if (*instdir) {
if (strncmp(admindir, instdir, instdirl) != 0)
ohshit(_("admindir must be inside instdir for dpkg to work properly"));
if (setenv("DPKG_ADMINDIR", admindir + instdirl, 1) < 0)
ohshite(_("unable to setenv for subprocesses"));
if (chroot(instdir)) ohshite(_("failed to chroot to `%.250s'"),instdir);
if (chdir("/"))
ohshite(_("failed to chdir to `%.255s'"), "/");
}
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 (!instdirl)
return cmd->filename;
assert(strlen(cmd->filename) >= instdirl);
return cmd->filename + instdirl;
}
void
post_postinst_tasks(struct pkginfo *pkg, enum pkgstatus new_status)
{
if (new_status < stat_triggersawaited)
pkg->status = new_status;
else
pkg->status = pkg->trigaw.head ? stat_triggersawaited :
pkg->trigpend_head ? stat_triggerspending : stat_installed;
post_postinst_tasks_core(pkg);
}
void
post_postinst_tasks_core(struct pkginfo *pkg)
{
modstatdb_note(pkg);
if (!f_noact) {
debug(dbg_triggersdetail, "post_postinst_tasks_core - trig_incorporate");
trig_incorporate(msdbrw_write);
}
}
static void
post_script_tasks(void)
{
ensure_diversions();
debug(dbg_triggersdetail,
"post_script_tasks - ensure_diversions; trig_incorporate");
trig_incorporate(msdbrw_write);
}
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);
}
static int
do_script(struct pkginfo *pkg, struct pkgbin *pif,
struct command *cmd, struct stat *stab, int warn)
{
pid_t pid;
int r;
setexecute(cmd->filename, stab);
push_cleanup(cu_post_script_tasks, ehflag_bombout, NULL, 0, 0);
pid = subproc_fork();
if (pid == 0) {
if (setenv("DPKG_MAINTSCRIPT_PACKAGE", pkg->name, 1) ||
setenv("DPKG_MAINTSCRIPT_ARCH", pif->arch, 1) ||
setenv("DPKG_MAINTSCRIPT_NAME", cmd->argv[0], 1) ||
setenv("DPKG_RUNNING_VERSION", PACKAGE_VERSION, 1))
ohshite(_("unable to setenv for maintainer script"));
cmd->filename = cmd->argv[0] = preexecscript(cmd);
command_exec(cmd);
}
subproc_signals_setup(cmd->name); /* This does a push_cleanup(). */
r = subproc_wait_check(pid, cmd->name, warn);
pop_cleanup(ehflag_normaltidy);
pop_cleanup(ehflag_normaltidy);
return r;
}
static int
vmaintainer_script_installed(struct pkginfo *pkg, const char *scriptname,
const char *desc, va_list args)
{
struct command cmd;
const char *scriptpath;
struct stat stab;
char buf[100];
scriptpath= pkgadminfile(pkg,scriptname);
sprintf(buf, _("installed %s script"), 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, "vmaintainer_script_installed nonexistent %s",
scriptname);
return 0;
}
ohshite(_("unable to stat %s `%.250s'"), buf, scriptpath);
}
do_script(pkg, &pkg->installed, &cmd, &stab, 0);
command_destroy(&cmd);
return 1;
}
/*
* All ...'s in maintainer_script_* are const char *'s.
*/
int
maintainer_script_installed(struct pkginfo *pkg, const char *scriptname,
const char *desc, ...)
{
int r;
va_list args;
va_start(args, desc);
r = vmaintainer_script_installed(pkg, scriptname, desc, args);
va_end(args);
if (r)
post_script_tasks();
return r;
}
int
maintainer_script_postinst(struct pkginfo *pkg, ...)
{
int r;
va_list args;
va_start(args, pkg);
r = vmaintainer_script_installed(pkg, POSTINSTFILE, "post-installation", args);
va_end(args);
if (r)
ensure_diversions();
return r;
}
int
maintainer_script_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[100];
strcpy(cidirrest, scriptname);
sprintf(buf, _("new %s script"), 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,"maintainer_script_new nonexistent %s `%s'",scriptname,cidir);
return 0;
}
ohshite(_("unable to stat %s `%.250s'"), buf, cidir);
}
do_script(pkg, &pkg->available, &cmd, &stab, 0);
command_destroy(&cmd);
post_script_tasks();
return 1;
}
int maintainer_script_alternative(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[100];
oldscriptpath= pkgadminfile(pkg,scriptname);
sprintf(buf, _("old %s script"), 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,"maintainer_script_alternative nonexistent %s `%s'",
scriptname,oldscriptpath);
command_destroy(&cmd);
return 0;
}
warning(_("unable to stat %s '%.250s': %s"),
cmd.name, oldscriptpath, strerror(errno));
} else {
if (!do_script(pkg, &pkg->installed, &cmd, &stab, PROCWARN)) {
command_destroy(&cmd);
post_script_tasks();
return 1;
}
}
fprintf(stderr, _("dpkg - trying script from the new package instead ...\n"));
strcpy(cidirrest,scriptname);
sprintf(buf, _("new %s script"), desc);
command_destroy(&cmd);
command_init(&cmd, cidir, buf);
command_add_args(&cmd, scriptname, iffallback,
versiondescribe(&pkg->installed.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);
}
do_script(pkg, &pkg->available, &cmd, &stab, 0);
fprintf(stderr, _("dpkg: ... it looks like that went OK.\n"));
command_destroy(&cmd);
post_script_tasks();
return 1;
}
void clear_istobes(void) {
struct pkgiterator *it;
struct pkginfo *pkg;
it = pkg_db_iter_new();
while ((pkg = pkg_db_iter_next(it)) != NULL) {
ensure_package_clientdata(pkg);
pkg->clientdata->istobe= itb_normal;
pkg->clientdata->replacingfilesandsaid= 0;
}
pkg_db_iter_free(it);
}
/*
* Returns true if the directory contains conffiles belonging to pkg,
* false otherwise.
*/
bool
dir_has_conffiles(struct filenamenode *file, struct pkginfo *pkg)
{
struct conffile *conff;
size_t namelen;
debug(dbg_veryverbose, "dir_has_conffiles '%s' (from %s)", file->name,
pkg->name);
namelen = strlen(file->name);
for (conff= pkg->installed.conffiles; conff; conff= conff->next) {
if (strncmp(file->name, conff->name, namelen) == 0 &&
conff->name[namelen] == '/') {
debug(dbg_veryverbose, "directory %s has conffile %s from %s",
file->name, conff->name, pkg->name);
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 filenamenode *file, struct pkginfo *pkg)
{
struct filepackages_iterator *iter;
struct pkginfo *other_pkg;
debug(dbg_veryverbose, "dir_is_used_by_others '%s' (except %s)", file->name,
pkg ? pkg->name : "<none>");
iter = filepackages_iter_new(file);
while ((other_pkg = filepackages_iter_next(iter))) {
debug(dbg_veryverbose, "dir_is_used_by_others considering %s ...",
other_pkg->name);
if (other_pkg == pkg)
continue;
debug(dbg_veryverbose, "dir_is_used_by_others yes");
return true;
}
filepackages_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 filenamenode *file, struct pkginfo *pkg,
struct fileinlist *list)
{
struct fileinlist *node;
size_t namelen;
debug(dbg_veryverbose, "dir_is_used_by_pkg '%s' (by %s)",
file->name, pkg ? pkg->name : "<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 &&
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;
}
void oldconffsetflags(const struct conffile *searchconff) {
struct filenamenode *namenode;
while (searchconff) {
namenode= findnamenode(searchconff->name, 0); /* XXX */
namenode->flags |= fnnf_old_conff;
if (!namenode->oldhash)
namenode->oldhash= searchconff->hash;
debug(dbg_conffdetail, "oldconffsetflags `%s' namenode %p flags %o",
searchconff->name, namenode, namenode->flags);
searchconff= searchconff->next;
}
}
/*
* If the pathname to remove is:
*
* 1. a sticky or set-id file, or
* 2. an unknown object (i.e., not a file, link, directory, fifo or socket)
*
* we change its mode so that a malicious user cannot use it, even if it's
* linked to another file.
*/
int
secure_unlink(const char *pathname)
{
struct stat stab;
if (lstat(pathname,&stab)) return -1;
return secure_unlink_statted(pathname, &stab);
}
int
secure_unlink_statted(const char *pathname, const struct stat *stab)
{
if (S_ISREG(stab->st_mode) ? (stab->st_mode & 07000) :
!(S_ISLNK(stab->st_mode) || S_ISDIR(stab->st_mode) ||
S_ISFIFO(stab->st_mode) || S_ISSOCK(stab->st_mode))) {
if (chmod(pathname, 0600))
return -1;
}
if (unlink(pathname)) return -1;
return 0;
}
void ensure_pathname_nonexisting(const char *pathname) {
pid_t pid;
const char *u;
u = path_skip_slash_dotslash(pathname);
assert(*u);
debug(dbg_eachfile,"ensure_pathname_nonexisting `%s'",pathname);
if (!rmdir(pathname))
return; /* Deleted it OK, it was a directory. */
if (errno == ENOENT || errno == ELOOP) return;
if (errno == ENOTDIR) {
/* Either it's a file, or one of the path components is. If one
* of the path components is this will fail again ... */
if (secure_unlink(pathname) == 0)
return; /* OK, it was. */
if (errno == ENOTDIR) return;
}
if (errno != ENOTEMPTY && errno != EEXIST) { /* Huh? */
ohshite(_("unable to securely remove '%.255s'"), pathname);
}
pid = subproc_fork();
if (pid == 0) {
execlp(RM, "rm", "-rf", "--", pathname, NULL);
ohshite(_("unable to execute %s (%s)"), _("rm command for cleanup"), RM);
}
debug(dbg_eachfile,"ensure_pathname_nonexisting running rm -rf");
subproc_wait_check(pid, "rm cleanup", 0);
}
void log_action(const char *action, struct pkginfo *pkg) {
log_message("%s %s %s %s", action, pkg->name,
versiondescribe(&pkg->installed.version, vdew_nonambig),
versiondescribe(&pkg->available.version, vdew_nonambig));
statusfd_send("processing: %s: %s", action, pkg->name);
}