The following commit has been merged in the master branch: commit 02b12d75f1eff7c184fafb5a663a0421e9a645ea Author: Guillem Jover <guil...@debian.org> Date: Fri Feb 19 05:57:29 2010 +0100
dpkg-divert: Rewrite in C diff --git a/debian/changelog b/debian/changelog index 7bedacf..eafdc9f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -61,6 +61,7 @@ dpkg (1.15.8) UNRELEASED; urgency=low * Check version syntax when parsing it from libdpkg based programs. Closes: #574704 * Rewrite mksplit in C, and merge it into dpkg-split. + * Rewrite dpkg-divert in C. [ Updated programs translations ] * Catalan (Guillem Jover). diff --git a/po/POTFILES.in b/po/POTFILES.in index d73536c..f04bfd4 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -38,6 +38,7 @@ src/archives.c src/cleanup.c src/configure.c src/depcon.c +src/divertcmd.c src/divertdb.c src/enquiry.c src/errors.c @@ -69,5 +70,3 @@ dpkg-split/split.c utils/start-stop-daemon.c utils/update-alternatives.c - -scripts/dpkg-divert.pl diff --git a/scripts/.gitignore b/scripts/.gitignore index 403beff..d12947c 100644 --- a/scripts/.gitignore +++ b/scripts/.gitignore @@ -3,7 +3,6 @@ dpkg-buildflags dpkg-buildpackage dpkg-checkbuilddeps dpkg-distaddfile -dpkg-divert dpkg-genchanges dpkg-gencontrol dpkg-gensymbols diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 718f77f..4589fa9 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -8,7 +8,6 @@ bin_SCRIPTS = \ dpkg-buildpackage \ dpkg-checkbuilddeps \ dpkg-distaddfile \ - dpkg-divert \ dpkg-genchanges \ dpkg-gencontrol \ dpkg-gensymbols \ @@ -43,7 +42,6 @@ EXTRA_DIST = \ dpkg-scansources.pl \ dpkg-shlibdeps.pl \ dpkg-source.pl \ - dpkg-divert.pl \ dpkg-vendor.pl \ changelog/debian.pl \ $(test_cases) \ diff --git a/scripts/dpkg-divert.pl b/scripts/dpkg-divert.pl deleted file mode 100755 index 949a215..0000000 --- a/scripts/dpkg-divert.pl +++ /dev/null @@ -1,369 +0,0 @@ -#!/usr/bin/perl -# -# dpkg-divert -# -# 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 <http://www.gnu.org/licenses/>. - -BEGIN { # Work-around for bug #479711 in perl - $ENV{PERL_DL_NONLAZY} = 1; -} - -use strict; -use warnings; - -use POSIX qw(:errno_h); -use Dpkg; -use Dpkg::Gettext; - -textdomain("dpkg"); - -sub version { - printf _g("Debian %s version %s.\n"), $progname, $version; - - printf _g(" -Copyright (C) 1995 Ian Jackson. -Copyright (C) 2000,2001 Wichert Akkerman."); - - printf "\n" . _g( -"This is free software; see the GNU General Public License version 2 or -later for copying conditions. There is NO warranty. -"); -} - -sub usage { - printf(_g( -"Usage: %s [<option> ...] <command> - -Commands: - [--add] <file> add a diversion. - --remove <file> remove the diversion. - --list [<glob-pattern>] show file diversions. - --listpackage <file> show what package diverts the file. - --truename <file> return the diverted file. - -Options: - --package <package> name of the package whose copy of <file> will not - be diverted. - --local all packages' versions are diverted. - --divert <divert-to> the name used by other packages' versions. - --rename actually move the file aside (or back). - --admindir <directory> set the directory with the diversions file. - --test don't do anything, just demonstrate. - --quiet quiet operation, minimal output. - --help show this help message. - --version show the version. - -When adding, default is --local and --divert <original>.distrib. -When removing, --package or --local and --divert must match if specified. -Package preinst/postrm scripts should always specify --package and --divert. -"), $progname); -} - -my $testmode = 0; -my $dorename = 0; -my $verbose = 1; -my $mode = ''; -my $package = undef; -my $divertto = undef; -my @contest; -my @altname; -my @package; -my $file; -$|=1; - - -# FIXME: those should be local. -my ($rsrc, $rdest); -my (@ssrc, @sdest); - -sub checkmanymodes { - return unless $mode; - badusage(sprintf(_g("two commands specified: %s and --%s"), $_, $mode)); -} - -while (@ARGV) { - $_= shift(@ARGV); - last if m/^--$/; - if (!m/^-/) { - unshift(@ARGV,$_); last; - } elsif (m/^--help$/) { - usage(); - exit(0); - } elsif (m/^--version$/) { - version(); - exit(0); - } elsif (m/^--test$/) { - $testmode= 1; - } elsif (m/^--rename$/) { - $dorename= 1; - } elsif (m/^--quiet$/) { - $verbose= 0; - } elsif (m/^--local$/) { - $package= ':'; - } elsif (m/^--add$/) { - checkmanymodes(); - $mode= 'add'; - } elsif (m/^--remove$/) { - checkmanymodes(); - $mode= 'remove'; - } elsif (m/^--list$/) { - checkmanymodes(); - $mode= 'list'; - } elsif (m/^--listpackage$/) { - checkmanymodes(); - $mode= 'listpackage'; - } elsif (m/^--truename$/) { - checkmanymodes(); - $mode= 'truename'; - } elsif (m/^--divert$/) { - @ARGV || badusage(sprintf(_g("--%s needs a divert-to argument"), "divert")); - $divertto= shift(@ARGV); - $divertto =~ m/\n/ && badusage(_g("divert-to may not contain newlines")); - } elsif (m/^--package$/) { - @ARGV || badusage(sprintf(_g("--%s needs a <package> argument"), "package")); - $package= shift(@ARGV); - $package =~ m/\n/ && badusage(_g("package may not contain newlines")); - } elsif (m/^--admindir$/) { - @ARGV || badusage(sprintf(_g("--%s needs a <directory> argument"), "admindir")); - $admindir= shift(@ARGV); - } else { - badusage(sprintf(_g("unknown option \`%s'"), $_)); - } -} - -$mode='add' unless $mode; - -open(O, "$admindir/diversions") || quit(sprintf(_g("cannot open diversions: %s"), $!)); -while(<O>) { - s/\n$//; push(@contest,$_); - $_ = <O>; - s/\n$// || badfmt(_g("missing altname")); - push(@altname,$_); - $_ = <O>; - s/\n$// || badfmt(_g("missing package")); - push(@package,$_); -} -close(O); - -if ($mode eq 'add') { - @ARGV == 1 || badusage(sprintf(_g("--%s needs a single argument"), "add")); - $file= $ARGV[0]; - $file =~ m#^/# || badusage(sprintf(_g("filename \"%s\" is not absolute"), $file)); - $file =~ m/\n/ && badusage(_g("file may not contain newlines")); - -d $file && badusage(_g("Cannot divert directories")); - $divertto= "$file.distrib" unless defined($divertto); - $divertto =~ m#^/# || badusage(sprintf(_g("filename \"%s\" is not absolute"), $divertto)); - $file ne $divertto || badusage(sprintf(_g("cannot divert file '%s' to itself"), $file)); - $package= ':' unless defined($package); - for (my $i = 0; $i <= $#contest; $i++) { - if ($contest[$i] eq $file || $altname[$i] eq $file || - $contest[$i] eq $divertto || $altname[$i] eq $divertto) { - if ($contest[$i] eq $file && $altname[$i] eq $divertto && - $package[$i] eq $package) { - printf(_g("Leaving \`%s'")."\n", infon($i)) if $verbose > 0; - exit(0); - } - quit(sprintf(_g("\`%s' clashes with \`%s'"), infoa(), infon($i))); - } - } - push(@contest,$file); - push(@altname,$divertto); - push(@package,$package); - printf(_g("Adding \`%s'")."\n", infon($#contest)) if $verbose > 0; - checkrename($file, $divertto); - save(); - dorename($file, $divertto); - exit(0); -} elsif ($mode eq 'remove') { - @ARGV == 1 || badusage(sprintf(_g("--%s needs a single argument"), "remove")); - $file= $ARGV[0]; - for (my $i = 0; $i <= $#contest; $i++) { - next unless $file eq $contest[$i]; - quit(sprintf(_g("mismatch on divert-to\n when removing \`%s'\n found \`%s'"), infoa(), infon($i))) - if defined($divertto) && $altname[$i] ne $divertto; - quit(sprintf(_g("mismatch on package\n when removing \`%s'\n found \`%s'"), infoa(), infon($i))) - if defined($package) && $package[$i] ne $package; - printf(_g("Removing \`%s'")."\n", infon($i)) if $verbose > 0; - my $orgfile = $contest[$i]; - my $orgdivertto = $altname[$i]; - @contest= (($i > 0 ? @contest[0..$i-1] : ()), - ($i < $#contest ? @contest[$i+1..$#contest] : ())); - @altname= (($i > 0 ? @altname[0..$i-1] : ()), - ($i < $#altname ? @altname[$i+1..$#altname] : ())); - @package= (($i > 0 ? @package[0..$i-1] : ()), - ($i < $#package ? @package[$i+1..$#package] : ())); - checkrename($orgdivertto, $orgfile); - dorename($orgdivertto, $orgfile); - save(); - exit(0); - } - printf(_g("No diversion \`%s', none removed")."\n", infoa()) - if $verbose > 0; - exit(0); -} elsif ($mode eq 'list') { - my @list; - my @ilist = @ARGV ? @ARGV : ('*'); - while (defined($_=shift(@ilist))) { - s/\W/\\$&/g; - s/\\\?/./g; - s/\\\*/.*/g; - push(@list,"^$_\$"); - } - my $pat = join('|', @list); - for (my $i = 0; $i <= $#contest; $i++) { - next unless ($contest[$i] =~ m/$pat/o || - $altname[$i] =~ m/$pat/o || - $package[$i] =~ m/$pat/o); - print infon($i), "\n"; - } - exit(0); -} elsif ($mode eq 'truename') { - @ARGV == 1 || badusage(sprintf(_g("--%s needs a single argument"), "truename")); - $file= $ARGV[0]; - for (my $i = 0; $i <= $#contest; $i++) { - next unless $file eq $contest[$i]; - print $altname[$i], "\n"; - exit(0); - } - print $file, "\n"; - exit(0); -} elsif ($mode eq 'listpackage') { - @ARGV == 1 || badusage(sprintf(_g("--%s needs a single argument"), $mode)); - $file= $ARGV[0]; - for (my $i = 0; $i <= $#contest; $i++) { - next unless $file eq $contest[$i]; - if ($package[$i] eq ':') { - # indicate package is local using something not in package namespace - print "LOCAL\n"; - } else { - print $package[$i], "\n"; - } - exit(0); - } - # print nothing if file is not diverted - exit(0); -} else { - quit(sprintf(_g("internal error - bad mode \`%s'"), $mode)); -} - -sub infol { - return ((defined($_[2]) ? ($_[2] eq ':' ? "local " : "") : "any "). - "diversion of $_[0]". - (defined($_[1]) ? " to $_[1]" : ""). - (defined($_[2]) && $_[2] ne ':' ? " by $_[2]" : "")); -} - -sub checkrename { - return unless $dorename; - ($rsrc,$rdest) = @_; - (@ssrc = lstat($rsrc)) || $! == ENOENT || - quit(sprintf(_g("cannot stat old name \`%s': %s"), $rsrc, $!)); - (@sdest = lstat($rdest)) || $! == ENOENT || - quit(sprintf(_g("cannot stat new name \`%s': %s"), $rdest, $!)); - # 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) wont overwrite anything. If it succeeds, we - # assume a writable filesystem. - if (open (TMP, ">>", "${rsrc}.dpkg-devert.tmp")) { - close TMP; - unlink ("${rsrc}.dpkg-devert.tmp"); - } elsif ($! == ENOENT) { - $dorename = !$dorename; - # If the source file is not present and we are not going to do the - # rename anyway there's no point in checking the target. - return; - } else { - quit(sprintf(_g("error checking \`%s': %s"), $rsrc, $!)); - } - - if (open (TMP, ">>", "${rdest}.dpkg-devert.tmp")) { - close TMP; - unlink ("${rdest}.dpkg-devert.tmp"); - } else { - quit(sprintf(_g("error checking \`%s': %s"), $rdest, $!)); - } - if (@ssrc && @sdest && - !($ssrc[0] == $sdest[0] && $ssrc[1] == $sdest[1])) { - quit(sprintf(_g("rename involves overwriting \`%s' with\n". - " different file \`%s', not allowed"), $rdest, $rsrc)); - } -} - -sub rename_mv($$) -{ - return (rename($_[0], $_[1]) || (system(("mv", $_[0], $_[1])) == 0)); -} - -sub dorename { - return unless $dorename; - return if $testmode; - if (@ssrc) { - if (@sdest) { - unlink($rsrc) || quit(sprintf(_g("rename: remove duplicate old link \`%s': %s"), $rsrc, $!)); - } else { - rename_mv($rsrc, $rdest) || - quit(sprintf(_g("rename: rename \`%s' to \`%s': %s"), $rsrc, $rdest, $!)); - } - } -} - -sub save { - return if $testmode; - open(N, "> $admindir/diversions-new") || quit(sprintf(_g("create diversions-new: %s"), $!)); - chmod 0644, "$admindir/diversions-new"; - for (my $i = 0; $i <= $#contest; $i++) { - print(N "$contest[$i]\n$altname[$i]\n$package[$i]\n") - || quit(sprintf(_g("write diversions-new: %s"), $!)); - } - close(N) || quit(sprintf(_g("close diversions-new: %s"), $!)); - unlink("$admindir/diversions-old") || - $! == ENOENT || quit(sprintf(_g("remove old diversions-old: %s"), $!)); - link("$admindir/diversions","$admindir/diversions-old") || - $! == ENOENT || quit(sprintf(_g("create new diversions-old: %s"), $!)); - rename("$admindir/diversions-new","$admindir/diversions") - || quit(sprintf(_g("install new diversions: %s"), $!)); -} - -sub infoa -{ - infol($file, $divertto, $package); -} - -sub infon -{ - my $i = shift; - infol($contest[$i], $altname[$i], $package[$i]); -} - -sub quit -{ - printf STDERR "%s: %s\n", $progname, "@_"; - exit(2); -} - -sub badusage -{ - printf STDERR "%s: %s\n\n", $progname, "@_"; - usage(); - exit(2); -} - -sub badfmt -{ - quit(sprintf(_g("internal error: %s corrupt: %s"), "$admindir/diversions", $_[0])); -} - diff --git a/scripts/t/950_dpkg_divert.t b/scripts/t/950_dpkg_divert.t index f65d1e5..bff9e1e 100644 --- a/scripts/t/950_dpkg_divert.t +++ b/scripts/t/950_dpkg_divert.t @@ -26,7 +26,7 @@ my $tmpdir = 't.tmp/950_dpkg_divert'; my $admindir = File::Spec->rel2abs("$tmpdir/admindir"); my $testdir = File::Spec->rel2abs("$tmpdir/testdir"); -my @dd = ("$builddir/dpkg-divert"); +my @dd = ("$builddir/../src/dpkg-divert"); plan tests => 235; diff --git a/src/.gitignore b/src/.gitignore index 10f60e9..1602cb9 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,4 +1,5 @@ dpkg +dpkg-divert dpkg-query dpkg-statoverride dpkg-trigger diff --git a/src/Makefile.am b/src/Makefile.am index 414755f..152cfb7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,6 +14,7 @@ AM_CPPFLAGS = \ bin_PROGRAMS = \ dpkg \ + dpkg-divert \ dpkg-query \ dpkg-statoverride \ dpkg-trigger @@ -45,6 +46,17 @@ dpkg_LDADD = \ $(LIBINTL) \ $(SELINUX_LIBS) +dpkg_divert_SOURCES = \ + glob.c glob.h \ + filesdb.c filesdb.h \ + divertdb.c \ + divertcmd.c + +dpkg_divert_LDADD = \ + ../lib/dpkg/libdpkg.a \ + ../lib/compat/libcompat.a \ + $(LIBINTL) + dpkg_query_SOURCES = \ filesdb.c filesdb.h \ divertdb.c \ diff --git a/src/divertcmd.c b/src/divertcmd.c new file mode 100644 index 0000000..383ea3e --- /dev/null +++ b/src/divertcmd.c @@ -0,0 +1,757 @@ +/* + * dpkg-divert - override a package's version of a file + * + * Copyright © 1995 Ian Jackson + * Copyright © 2000, 2001 Wichert Akkerman + * Copyright © 2010 Guillem Jover <guil...@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 <http://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/file.h> +#include <dpkg/buffer.h> +#include <dpkg/myopt.h> + +#include "glob.h" +#include "filesdb.h" + + +const char thisname[] = "dpkg-divert"; +const char printforhelp[] = N_("Use --help for help about querying packages."); + +const struct cmdinfo *cipaction = NULL; +const char *admindir = 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"), thisname, DPKG_VERSION_ARCH); + + printf(_( +"Copyright (C) 1995 Ian Jackson.\n" +"Copyright (C) 2000,2001 Wichert Akkerman.\n" +"Copyright (C) 2010 Guillem Jover.\n")); + + 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"), thisname); + + 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 bool +check_rename(struct file *src, struct file *dst) +{ + struct varbuf tmpname = VARBUF_INIT; + int tmpfd; + + file_stat(src); + 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) wont overwrite anything. If it + * succeeds, we assume a writable filesystem. + */ + + varbufprintf(&tmpname, "%s%s", src->name, ".dpkg-divert.tmp"); + + tmpfd = creat(tmpname.buf, 0600); + if (tmpfd >= 0) { + close(tmpfd); + unlink(tmpname.buf); + } else if (errno == ENOENT) { + varbuf_destroy(&tmpname); + + /* If the source file is not present and we are not going + * to do the rename anyway there's no point in checking the + * target. */ + return false; + } else + ohshite(_("error checking '%s'"), src->name); + + varbufreset(&tmpname); + varbufprintf(&tmpname, "%s%s", dst->name, ".dpkg-divert.tmp"); + + tmpfd = creat(tmpname.buf, 0600); + if (tmpfd >= 0) { + close(tmpfd); + unlink(tmpname.buf); + } else + ohshite(_("error checking '%s'"), dst->name); + + varbuf_destroy(&tmpname); + + 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 int +file_copy(const char *src, const char *dst) +{ + int srcfd, dstfd; + + srcfd = open(src, O_RDONLY); + if (srcfd < 0) + return -1; + + dstfd = creat(dst, 0600); + if (dstfd < 0) + return -1; + + /* FIXME: leaves a dangling destination file on error. */ + + fd_fd_copy(srcfd, dstfd, -1, _("file copy")); + + close(srcfd); + + if (fsync(dstfd)) + return -1; + if (close(dstfd)) + return -1; + + file_copy_perms(src, dst); + + return 0; +} + +static int +rename_mv(const char *src, const char *dst) +{ + struct varbuf tmpdst = VARBUF_INIT; + + if (rename(src, dst) == 0) + return 0; + + varbufprintf(&tmpdst, "%s%s", dst, ".dpkg-divert.tmp"); + + /* If a simple rename didn't work try an atomic copy, rename, unlink + * instead. */ + if (file_copy(src, tmpdst.buf) != 0) + return -1; + + if (rename(tmpdst.buf, dst) != 0) + return -1; + + varbuf_destroy(&tmpdst); + + return -1; +} + +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_mv(src->name, dst->name)) + ohshite(_("cannot rename '%s' to '%s'"), + src->name, dst->name); + } +} + +static const char * +diversion_pkg_name(struct diversion *d) +{ + if (d->pkg == NULL) + return ":"; + else + return d->pkg->name; +} + +static const char * +varbuf_diversion(struct varbuf *str, const char *pkgname, + const char *filename, const char *divertto) +{ + varbufreset(str); + + if (pkgname == NULL) { + if (divertto == NULL) + varbufprintf(str, _("local diversion of %s"), filename); + else + varbufprintf(str, _("local diversion of %s to %s"), + filename, divertto); + } else { + if (divertto == NULL) + varbufprintf(str, _("diversion of %s by %s"), + filename, pkgname); + else + varbufprintf(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) { + varbufreset(&str); + + if (opt_divertto == NULL) + varbufprintf(&str, _("any diversion of %s"), filename); + else + varbufprintf(&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->pkg == NULL) + pkgname = NULL; + else + pkgname = d->pkg->name; + + return varbuf_diversion(&str, pkgname, name_from, name_to); +} + +static void +divertdb_write(void) +{ + FILE *dbfile; + struct fileiterator *iter; + struct filenamenode *namenode; + struct varbuf dbname = VARBUF_INIT; + struct varbuf dbname_new = VARBUF_INIT; + struct varbuf dbname_old = VARBUF_INIT; + + varbufprintf(&dbname, "%s/%s", admindir, DIVERSIONSFILE); + varbufprintf(&dbname_new, "%s%s", dbname.buf, NEWDBEXT); + varbufprintf(&dbname_old, "%s%s", dbname.buf, OLDDBEXT); + + dbfile = fopen(dbname_new.buf, "w"); + if (!dbfile) + ohshite(_("cannot create new %s file"), DIVERSIONSFILE); + chmod(dbname_new.buf, 0644); + + iter = iterfilestart(); + while ((namenode = iterfilenext(iter))) { + struct diversion *d = namenode->divert; + + if (d == NULL || d->useinstead == NULL) + continue; + + fprintf(dbfile, "%s\n%s\n%s\n", + d->useinstead->divert->camefrom->name, + d->useinstead->name, + diversion_pkg_name(d)); + } + iterfileend(iter); + + if (fflush(dbfile)) + ohshite(_("unable to flush file '%s'"), dbname_new.buf); + if (fsync(fileno(dbfile))) + ohshite(_("unable to sync file '%s'"), dbname_new.buf); + if (fclose(dbfile)) + ohshite(_("unable to close file '%s'"), dbname_new.buf); + + if (unlink(dbname_old.buf) && errno != ENOENT) + ohshite(_("error removing old diversions-old")); + if (link(dbname.buf, dbname_old.buf) && errno != ENOENT) + ohshite(_("error creating new diversions-old")); + if (rename(dbname_new.buf, dbname.buf)) + ohshite(_("error installing new diversions")); + + varbuf_destroy(&dbname); + varbuf_destroy(&dbname_new); + varbuf_destroy(&dbname_old); +} + +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 pkginfo *pkg; + + opt_pkgname_match_any = false; + + /* Handle filename. */ + if (!filename || argv[1]) + badusage(_("--%s needs a single argument"), cipaction->olong); + + if (filename[0] != '/') + badusage(_("filename \"%s\" is not absolute"), filename); + if (strchr(filename, '\n') != NULL) + badusage(_("file may not contain newlines")); + + file_init(&file_from, filename); + file_stat(&file_from); + + if (S_ISDIR(file_from.stat.st_mode)) + badusage(_("Cannot divert directories")); + + fnn_from = findnamenode(filename, 0); + + /* Handle divertto. */ + if (opt_divertto == NULL) { + struct varbuf str = VARBUF_INIT; + + varbufprintf(&str, "%s.distrib", filename); + opt_divertto = varbuf_detach(&str); + } + if (opt_divertto[0] != '/') + badusage(_("filename \"%s\" is not absolute"), opt_divertto); + + 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) + pkg = NULL; + else + pkg = findpackage(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->pkg == pkg) { + if (opt_verbose > 0) + printf(_("Leaving '%s'\n"), + diversion_describe(fnn_from->divert)); + exit(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->pkg = pkg; + + contest->useinstead = fnn_to; + contest->useinstead->divert = altname; + contest->camefrom = NULL; + contest->pkg = pkg; + + /* 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); + if (!opt_test) { + divertdb_write(); + if (opt_rename) + file_rename(&file_from, &file_to); + } + + return 0; +} + +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 pkginfo *pkg; + + if (!filename || argv[1]) + badusage(_("--%s needs a single argument"), cipaction->olong); + + 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) + pkg = NULL; + else + pkg = findpackage(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 && pkg != contest->pkg) + ohshit(_("mismatch on package\n" + " when removing `%s'\n" + " found `%s'"), + diversion_current(filename), + diversion_describe(contest)); + + 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 = iterfilestart(); + while ((namenode = iterfilenext(iter))) { + struct glob_node *g; + struct diversion *contest = namenode->divert; + struct diversion *altname; + const char *pkg_name; + + if (contest->useinstead == NULL) + continue; + + altname = contest->useinstead->divert; + + pkg_name = diversion_pkg_name(contest); + + for (g = glob_list; g; g = g->next) { + if (fnmatch(g->pattern, pkg_name, 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; + } + } + } + iterfileend(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); + + 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); + + namenode = findnamenode(filename, fnn_nonew); + + /* Print nothing if file is not diverted. */ + if (namenode == NULL) + return 0; + + if (namenode->divert->pkg == NULL) + /* Indicate package is local using something not in package + * namespace. */ + printf("LOCAL\n"); + else + printf("%s\n", namenode->divert->pkg->name); + + return 0; +} + +static inline int +option_short(int c) +{ + return c ? c : '\b'; +} + +static void +setaction(const struct cmdinfo *cip, const char *value) +{ + if (cipaction) + badusage(_("conflicting actions -%c (--%s) and -%c (--%s)"), + option_short(cip->oshort), cip->olong, + option_short(cipaction->oshort), cipaction->olong); + cipaction = cip; +} + +static void +setpackage(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 +setdivertto(const struct cmdinfo *cip, const char *value) +{ + opt_divertto = value; + + if (strchr(opt_divertto, '\n') != NULL) + badusage(_("divert-to may not contain newlines")); +} + +#define ACTION(longopt, shortopt, code, function) \ + { longopt, shortopt, 0, 0, 0, setaction, code, 0, (voidfnp)function } + +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, setdivertto }, + { "package", 0, 1, NULL, NULL, setpackage }, + { "local", 0, 0, NULL, NULL, setpackage }, + { "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, 0, NULL, NULL, usage }, + { "version", 0, 0, NULL, NULL, printversion }, + { NULL, 0 } +}; + +int +main(int argc, const char * const *argv) +{ + jmp_buf ejbuf; + int (*actionfunction)(const char *const *argv); + int ret; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + standard_startup(&ejbuf); + myopt(&argv, cmdinfos); + + if (!cipaction) + cipaction = &cmdinfo_add; + + actionfunction = (int (*)(const char *const *))cipaction->farg; + + setvbuf(stdout, NULL, _IONBF, 0); + + filesdbinit(); + ensure_diversions(); + + ret = actionfunction(argv); + + standard_shutdown(); + + return ret; +} -- dpkg's main repository -- To UNSUBSCRIBE, email to debian-dpkg-cvs-requ...@lists.debian.org with a subject of "unsubscribe". Trouble? Contact listmas...@lists.debian.org