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

Reply via email to