123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791 |
- /*
- * dpkg-divert - override a package's version of a file
- *
- * Copyright © 1995 Ian Jackson
- * Copyright © 2000, 2001 Wichert Akkerman
- * Copyright © 2006-2014 Guillem Jover <guillem@debian.org>
- * Copyright © 2011 Linaro Limited
- * Copyright © 2011 Raphaël Hertzog <hertzog@debian.org>
- *
- * This program 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 program 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>
- #if HAVE_LOCALE_H
- #include <locale.h>
- #endif
- #include <fcntl.h>
- #include <fnmatch.h>
- #include <string.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <dpkg/i18n.h>
- #include <dpkg/dpkg.h>
- #include <dpkg/dpkg-db.h>
- #include <dpkg/arch.h>
- #include <dpkg/file.h>
- #include <dpkg/glob.h>
- #include <dpkg/buffer.h>
- #include <dpkg/options.h>
- #include "filesdb.h"
- static const char printforhelp[] = N_(
- "Use --help for help about diverting files.");
- static const char *admindir;
- static bool opt_pkgname_match_any = true;
- static const char *opt_pkgname = NULL;
- static const char *opt_divertto = NULL;
- static int opt_verbose = 1;
- static int opt_test = 0;
- static int opt_rename = 0;
- static void
- printversion(const struct cmdinfo *cip, const char *value)
- {
- printf(_("Debian %s version %s.\n"), dpkg_get_progname(),
- 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>"));
- exit(0);
- }
- static void
- usage(const struct cmdinfo *cip, const char *value)
- {
- printf(_(
- "Usage: %s [<option> ...] <command>\n"
- "\n"), dpkg_get_progname());
- printf(_(
- "Commands:\n"
- " [--add] <file> add a diversion.\n"
- " --remove <file> remove the diversion.\n"
- " --list [<glob-pattern>] show file diversions.\n"
- " --listpackage <file> show what package diverts the file.\n"
- " --truename <file> return the diverted file.\n"
- "\n"));
- printf(_(
- "Options:\n"
- " --package <package> name of the package whose copy of <file> will not\n"
- " be diverted.\n"
- " --local all packages' versions are diverted.\n"
- " --divert <divert-to> the name used by other packages' versions.\n"
- " --rename actually move the file aside (or back).\n"
- " --admindir <directory> set the directory with the diversions file.\n"
- " --test don't do anything, just demonstrate.\n"
- " --quiet quiet operation, minimal output.\n"
- " --help show this help message.\n"
- " --version show the version.\n"
- "\n"));
- printf(_(
- "When adding, default is --local and --divert <original>.distrib.\n"
- "When removing, --package or --local and --divert must match if specified.\n"
- "Package preinst/postrm scripts should always specify --package and --divert.\n"));
- m_output(stdout, _("<standard output>"));
- exit(0);
- }
- struct file {
- const char *name;
- enum {
- FILE_STAT_INVALID,
- FILE_STAT_VALID,
- FILE_STAT_NOFILE,
- } stat_state;
- struct stat stat;
- };
- static void
- file_init(struct file *f, const char *filename)
- {
- f->name = filename;
- f->stat_state = FILE_STAT_INVALID;
- }
- static void
- file_stat(struct file *f)
- {
- int ret;
- if (f->stat_state != FILE_STAT_INVALID)
- return;
- ret = lstat(f->name, &f->stat);
- if (ret && errno != ENOENT)
- ohshite(_("cannot stat file '%s'"), f->name);
- if (ret == 0)
- f->stat_state = FILE_STAT_VALID;
- else
- f->stat_state = FILE_STAT_NOFILE;
- }
- static void
- check_writable_dir(struct file *f)
- {
- char *tmpname;
- int tmpfd;
- tmpname = str_fmt("%s%s", f->name, ".dpkg-divert.tmp");
- tmpfd = creat(tmpname, 0600);
- if (tmpfd < 0)
- ohshite(_("error checking '%s'"), f->name);
- close(tmpfd);
- (void)unlink(tmpname);
- free(tmpname);
- }
- static bool
- check_rename(struct file *src, struct file *dst)
- {
- file_stat(src);
- /* If the source file is not present and we are not going to do
- * the rename anyway there's no point in checking any further. */
- if (src->stat_state == FILE_STAT_NOFILE)
- return false;
- file_stat(dst);
- /*
- * Unfortunately we have to check for write access in both places,
- * just having +w is not enough, since people do mount things RO,
- * and we need to fail before we start mucking around with things.
- * So we open a file with the same name as the diversions but with
- * an extension that (hopefully) won't overwrite anything. If it
- * succeeds, we assume a writable filesystem.
- */
- check_writable_dir(src);
- check_writable_dir(dst);
- if (src->stat_state == FILE_STAT_VALID &&
- dst->stat_state == FILE_STAT_VALID &&
- !(src->stat.st_dev == dst->stat.st_dev &&
- src->stat.st_ino == dst->stat.st_ino))
- ohshit(_("rename involves overwriting '%s' with\n"
- " different file '%s', not allowed"),
- dst->name, src->name);
- return true;
- }
- static void
- file_copy(const char *src, const char *dst)
- {
- struct dpkg_error err;
- char *tmp;
- int srcfd, dstfd;
- srcfd = open(src, O_RDONLY);
- if (srcfd < 0)
- ohshite(_("unable to open file '%s'"), src);
- tmp = str_fmt("%s%s", dst, ".dpkg-divert.tmp");
- dstfd = creat(tmp, 0600);
- if (dstfd < 0)
- ohshite(_("unable to create file '%s'"), tmp);
- push_cleanup(cu_filename, ~ehflag_normaltidy, NULL, 0, 1, tmp);
- if (fd_fd_copy(srcfd, dstfd, -1, &err) < 0)
- ohshit(_("cannot copy '%s' to '%s': %s"), src, tmp, err.str);
- close(srcfd);
- if (fsync(dstfd))
- ohshite(_("unable to sync file '%s'"), tmp);
- if (close(dstfd))
- ohshite(_("unable to close file '%s'"), tmp);
- file_copy_perms(src, tmp);
- if (rename(tmp, dst) != 0)
- ohshite(_("cannot rename '%s' to '%s'"), tmp, dst);
- free(tmp);
- pop_cleanup(ehflag_normaltidy);
- }
- static void
- file_rename(struct file *src, struct file *dst)
- {
- if (src->stat_state == FILE_STAT_NOFILE)
- return;
- if (dst->stat_state == FILE_STAT_VALID) {
- if (unlink(src->name))
- ohshite(_("rename: remove duplicate old link '%s'"),
- src->name);
- } else {
- if (rename(src->name, dst->name) == 0)
- return;
- /* If a rename didn't work try moving the file instead. */
- file_copy(src->name, dst->name);
- if (unlink(src->name))
- ohshite(_("unable to remove copied source file '%s'"),
- src->name);
- }
- }
- static void
- diversion_check_filename(const char *filename)
- {
- if (filename[0] != '/')
- badusage(_("filename \"%s\" is not absolute"), filename);
- if (strchr(filename, '\n') != NULL)
- badusage(_("file may not contain newlines"));
- }
- static const char *
- diversion_pkg_name(struct diversion *d)
- {
- if (d->pkgset == NULL)
- return ":";
- else
- return d->pkgset->name;
- }
- static const char *
- varbuf_diversion(struct varbuf *str, const char *pkgname,
- const char *filename, const char *divertto)
- {
- varbuf_reset(str);
- if (pkgname == NULL) {
- if (divertto == NULL)
- varbuf_printf(str, _("local diversion of %s"), filename);
- else
- varbuf_printf(str, _("local diversion of %s to %s"),
- filename, divertto);
- } else {
- if (divertto == NULL)
- varbuf_printf(str, _("diversion of %s by %s"),
- filename, pkgname);
- else
- varbuf_printf(str, _("diversion of %s to %s by %s"),
- filename, divertto, pkgname);
- }
- return str->buf;
- }
- static const char *
- diversion_current(const char *filename)
- {
- static struct varbuf str = VARBUF_INIT;
- if (opt_pkgname_match_any) {
- varbuf_reset(&str);
- if (opt_divertto == NULL)
- varbuf_printf(&str, _("any diversion of %s"), filename);
- else
- varbuf_printf(&str, _("any diversion of %s to %s"),
- filename, opt_divertto);
- } else {
- return varbuf_diversion(&str, opt_pkgname, filename, opt_divertto);
- }
- return str.buf;
- }
- static const char *
- diversion_describe(struct diversion *d)
- {
- static struct varbuf str = VARBUF_INIT;
- const char *pkgname;
- const char *name_from, *name_to;
- if (d->camefrom) {
- name_from = d->camefrom->name;
- name_to = d->camefrom->divert->useinstead->name;
- } else {
- name_from = d->useinstead->divert->camefrom->name;
- name_to = d->useinstead->name;
- }
- if (d->pkgset == NULL)
- pkgname = NULL;
- else
- pkgname = d->pkgset->name;
- return varbuf_diversion(&str, pkgname, name_from, name_to);
- }
- static void
- divertdb_write(void)
- {
- char *dbname;
- struct atomic_file *file;
- struct fileiterator *iter;
- struct filenamenode *namenode;
- dbname = dpkg_db_get_path(DIVERSIONSFILE);
- file = atomic_file_new(dbname, ATOMIC_FILE_BACKUP);
- atomic_file_open(file);
- iter = files_db_iter_new();
- while ((namenode = files_db_iter_next(iter))) {
- struct diversion *d = namenode->divert;
- if (d == NULL || d->useinstead == NULL)
- continue;
- fprintf(file->fp, "%s\n%s\n%s\n",
- d->useinstead->divert->camefrom->name,
- d->useinstead->name,
- diversion_pkg_name(d));
- }
- files_db_iter_free(iter);
- atomic_file_sync(file);
- atomic_file_close(file);
- atomic_file_commit(file);
- atomic_file_free(file);
- free(dbname);
- }
- static bool
- diversion_is_owned_by_self(struct pkgset *set, struct filenamenode *namenode)
- {
- struct pkginfo *pkg;
- struct filepackages_iterator *iter;
- bool owned = false;
- if (set == NULL)
- return false;
- for (pkg = &set->pkg; pkg; pkg = pkg->arch_next)
- ensure_packagefiles_available(pkg);
- iter = filepackages_iter_new(namenode);
- while ((pkg = filepackages_iter_next(iter))) {
- if (pkg->set == set) {
- owned = true;
- break;
- }
- }
- filepackages_iter_free(iter);
- return owned;
- }
- static int
- diversion_add(const char *const *argv)
- {
- const char *filename = argv[0];
- struct file file_from, file_to;
- struct diversion *contest, *altname;
- struct filenamenode *fnn_from, *fnn_to;
- struct pkgset *pkgset;
- opt_pkgname_match_any = false;
- /* Handle filename. */
- if (!filename || argv[1])
- badusage(_("--%s needs a single argument"), cipaction->olong);
- diversion_check_filename(filename);
- file_init(&file_from, filename);
- file_stat(&file_from);
- if (file_from.stat_state == FILE_STAT_VALID &&
- S_ISDIR(file_from.stat.st_mode))
- badusage(_("cannot divert directories"));
- fnn_from = findnamenode(filename, 0);
- /* Handle divertto. */
- if (opt_divertto == NULL)
- opt_divertto = str_fmt("%s.distrib", filename);
- if (strcmp(filename, opt_divertto) == 0)
- badusage(_("cannot divert file '%s' to itself"), filename);
- file_init(&file_to, opt_divertto);
- fnn_to = findnamenode(opt_divertto, 0);
- /* Handle package name. */
- if (opt_pkgname == NULL)
- pkgset = NULL;
- else
- pkgset = pkg_db_find_set(opt_pkgname);
- /* Check we are not stomping over an existing diversion. */
- if (fnn_from->divert || fnn_to->divert) {
- if (fnn_to->divert && fnn_to->divert->camefrom &&
- strcmp(fnn_to->divert->camefrom->name, filename) == 0 &&
- fnn_from->divert && fnn_from->divert->useinstead &&
- strcmp(fnn_from->divert->useinstead->name, opt_divertto) == 0 &&
- fnn_from->divert->pkgset == pkgset) {
- if (opt_verbose > 0)
- printf(_("Leaving '%s'\n"),
- diversion_describe(fnn_from->divert));
- return 0;
- }
- ohshit(_("'%s' clashes with '%s'"),
- diversion_current(filename),
- fnn_from->divert ?
- diversion_describe(fnn_from->divert) :
- diversion_describe(fnn_to->divert));
- }
- /* Create new diversion. */
- contest = nfmalloc(sizeof(*contest));
- altname = nfmalloc(sizeof(*altname));
- altname->camefrom = fnn_from;
- altname->camefrom->divert = contest;
- altname->useinstead = NULL;
- altname->pkgset = pkgset;
- contest->useinstead = fnn_to;
- contest->useinstead->divert = altname;
- contest->camefrom = NULL;
- contest->pkgset = pkgset;
- /* Update database and file system if needed. */
- if (opt_verbose > 0)
- printf(_("Adding '%s'\n"), diversion_describe(contest));
- if (opt_rename)
- opt_rename = check_rename(&file_from, &file_to);
- /* Check we are not renaming a file owned by the diverting pkgset. */
- if (opt_rename && diversion_is_owned_by_self(pkgset, fnn_from)) {
- if (opt_verbose > 0)
- printf(_("Ignoring request to rename file '%s' "
- "owned by diverting package '%s'\n"),
- filename, pkgset->name);
- opt_rename = false;
- }
- if (!opt_test) {
- divertdb_write();
- if (opt_rename)
- file_rename(&file_from, &file_to);
- }
- return 0;
- }
- static bool
- diversion_is_shared(struct pkgset *set, struct filenamenode *namenode)
- {
- const char *archname;
- struct pkginfo *pkg;
- struct dpkg_arch *arch;
- struct filepackages_iterator *iter;
- bool shared = false;
- if (set == NULL)
- return false;
- archname = getenv("DPKG_MAINTSCRIPT_ARCH");
- arch = dpkg_arch_find(archname);
- if (arch->type == DPKG_ARCH_NONE || arch->type == DPKG_ARCH_EMPTY)
- return false;
- for (pkg = &set->pkg; pkg; pkg = pkg->arch_next)
- ensure_packagefiles_available(pkg);
- iter = filepackages_iter_new(namenode);
- while ((pkg = filepackages_iter_next(iter))) {
- if (pkg->set == set && pkg->installed.arch != arch) {
- shared = true;
- break;
- }
- }
- filepackages_iter_free(iter);
- return shared;
- }
- static int
- diversion_remove(const char *const *argv)
- {
- const char *filename = argv[0];
- struct filenamenode *namenode;
- struct diversion *contest, *altname;
- struct file file_from, file_to;
- struct pkgset *pkgset;
- if (!filename || argv[1])
- badusage(_("--%s needs a single argument"), cipaction->olong);
- diversion_check_filename(filename);
- namenode = findnamenode(filename, fnn_nonew);
- if (namenode == NULL || namenode->divert == NULL ||
- namenode->divert->useinstead == NULL) {
- if (opt_verbose > 0)
- printf(_("No diversion '%s', none removed.\n"),
- diversion_current(filename));
- return 0;
- }
- if (opt_pkgname == NULL)
- pkgset = NULL;
- else
- pkgset = pkg_db_find_set(opt_pkgname);
- contest = namenode->divert;
- altname = contest->useinstead->divert;
- if (opt_divertto != NULL &&
- strcmp(opt_divertto, contest->useinstead->name) != 0)
- ohshit(_("mismatch on divert-to\n"
- " when removing '%s'\n"
- " found '%s'"),
- diversion_current(filename),
- diversion_describe(contest));
- if (!opt_pkgname_match_any && pkgset != contest->pkgset)
- ohshit(_("mismatch on package\n"
- " when removing '%s'\n"
- " found '%s'"),
- diversion_current(filename),
- diversion_describe(contest));
- /* Ignore removal request if the diverted file is still owned
- * by another package in the same set. */
- if (diversion_is_shared(pkgset, namenode)) {
- if (opt_verbose > 0)
- printf(_("Ignoring request to remove shared diversion '%s'.\n"),
- diversion_describe(contest));
- return 0;
- }
- if (opt_verbose > 0)
- printf(_("Removing '%s'\n"), diversion_describe(contest));
- file_init(&file_from, altname->camefrom->name);
- file_init(&file_to, contest->useinstead->name);
- /* Remove entries from database. */
- contest->useinstead->divert = NULL;
- altname->camefrom->divert = NULL;
- if (opt_rename)
- opt_rename = check_rename(&file_to, &file_from);
- if (opt_rename && !opt_test)
- file_rename(&file_to, &file_from);
- if (!opt_test)
- divertdb_write();
- return 0;
- }
- static int
- diversion_list(const char *const *argv)
- {
- struct fileiterator *iter;
- struct filenamenode *namenode;
- struct glob_node *glob_list = NULL;
- const char *pattern;
- while ((pattern = *argv++))
- glob_list_prepend(&glob_list, m_strdup(pattern));
- if (glob_list == NULL)
- glob_list_prepend(&glob_list, m_strdup("*"));
- iter = files_db_iter_new();
- while ((namenode = files_db_iter_next(iter))) {
- struct glob_node *g;
- struct diversion *contest = namenode->divert;
- struct diversion *altname;
- const char *pkgname;
- if (contest->useinstead == NULL)
- continue;
- altname = contest->useinstead->divert;
- pkgname = diversion_pkg_name(contest);
- for (g = glob_list; g; g = g->next) {
- if (fnmatch(g->pattern, pkgname, 0) == 0 ||
- fnmatch(g->pattern, contest->useinstead->name, 0) == 0 ||
- fnmatch(g->pattern, altname->camefrom->name, 0) == 0) {
- printf("%s\n", diversion_describe(contest));
- break;
- }
- }
- }
- files_db_iter_free(iter);
- glob_list_free(glob_list);
- return 0;
- }
- static int
- diversion_truename(const char *const *argv)
- {
- const char *filename = argv[0];
- struct filenamenode *namenode;
- if (!filename || argv[1])
- badusage(_("--%s needs a single argument"), cipaction->olong);
- diversion_check_filename(filename);
- namenode = findnamenode(filename, fnn_nonew);
- /* Print the given name if file is not diverted. */
- if (namenode && namenode->divert->useinstead)
- printf("%s\n", namenode->divert->useinstead->name);
- else
- printf("%s\n", filename);
- return 0;
- }
- static int
- diversion_listpackage(const char *const *argv)
- {
- const char *filename = argv[0];
- struct filenamenode *namenode;
- if (!filename || argv[1])
- badusage(_("--%s needs a single argument"), cipaction->olong);
- diversion_check_filename(filename);
- namenode = findnamenode(filename, fnn_nonew);
- /* Print nothing if file is not diverted. */
- if (namenode == NULL)
- return 0;
- if (namenode->divert->pkgset == NULL)
- /* Indicate package is local using something not in package
- * namespace. */
- printf("LOCAL\n");
- else
- printf("%s\n", namenode->divert->pkgset->name);
- return 0;
- }
- static void
- set_package(const struct cmdinfo *cip, const char *value)
- {
- opt_pkgname_match_any = false;
- /* If value is NULL we are being called from --local. */
- opt_pkgname = value;
- if (opt_pkgname && strchr(opt_pkgname, '\n') != NULL)
- badusage(_("package may not contain newlines"));
- }
- static void
- set_divertto(const struct cmdinfo *cip, const char *value)
- {
- opt_divertto = value;
- if (opt_divertto[0] != '/')
- badusage(_("filename \"%s\" is not absolute"), opt_divertto);
- if (strchr(opt_divertto, '\n') != NULL)
- badusage(_("divert-to may not contain newlines"));
- }
- static const struct cmdinfo cmdinfo_add =
- ACTION("add", 0, 0, diversion_add);
- static const struct cmdinfo cmdinfos[] = {
- ACTION("add", 0, 0, diversion_add),
- ACTION("remove", 0, 0, diversion_remove),
- ACTION("list", 0, 0, diversion_list),
- ACTION("listpackage", 0, 0, diversion_listpackage),
- ACTION("truename", 0, 0, diversion_truename),
- { "admindir", 0, 1, NULL, &admindir, NULL },
- { "divert", 0, 1, NULL, NULL, set_divertto },
- { "package", 0, 1, NULL, NULL, set_package },
- { "local", 0, 0, NULL, NULL, set_package },
- { "quiet", 0, 0, &opt_verbose, NULL, NULL, 0 },
- { "rename", 0, 0, &opt_rename, NULL, NULL, 1 },
- { "test", 0, 0, &opt_test, NULL, NULL, 1 },
- { "help", '?', 0, NULL, NULL, usage },
- { "version", 0, 0, NULL, NULL, printversion },
- { NULL, 0 }
- };
- int
- main(int argc, const char * const *argv)
- {
- const char *env_pkgname;
- int ret;
- dpkg_locales_init(PACKAGE);
- dpkg_program_init("dpkg-divert");
- dpkg_options_parse(&argv, cmdinfos, printforhelp);
- admindir = dpkg_db_set_dir(admindir);
- env_pkgname = getenv("DPKG_MAINTSCRIPT_PACKAGE");
- if (opt_pkgname_match_any && env_pkgname)
- set_package(NULL, env_pkgname);
- if (!cipaction)
- setaction(&cmdinfo_add, NULL);
- modstatdb_open(msdbrw_readonly);
- filesdbinit();
- ensure_diversions();
- ret = cipaction->action(argv);
- modstatdb_shutdown();
- dpkg_program_done();
- return ret;
- }
|