[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

PATCH: dpkg-divert in C



Appended is a rewrite of dpkg-divert in C.  I'm planning to rewrite
all of the Perl scripts that can get run from package install/remove
scripts in C, so that it will no longer be possible for a busted Perl
installation to mangle the entire packaging system.  In addition to
dpkg-divert, that's update-alternatives, update-rc.d, dpkg-statoverride,
and maybe install-info.  [I would prefer to figure out why exactly we
can't use the upstream install-info and correct that.]  I may have
missed one or two.

There are a few functional changes.  It will now prepend the current
directory to relative paths, rather than reject them.  If you'd rather
the old behavior I will put it back.  Some of the messages printed are
slightly different, there are now short options to go with the long
ones, and the manpage has been updated.

I have put this through its paces with a dummy diversions file, but
not tested it in production.

zw

ChangeLog:

	* scripts/dpkg-divert.8: Move to utils.
	* scripts/dpkg-divert.pl: Delete.
	* scripts/Makefile.in: Don't build dpkg-divert.

	* utils/Makefile.in: Build dpkg-divert from dpkg-divert.c.
	* utils/dpkg-divert.8: Move from scripts, update to match code.
	* utils/dpkg-divert.c: New file - program reimplemented in C.

===================================================================
Index: scripts/Makefile.in
--- scripts/Makefile.in	2000/11/08 14:03:01	1.12
+++ scripts/Makefile.in	2001/01/29 09:06:33
@@ -10,13 +10,13 @@ BIN_SCRIPTS		= dpkg-name dpkg-source dpk
 			  dpkg-scanpackages dpkg-scansources dpkg-architecture
 
 SBIN_SCRIPTS		= update-rc.d update-alternatives install-info \
-			  dpkg-divert dpkg-statoverride cleanup-info
+			  dpkg-statoverride cleanup-info
 
 MAN1PAGES		= dpkg-name.1 dpkg-source.1 822-date.1 \
 			  dpkg-architecture.1
 MAN8PAGES		= update-rc.d.8 update-alternatives.8 install-info.8  \
-			  cleanup-info.8 dpkg-scanpackages.8 dpkg-scansources.8 \
-			  dpkg-divert.8 dpkg-statoverride.8
+			  cleanup-info.8 dpkg-scanpackages.8 \
+			  dpkg-scansources.8 dpkg-statoverride.8
 
 CHANGELOG_PARSERS	= cl-debian
 
@@ -24,10 +24,12 @@ SCRIPTLIBS		= controllib.pl
 
 GEN_MAN8PAGES		= dpkg-scansources.8
 
-MAN_SOURCE_ALIASES	= dpkg-gencontrol.1 dpkg-genchanges.1 dpkg-buildpackage.1 \
-			  dpkg-distaddfile.1 dpkg-parsechangelog.1 dpkg-shlibdeps.1
+MAN_SOURCE_ALIASES	= dpkg-gencontrol.1 dpkg-genchanges.1 \
+			  dpkg-buildpackage.1 dpkg-distaddfile.1 \
+			  dpkg-parsechangelog.1 dpkg-shlibdeps.1
 
-GENFILES		= $(CHANGELOG_PARSERS) $(BIN_SCRIPTS) $(SBIN_SCRIPTS) $(GEN_MAN8PAGES)
+GENFILES		= $(CHANGELOG_PARSERS) $(BIN_SCRIPTS) \
+			  $(SBIN_SCRIPTS) $(GEN_MAN8PAGES)
 
 .PHONY: all
 all:: $(GENFILES)
===================================================================
Index: scripts/dpkg-divert.8
--- scripts/dpkg-divert.8	Mon Jan 29 01:06:34 2001
+++ scripts/dpkg-divert.8	Sun Nov 12 00:28:38 2000
@@ -1,88 +0,0 @@
-.TH DPKG-DIVERT 8 "December 1999" "Debian Project" "dpkg utilities"
-.SH NAME
-dpkg-divert -- override a package's version of a file
-.SH SYNOPSIS
-.B dpkg-divert
-[options] [--add]
-.I <file>
-.br
-.B dpkg-divert
-[options] --remove
-.I <file>
-.br
-.B dpkg-divert
-[options]
---list
-.I <glob-pattern>
-.B dpkg-divert
-[options] --truename
-.I <file>
-.br
-.SH DESCRIPTION
-File `diversions' are a way of forcing dpkg not to install a file into its
-location, but to a `diverted' location. Diversions can be used through the
-Debian package scripts to move a file away when it causes a conflict. System
-administrators can also use it to override some package's configuration
-file, or whenever some files (which aren't marked as 'conffiles') need to be
-preserved by dpkg, when installing a newer version of a package which
-contains those files.
-.sp
-.B dpkg-divert
-is the utility used to set up and update the list of diversions. It
-functions in three basic modes - adding, removing, and listing diversions.
-The options are --add, --remove, and --list, respectively. Additionally,
-it can print out the real name for a diverted file. Other options
-(listed below) may also be specified.
-.SH OPTIONS
-.TP
-.I --admindir <directory>
-Set the dpkg data directory to <directory> (default: /var/lib/dpkg).
-.TP
-.I --divert <divert-to>
-<divert-to> is the name used by other packages' versions.
-.TP
-.I --help
-Output the version and the short usage instructions, and exit successfully.
-.TP
-.I --local
-Specifies that all packages' versions are diverted.
-.TP
-.I --package <package>
-<package> is the name of a package whose copy of <file> will not be diverted.
-.TP
-.I --quiet
-Quiet mode, i.e. no verbose output.
-.TP
-.I --rename
-Actually move the file aside (or back). dpkg-divert will abort operation
-in case the destination file already exists.
-.TP
-.I --test
-Test mode, i.e. don't actually perform any changes, just demonstrate.
-.TP
-.I --version
-Output program name and version and exit successfully.
-.SH NOTES
-When adding, default is --local and --divert <original>.distrib.
-When removing, --package or --local and --divert must match if specified.
-.br
-Directories can't be diverted with dpkg-divert.
-.SH FILES
-.TP
-.I /var/lib/dpkg/diversions
-File which contains the current list of diversions of the system. It is
-located in the dpkg administration directory, along with other files
-important to dpkg, such as `status' or `available'.
-.br
-Note: dpkg-divert preserves the old copy of this file, with extension
-"-old", before replacing it with the new one.
-.SH SEE ALSO
-.BR dpkg (8).
-.sp
-Please read the Debian Packaging Manual, section 11., "Diversions -
-overriding a package's version of a file" for more information.
-.SH AUTHOR
-Copyright (C) 1995 Ian Jackson.
-.sp
-This is free software; see the GNU General Public Licence
-version 2 or later for copying conditions.  There is NO warranty.
===================================================================
Index: scripts/dpkg-divert.pl
--- scripts/dpkg-divert.pl	Mon Jan 29 01:06:34 2001
+++ scripts/dpkg-divert.pl	Sun Nov 12 00:28:38 2000
@@ -1,272 +0,0 @@
-#!/usr/bin/perl --
-
-#use POSIX; &ENOENT;
-sub ENOENT { 2; }
-# Sorry about this, but the errno-part of POSIX.pm isn't in perl-*-base
-
-$version= '1.0.11'; # This line modified by Makefile
-$admindir= "/var/lib/dpkg"; # This line modified by Makefile
-
-sub showversion {
-    print("Debian GNU/Linux dpkg-divert $version.\n") || &quit("failed to write version: $!");
-}
-
-sub usage {
-    &showversion;
-    print STDERR <<EOF
-Copyright (C) 1995 Ian Jackson.
-Copyright (C) 2000 Wichert Akkerman.
-
-This is free software; see the GNU General Public Licence version 2 or later
-for copying conditions. There is NO warranty.
-
-Usage:
-
- dpkg-divert [options] [--add] <file>               - add a diversion
- dpkg-divert [options] --remove <file>              - remove the diversion
- dpkg-divert [options] --list [<glob-pattern>]      - show file diversions
- dpkg-divert [options] --truname <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).
-    --quiet                    quiet operation, minimal output
-    --test                     don't do anything, just demonstrate
-    --help                     print this help screen and exit
-    --version                  output version and exit
-    --admindir <directory>     set the directory with the diversions file
-
-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.
-EOF
-        || &quit("failed to write usage: $!");
-}
-
-$testmode= 0;
-$dorename= 0;
-$verbose= 1;
-$mode='';
-$|=1;
-
-sub checkmanymodes {
-    return unless $mode;
-    &badusage("two modes specified: $_ and --$mode");
-}
-
-while (@ARGV) {
-    $_= shift(@ARGV);
-    last if m/^--$/;
-    if (!m/^-/) {
-        unshift(@ARGV,$_); last;
-    } elsif (m/^--help$/) {
-        &usage; exit(0);
-    } elsif (m/^--version$/) {
-        &showversion; 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/^--truename$/) {
-        &checkmanymodes;
-        $mode= 'truename';
-    } elsif (m/^--divert$/) {
-        @ARGV || &badusage("--divert needs a divert-to argument");
-        $divertto= shift(@ARGV);
-        $divertto =~ m/\n/ && &badusage("divert-to may not contain newlines");
-    } elsif (m/^--package$/) {
-        @ARGV || &badusage("--package needs a package argument");
-        $package= shift(@ARGV);
-        $divertto =~ m/\n/ && &badusage("package may not contain newlines");
-    } elsif (m/^--admindir$/) {
-        @ARGV || &badusage("--admindir needs a directory argument");
-        $admindir= shift(@ARGV);
-    } else {
-        &badusage("unknown option \`$_'");
-    }
-}
-
-$mode='add' unless $mode;
-
-open(O,"$admindir/diversions") || &quit("cannot open diversions: $!");
-while(<O>) {
-    s/\n$//; push(@contest,$_);
-    $_=<O>; s/\n$// || &badfmt("missing altname");
-    push(@altname,$_);
-    $_=<O>; s/\n$// || &badfmt("missing package");
-    push(@package,$_);
-}
-close(O);
-
-if ($mode eq 'add') {
-    @ARGV == 1 || &badusage("--add needs a single argument");
-    $file= $ARGV[0];
-    $file =~ m#^/# || &badusage("filename \"$file\" is not absolute");
-    $file =~ m/\n/ && &badusage("file may not contain newlines");
-	-d $file && &badusage("Cannot divert directories");
-    $divertto= "$file.distrib" unless defined($divertto);
-    $divertto =~ m#^/# || &badusage("filename \"$divertto\" is not absolute");
-    $package= ':' unless defined($package);
-    for ($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) {
-                print "Leaving \`",&infon($i),"'\n" if $verbose > 0;
-                exit(0);
-            }
-            &quit("\`".&infoa."' clashes with \`".&infon($i)."'");
-        }
-    }
-    push(@contest,$file);
-    push(@altname,$divertto);
-    push(@package,$package);
-    print "Adding \`",&infon($#contest),"'\n" if $verbose > 0;
-    &checkrename($file,$divertto);
-    &save;
-    &dorename($file,$divertto);
-    exit(0);
-} elsif ($mode eq 'remove') {
-    @ARGV == 1 || &badusage("--remove needs a single argument");
-    $file= $ARGV[0];
-    for ($i=0; $i<=$#contest; $i++) {
-        next unless $file eq $contest[$i];
-        &quit("mismatch on divert-to\n  when removing \`".&infoa."'\n  found \`".
-              &infon($i)."'") if defined($divertto) && $altname[$i] ne $divertto;
-        &quit("mismatch on package\n  when removing \`".&infoa."'\n  found \`".
-              &infon($i)."'") if defined($package) && $package[$i] ne $package;
-        print "Removing \`",&infon($i),"'\n" if $verbose > 0;
-        $orgfile= $contest[$i];
-        $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);
-    }
-    print "No diversion \`",&infoa,"', none removed\n" if $verbose > 0;
-    exit(0);
-} elsif ($mode eq 'list') {
-    @ilist= @ARGV ? @ARGV : ('*');
-    while (defined($_=shift(@ilist))) {
-        s/\W/\\$&/g;
-        s/\\\?/./g;
-        s/\\\*/.*/g;
-        push(@list,"^$_\$");
-    }
-    $pat= join('|',@list);
-    for ($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("--truename needs a single argument");
-    $file= $ARGV[0];
-    for ($i=0; $i<=$#contest; $i++) {
-	next unless $file eq $contest[$i];
-	print $altname[$i], "\n";
-	exit(0);
-    }
-    print $file, "\n";
-    exit(0);
-} else {
-    &quit("internal error - bad mode \`$mode'");
-}
-
-sub infol {
-    return (($_[2] eq ':' ? "local " : length($_[2]) ? "" : "any ").
-            "diversion of $_[0]".
-            (length($_[1]) ? " to $_[1]" : "").
-            (length($_[2]) && $_[2] ne ':' ? " by $_[2]" : ""));
-}
-
-sub checkrename {
-    return unless $dorename;
-    ($rsrc,$rdest) = @_;
-    (@ssrc= lstat($rsrc)) || $! == &ENOENT ||
-        &quit("cannot stat old name \`$rsrc': $!");
-    (@sdest= lstat($rdest)) || $! == &ENOENT ||
-        &quit("cannot stat new name \`$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.
-    foreach $file ($rsrc,$rdest) {
-	open (TMP, ">> ${file}.dpkg-devert.tmp") || $! == ENOENT ||
-		&quit("error checking \`$file': $!");
-	close TMP;
-	if ($1 == ENOENT) {
-		$dorename = 0;
-	} else {
-		unlink ("${file}.dpkg-devert.tmp");
-	}
-    }
-    if (@ssrc && @sdest &&
-        !($ssrc[0] == $sdest[0] && $ssrc[1] == $sdest[1])) {
-        &quit("rename involves overwriting \`$rdest' with\n".
-              "  different file \`$rsrc', not allowed");
-    }
-}
-
-sub dorename {
-    return unless $dorename;
-    return if $testmode;
-    if (@ssrc) {
-        if (@sdest) {
-            unlink($rsrc) || &quit("rename: remove duplicate old link \`$rsrc': $!");
-        } else {
-            rename($rsrc,$rdest) || &quit("rename: rename \`$rsrc' to \`$rdest': $!");
-        }
-    }
-}            
-    
-sub save {
-    return if $testmode;
-    open(N,"> $admindir/diversions-new") || &quit("create diversions-new: $!");
-    chmod 0644, "$admindir/diversions-new";
-    for ($i=0; $i<=$#contest; $i++) {
-        print(N "$contest[$i]\n$altname[$i]\n$package[$i]\n")
-            || &quit("write diversions-new: $!");
-    }
-    close(N) || &quit("close diversions-new: $!");
-    unlink("$admindir/diversions-old") ||
-        $! == &ENOENT || &quit("remove old diversions-old: $!");
-    link("$admindir/diversions","$admindir/diversions-old") ||
-        $! == &ENOENT || &quit("create new diversions-old: $!");
-    rename("$admindir/diversions-new","$admindir/diversions")
-        || &quit("install new diversions: $!");
-}
-
-sub infoa { &infol($file,$divertto,$package); }
-sub infon { &infol($contest[$i],$altname[$i],$package[$i]); }
-
-sub quit { print STDERR "dpkg-divert: @_\n"; exit(2); }
-sub badusage { print STDERR "dpkg-divert: @_\n\n"; print("You need --help.\n"); exit(2); }
-sub badfmt { &quit("internal error: $admindir/diversions corrupt: $_[0]"); }
===================================================================
Index: utils/Makefile.in
--- utils/Makefile.in	2000/12/25 05:50:15	1.11
+++ utils/Makefile.in	2001/01/29 09:06:33
@@ -2,65 +2,62 @@
 VPATH		= @srcdir@
 srcdir		= @srcdir@
 top_srcdir	= @top_srcdir@
+USE_SSD		= @USE_START_STOP_DAEMON@
 
 include ../Makefile.conf
 
 CFLAGS			+= -I$(top_srcdir)/optlib
 
-SSD_SOURCES		= start-stop-daemon.c
-SSD_OBJECTS		= $(patsubst %.c, %.o, $(SSD_SOURCES))
-SSD_MANPAGES		= start-stop-daemon.8
-
-MD5_SOURCES		= md5sum.c
-MD5_OBJECTS		= $(patsubst %.c, %.o, $(MD5_SOURCES))
-MD5_MANPAGES		= md5sum.1
+OBJECTS			= start-stop-daemon.o md5sum.o dpkg-divert.o
+MANPAGES		= start-stop-daemon.8 md5sum.1 dpkg-divert.8
 
-GENFILES		= $(MD5_OBJECTS) md5sum
-ifeq (@USE_START_STOP_DAEMON@, true)
-GENFILES		+= $(SSD_OBJECTS) start-stop-daemon
+PROGRAMS		= md5sum dpkg-divert
+ifeq ($(USE_SSD), true)
+PROGRAMS		+= start-stop-daemon
 endif
 
 .PHONY: all
-ifeq (@USE_START_STOP_DAEMON@, true)
-all:: start-stop-daemon md5sum
-else
-all:: md5sum
-endif
+all:: $(PROGRAMS)
 
 .PHONY: install
 install:: all
 
 .PHONY: clean
 clean::
-	$(RM) $(GENFILES)
+	-$(RM) -f $(PROGRAMS) $(OBJECTS)
 
 .PHONY: distclean
 cvslean:: clean
-	$(RM) Makefile confdefs.h config.log
+	-$(RM) -f Makefile confdefs.h config.log
 
 .PHONY: install
 install:: install-program install-doc
 
 .PHONY: install-program
 install-program:
-ifeq (@USE_START_STOP_DAEMON@, true)
 	$(mkinstalldirs) $(DESTDIR)/$(sbindir)
-	$(INSTALL) start-stop-daemon $(DESTDIR)/$(sbindir)
-endif
 	$(mkinstalldirs) $(DESTDIR)/$(bindir)
+	$(INSTALL) dpkg-divert $(DESTDIR)/$(sbindir)
 	$(INSTALL) md5sum $(DESTDIR)/$(bindir)
+ifeq ($(USE_SSD), true)
+	$(INSTALL) start-stop-daemon $(DESTDIR)/$(sbindir)
+endif
 
 .PHONY: install-doc
 install-doc:
-ifeq (@USE_START_STOP_DAEMON@, true)
+	$(mkinstalldirs) $(DESTDIR)/$(man1dir)
 	$(mkinstalldirs) $(DESTDIR)/$(man8dir)
-	$(INSTALL_DATA) $(srcdir)/$(SSD_MANPAGES) $(DESTDIR)/$(man8dir)
+	$(INSTALL_DATA) $(srcdir)/md5sum.1 $(DESTDIR)/$(man1dir)
+	$(INSTALL_DATA) $(srcdir)/dpkg-divert.8 $(DESTDIR)/$(man8dir)
+ifeq (@USE_START_STOP_DAEMON@, true)
+	$(INSTALL_DATA) $(srcdir)/start-stop-daemon.8 $(DESTDIR)/$(man8dir)
 endif
-	$(mkinstalldirs) $(DESTDIR)/$(man1dir)
-	$(INSTALL_DATA) $(srcdir)/$(MD5_MANPAGES) $(DESTDIR)/$(man1dir)
 
-start-stop-daemon: $(SSD_OBJECTS) ../optlib/libopt.a
+start-stop-daemon: start-stop-daemon.o ../optlib/libopt.a
 	$(CC) $(LDFLAGS) -o $@ $^ $(SSD_LIBS)
 
-md5sum: $(MD5_OBJECTS) ../optlib/libopt.a ../lib/libdpkg.a
+md5sum: md5sum.o ../optlib/libopt.a ../lib/libdpkg.a
 	$(CC) $(LDFLAGS) -o $@ $^ $(NLS_LIBS)
+
+dpkg-divert: dpkg-divert.o ../optlib/libopt.a
+	$(CC) $(LDFLAGS) -o $@ $^
===================================================================
Index: utils/dpkg-divert.8
--- utils/dpkg-divert.8	Sun Nov 12 00:28:38 2000
+++ utils/dpkg-divert.8	Mon Jan 29 01:06:33 2001
@@ -0,0 +1,140 @@
+.TH DPKG-DIVERT 8 "January 2001" "Debian Project" "dpkg utilities"
+.SH NAME
+dpkg-divert - override a package's version of a file
+.SH SYNOPSIS
+.nh
+.B dpkg-divert
+[options] [--add]
+.I <file>
+.br
+.B dpkg-divert
+[options] --remove
+.I <file>
+.br
+.B dpkg-divert
+[options]
+--list
+.I <glob-pattern> ...
+.br
+.B dpkg-divert
+[options] --truename
+.I <file>
+.br
+.SH DESCRIPTION
+File diversions are a way of forcing dpkg not to install a file into
+its usual location.  System administrators can use it to override
+files supplied by packages with their own versions.  Package
+maintainers can also use it when there are two packages that supply
+the same file; however, this is better handled by the alternatives
+mechanism (see
+.BR update-alternatives (8)).
+.PP
+There can be only one diversion of any given file.  Directories cannot
+be diverted.
+.PP
+.B dpkg-divert
+is the utility used to set up and update the list of diversions. It
+has four basic modes:
+.TP
+.BR -a / --add
+Add a diversion of a file.  This is the default mode.
+.TP
+.BR -r / --remove
+Remove a diversion, restoring the file to its original location.  The
+.BR --package / --local
+and
+.B --divert
+options must match exactly what was given when the diversion was added.
+.TP
+.BR -l / --list
+List diversions which match any of the glob patterns.  A diversion
+matches if any of its old name, new name, or initiating package
+matches.  To list local diversions, say `\f(CW-l :\fP'.
+.TP
+.BR -T / --truename
+Given the name of a file, print the name it was diverted to.
+.PP
+Note that by default
+.B dpkg-divert
+does not actually rename the file it is asked to divert.  It merely
+records the diversion for later reference by
+.BR dpkg .
+This is the appropriate behavior when
+.B dpkg-divert
+is used from package preinstall scripts, but usually not what a system
+administrator wants.  See below.
+.SH OPTIONS
+.IP "\fB-A\fP \fIdirectory\fP, \fB--admindir\fP \fIdirectory\fP"
+Look for dpkg metadata in
+.I directory
+(default: /var/lib/dpkg).
+.IP "\fB-d\fP \fIdivert-to\fP, \fB--divert\fP \fIdivert-to\fP"
+Specify the name to divert the file to.  The default is to append
+.I .distrib
+to the original name.
+.IP "\fB-?\fP, \fB--help\fP"
+Output the version and the short usage instructions, and exit successfully.
+.IP "\fB-l\fP, \fB--local\fP"
+This diversion was requested by the system administrator.  This is the
+default.
+.IP "\fB-p\fP \fIpackage\fP, \fB--package\fP \fIpackage\fP"
+This diversion was requested by
+.IR package .
+If it supplies the diverted file, its copy will not be diverted.
+.IP "\fB-q\fP, \fB--quiet\fP"
+Quiet mode: no output is generated unless there are errors.  This
+option is ignored by
+.B --list
+and
+.BR --truename ,
+or in
+.B --test
+mode.
+.IP "\fB-R\fP, \fB--rename\fP"
+Actually move the file aside (or back). dpkg-divert will abort operation
+in case the destination file already exists.
+.IP "\fB-t\fP, \fB--test\fP"
+Test mode.  Don't make any changes, just print what would be done.
+.IP "\fB-v\fP, \fB--version\fP"
+Print the program name and version, and exit successfully.
+.SH EXAMPLE
+To make \f(CW/bin/sh\fP be \fBash\fP:
+.PP
+.na
+.nf
+.ft CW
+# apt-get install ash
+# cd /bin
+# dpkg-divert -R sh
+# ln -s ash sh
+# ls -l ash bash sh sh.distrib
+-rwxr-xr-x    1 root  root     89020 Jan 12 20:29 ash
+-rwxr-xr-x    1 root  root    404340 Nov 20 14:38 bash
+lrwxrwxrwx    1 root  root         3 Jan  8  2000 sh -> ash
+lrwxrwxrwx    1 root  root         4 Nov 22 10:33 sh.distrib -> bash
+.ft P
+.ad
+.fi
+.SH FILES
+.TP
+.I /var/lib/dpkg/diversions
+File which contains the current list of diversions. It is located in
+the dpkg administration directory, along with other files important to
+dpkg, such as `status' or `available'. Whenever
+.B dpkg-divert 
+changes this file, it saves the old version as
+.I diversions-old
+in the same directory.
+.SH SEE ALSO
+.BR dpkg (8), update-alternatives (8).
+.sp
+Please read the Debian Packaging Manual, section 11,
+"Diversions\(emoverriding a package's version of a file" for more
+information.
+.SH AUTHOR
+Copyright (C) 1995 Ian Jackson.
+Copyright (C) 2000 Wichert Akkerman.
+Copyright (C) 2001 Zack Weinberg.
+.sp
+This is free software; see the GNU General Public Licence
+version 2 or later for copying conditions.  There is NO warranty.
===================================================================
Index: utils/dpkg-divert.c
--- utils/dpkg-divert.c	Sun Nov 12 00:28:38 2000
+++ utils/dpkg-divert.c	Mon Jan 29 01:06:33 2001
@@ -0,0 +1,610 @@
+/* dpkg-divert: push files aside.
+   Based on dpkg-divert.pl from previous versions of dpkg.  */
+
+/* Rename some things out of the way to avoid clashes.  */
+#define badusage dpkg_badusage
+#define getline libc_getline
+
+#include <config.h>
+#include <dpkg.h>
+
+#include <stddef.h>
+#include <sys/param.h>
+#include <limits.h>
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <obstack.h>
+
+#undef badusage
+#undef getline
+
+#define obstack_chunk_alloc xmalloc
+#define obstack_chunk_free free
+
+/* Globals and data structure */
+
+struct diversion
+{
+	struct diversion *next;
+	const char *oldname;
+	const char *newname;
+	const char *package;
+};
+
+static struct diversion *diversions;
+static struct obstack div_ob;
+
+static int verbose = 1;
+static int dorename;
+static int testing;
+
+/* prototypes */
+static char *getline		(FILE *);
+static void *xmalloc		(size_t);
+static void read_diversions	(void);
+static void write_diversions	(void);
+static void commit_diversions	(void);
+
+static void add_entry		(struct diversion *);
+static void remove_entry	(struct diversion *);
+static void canonicalize	(struct diversion *);
+static void rename_noclobber	(const char *, const char *);
+static void check_file		(const char *);
+
+static void do_list		(char **, int);
+static void do_truname		(struct diversion *);
+static void do_move		(const char *, const char *);
+
+static void report		(const char *, struct diversion *);
+static void clash		(const char *, struct diversion *,
+				 struct diversion *) NONRETURNING;
+
+static void show_version	(void);
+static void show_usage		(void);
+
+static void badusage		(const char *) NONRETURNING;
+static void quit		(const char *) NONRETURNING;
+static void errquit		(const char *) NONRETURNING;
+static void badfmt		(const char *) NONRETURNING;
+
+/* centralize out-of-mem checks in one place.  */
+static void *
+xmalloc(size_t s)
+{
+	void *r = malloc(s);
+	if (r == 0) quit("out of memory");
+	return r;
+}
+
+static void
+cleanup(void)
+{
+	obstack_free(&div_ob, 0);
+}
+
+/* Option table.  */
+static struct option opts[] =
+{
+	/* Actions.  */
+	{ "add",	no_argument, 	   0, 'a' },
+	{ "list",	no_argument,	   0, 'l' },
+	{ "remove",	no_argument,	   0, 'r' },
+	{ "truename",	no_argument,	   0, 'T' },
+
+	/* Options.  */
+	{ "admindir",   required_argument, 0, 'A' },
+	{ "divert",	required_argument, 0, 'd' },
+	{ "help",	no_argument,	   0, '?' },
+	{ "local",	no_argument,	   0, 'L' },
+	{ "package",	required_argument, 0, 'p' },
+	{ "quiet",	no_argument,	   0, 'q' },
+	{ "rename",	no_argument,	   0, 'R' },
+	{ "test",	no_argument,	   0, 't' },
+	{ "version",	no_argument,	   0, 'v' },
+};
+
+int
+main(int ac, char **av)
+{
+	int dummy, optval;
+	enum { ADD, REMOVE, LIST, TRUNAME } action = ADD;
+	struct diversion op = { 0, 0, 0 };
+	int seenaction = 0;
+	const char *admindir = 0;
+
+	obstack_init(&div_ob);
+	atexit(cleanup);
+
+#define checktwomodes() if(seenaction++) badusage("two modes specified");
+
+	/* Decode options.  */
+	while ((optval = getopt_long (ac, av, "alrTA:d:Lp:qRtv?",
+				      opts, &dummy)) != -1) {
+		switch (optval) {
+		case 'a':	checktwomodes(); action = ADD;     break;
+		case 'l':	checktwomodes(); action = LIST;    break;
+		case 'r':	checktwomodes(); action = REMOVE;  break;
+		case 'T':	checktwomodes(); action = TRUNAME; break;
+
+		case 'A':
+			if (admindir) badusage("two admindirs specified");
+			admindir = optarg;
+			break;
+
+		case 'd':
+			if (op.newname) badusage("two new names specified");
+			op.newname = optarg;
+			break;
+
+		case 'L':
+			if (op.package) badusage("--local cannot be used "
+						 "with --package");
+			break;  /* don't actually have to do anything */
+		case 'p':
+			if (op.package) badusage("two packages specified");
+			op.package = optarg;
+			break;
+
+		case 'q': verbose = 0; break;
+		case 'R': dorename = 1; break;
+		case 't': testing = 1; break;
+
+		case 'v': show_version(); exit(0);
+		case '?': show_usage(); exit(0);
+
+		default: badusage("unknown option");
+ 		}
+	}
+	/* -t beats -q.  */
+	if (testing) verbose = 1;
+	if (!admindir) admindir = ADMINDIR;
+
+	if (action == LIST) {
+		if (op.package) badusage("can't use --package with --list");
+		if (op.newname) badusage("can't use --divert with --list");
+	} else {
+		if (optind == ac) badusage("missing file argument");
+		if (optind < ac - 1) badusage("too many files");
+		op.oldname = av[optind];
+		canonicalize(&op);
+	}
+
+	if (chdir(admindir)) errquit(admindir);
+	read_diversions();
+
+	switch (action) {
+	case LIST:
+		do_list(&av[optind], ac - optind);
+		break;
+
+	case TRUNAME:
+		do_truname(&op);
+		break;
+
+	case ADD:
+		add_entry(&op);
+		do_move(op.oldname, op.newname);
+		break;
+
+	case REMOVE:
+		remove_entry(&op);
+		do_move(op.newname, op.oldname);
+		break;
+	}
+	return 0;
+}
+#undef checktwomodes
+
+static void
+do_list(char **globs, int count)
+{
+	int i;
+	struct diversion *d;
+
+	for(d = diversions; d; d = d->next)
+		if (count == 0) report("", d);
+		else for (i = 0; i < count; i++)
+			if (!fnmatch(globs[i], d->oldname, FNM_PATHNAME)
+			    || !fnmatch(globs[i], d->newname, FNM_PATHNAME)
+			    || !fnmatch(globs[i], d->package, 0))
+				report("", d);
+}
+
+static void
+do_truname(struct diversion *op)
+{
+	struct diversion *d;
+	for (d = diversions; d; d = d->next)
+		if (!strcmp(d->oldname, op->oldname)) {
+			printf("%s\n", d->newname);
+			break;
+		}
+}
+
+static void
+do_move(const char *from, const char *to)
+{
+	check_file(from);
+	if (!testing) {
+		write_diversions();
+		if (dorename) rename_noclobber(from, to);
+		commit_diversions();
+	}
+}
+
+/* Add a diversion to the table.  */
+static void
+add_entry(struct diversion *new)
+{
+	struct diversion *d;
+
+	for (d = diversions; d; d = d->next)
+		if (!strcmp(d->oldname, new->oldname)) {
+			/* It is not an error if we are asked to add
+			   the exact same diversion as one that
+			   already exists.  */
+			if (!strcmp(d->newname, new->newname)
+			    && !strcmp(d->package, new->package)) {
+				if (verbose) report("Leaving ", d);
+				exit(0);
+			}
+			clash("clashes", d, new);
+			/* NOTREACHED */
+		}
+
+	if (verbose) report("Adding ", new);
+	d = obstack_copy(&div_ob, new, sizeof(struct diversion));
+	d->next = diversions;
+	diversions = d;
+}
+
+static void
+remove_entry(struct diversion *rem)
+{
+	struct diversion *d, *prev;
+
+	for (d = diversions, prev = 0; d; prev = d, d = d->next) {
+		if (!strcmp(d->oldname, rem->oldname)) {
+			/* The diversion we're asked to remove must
+			   match in all particulars.  */
+			if (!strcmp(d->newname, rem->newname)
+			    && !strcmp(d->package, rem->package))
+				goto found;
+
+			clash("does not match", d, rem);
+			/* NOTREACHED */
+		}
+	}
+	
+	if (verbose) report("No diversion ", rem);
+	exit(0);
+
+found:
+	if (verbose) report("Removing ", rem);
+
+	if (prev) prev->next = d->next;
+	else	  diversions = d->next;
+}
+
+/* Checks for the file being diverted.  Diverting directories is not
+   supported.  Diverting nonexistent files is ok (we just turn off
+   dorename).  */
+static void
+check_file (const char *path)
+{
+	struct stat st;
+
+	if (lstat(path, &st)) {
+		if (errno == ENOENT) dorename = 0;
+		else errquit(path);
+	} else if (S_ISDIR(st.st_mode)) {
+		errno = EISDIR;
+		errquit(path);
+	}
+}
+	
+/* Rename SRC to DEST, with paranoid error checking, and do not allow
+   DEST to be clobbered if it already exists.
+
+   There is an unavoidable race in this operation.  rename() silently
+   overwrites an existing file, but it could have been created between
+   the lstat and the rename.  There's also unavoidable races between
+   this operation and the update of the diversions file.  The best we
+   can do there is order things so the things which are likely to fail
+   happen first.
+
+   dpkg-divert.pl used to do a huge number of prep checks, all of which
+   were unnecessary: if any of them failed, the rename would've failed.
+   (The idea may have been to leave the diversions table alone in that
+   case.  We deal with that by not moving diversions-new over until we
+   know we've succeeded.)  */
+static void
+rename_noclobber(const char *src, const char *dst)
+{
+	struct stat dummy;
+	int serrno;
+
+	if (lstat(dst, &dummy) != -1) errno = EEXIST;
+	if (errno != ENOENT) goto err;
+	if (rename(src, dst)) goto err;
+	return;
+
+ err:
+	serrno = errno;
+	unlink("diversions-new");
+	errno = serrno;
+	errquit("renaming diverted file");
+}
+
+/* Reading and writing the diversions file.
+   This file is a list of 3-line groups:
+	old absolute pathname
+	new absolute pathname
+	package responsible (: for local).  */
+
+static char *
+getline(FILE *f)
+{
+	int c;
+	size_t len = 0;
+
+	clearerr(f);
+	for (;;) {
+		c = getc(f);
+		if (c == '\n' || c == EOF) break;
+		if (c == '\0') badfmt("ASCII NUL in file");
+		obstack_1grow(&div_ob, c);
+		len++;
+	}
+	if (ferror(f)) errquit("reading diversions");
+	if (len == 0 && c == EOF) return 0;
+
+	obstack_1grow(&div_ob, '\0');
+	return obstack_finish(&div_ob);
+}
+
+static void
+read_diversions(void)
+{
+	FILE *divf;
+	struct diversion d;
+	struct diversion *divs = 0;
+
+	divf = fopen("diversions", "r");
+	if (divf == 0) errquit("opening diversions");
+
+	for (;;) {
+		d.oldname = getline(divf);
+		d.newname = getline(divf);
+		d.package = getline(divf);
+
+		if(d.oldname == 0) break;
+		if(d.oldname[0] == '\0')
+			badfmt("missing oldname");
+		if(d.newname == 0 || d.newname[0] == '\0')
+			badfmt("missing newname");
+		if(d.package == 0 || d.package[0] == '\0')
+			badfmt("missing package");
+
+		d.next = divs;
+		divs = obstack_copy(&div_ob, &d, sizeof(struct diversion));
+	}
+	fclose(divf);
+
+	diversions = divs;
+}
+
+static void
+write_diversions(void)
+{
+	FILE *divf;
+	int serrno, fd;
+	struct diversion *d;
+
+	fd = open("diversions-new", O_WRONLY|O_CREAT|O_EXCL, 0644);
+	if (fd == -1) errquit("creating new diversions");
+
+	divf = fdopen(fd, "w");
+	for (d = diversions; d; d = d->next) {
+		fputs(d->oldname, divf); putc('\n', divf);
+		fputs(d->newname, divf); putc('\n', divf);
+		fputs(d->package, divf); putc('\n', divf);
+	}
+	/* Make sure it hits the disk.  */
+	if (!ferror(divf) && !fflush(divf) && !fsync(fd) && !fclose(divf))
+		return;
+
+	serrno = errno;
+	unlink("diversions-new");	/* ignore errors */
+	errno = serrno;
+	errquit("writing new diversions");
+}
+
+/* This takes place after we have successfully renamed the file
+   (if we were asked to rename the file).  Note that if an
+   error occurs at this point, the file has still been renamed;
+   therefore we leave diversions-new around for the sysadmin to
+   attempt to recover.  */
+static void
+commit_diversions(void)
+{
+	if (unlink("diversions-old") && errno != ENOENT) goto err;
+	if (link("diversions", "diversions-old")) goto err;
+	if (rename("diversions-new", "diversions")) goto err;
+	return;
+
+ err:
+	if (!dorename) errquit("committing new diversions list");
+	else errquit("committing new diversions list - FILE WAS MOVED");
+}
+	
+/* Put a diversion structure into canonical form.  This handles
+   path canonicalization, default new path (foo.distrib), and default
+   local diversion (d->package = ":").  */
+static void
+canonicalize(struct diversion *d)
+{
+	size_t path_max;
+	char *cwd;
+
+	/* Check for newlines in oldname and newname.  The diversions
+	   file cannot handle paths that contain newlines.  */
+	if (strchr (d->oldname, '\n'))
+		quit("diverted file name may not contain newlines");
+	if (d->newname && strchr(d->newname, '\n'))
+		quit("diverted-to file name may not contain newlines");
+
+#ifdef PATH_MAX
+	path_max = PATH_MAX;
+#else
+	path_max = pathconf (".", _PC_PATH_MAX);	/* best guess */
+	if (path_max <= 0)
+		path_max = getpagesize();
+#endif
+	cwd = xmalloc (path_max);
+	if (getcwd(cwd, path_max) == NULL) errquit("getcwd");
+
+	/* If we've been given non-absolute paths, prepend the
+	   current working directory.  Otherwise just copy the
+	   paths into malloced memory.  */
+	if (d->oldname[0] != '/') {
+		obstack_grow(&div_ob, cwd, strlen(cwd));
+		obstack_1grow(&div_ob, '/');
+	}
+	obstack_grow0(&div_ob, d->oldname, strlen(d->oldname));
+	d->oldname = obstack_finish(&div_ob);
+
+	if (d->newname == 0) {
+		obstack_grow(&div_ob, d->oldname, strlen(d->oldname));
+		obstack_grow0(&div_ob, ".distrib", sizeof(".distrib") - 1);
+	} else {
+		if (d->newname[0] != '/') {
+			obstack_grow(&div_ob, cwd, strlen(cwd));
+			obstack_1grow(&div_ob, '/');
+		}
+		obstack_grow0(&div_ob, d->newname, strlen(d->oldname));
+	}
+	d->newname = obstack_finish(&div_ob);
+
+	if (d->package)
+		d->package = obstack_copy0(&div_ob, d->package,
+					   strlen(d->package));
+	else
+		d->package = obstack_copy0(&div_ob, ":", 1);
+
+	free(cwd);
+}
+
+/* Version, usage, error messages.  */
+
+static void
+show_version(void)
+{
+	fputs("Debian GNU/Linux dpkg-divert " VERSION ".\n", stderr);
+}
+
+static void
+show_usage(void)
+{
+	show_version();
+	fputs(
+"Copyright (C) 1995 Ian Jackson.\n"
+"Copyright (C) 2000 Wichert Akkerman.\n"
+"Copyright (C) 2001 Zack Weinberg.\n"
+"\n"
+"This is free software; see the GNU General Public Licence version 2 or later\n"
+"for copying conditions. There is NO warranty.\n"
+"\n"
+"Usage:\n"
+"\n"
+" dpkg-divert [options] [-a/--add] <file>          - add a diversion\n"
+" dpkg-divert [options] -r/--remove <file>         - remove the diversion\n"
+" dpkg-divert [options] -l/--list [<glob-pattern>] - show file diversions\n"
+" dpkg-divert [options] -T/--truname <file>        - return the diverted file\n"
+"\n"
+"Options:\n"
+"    -p, --package <package>    name of the package diverting the file.\n"
+"    -l, --local                diversion is a local change [default].\n"
+"    -d, --divert <divert-to>   the new name for the diverted file.\n"
+"    -R, --rename               actually move the file aside (or back).\n"
+"    -q, --quiet                quiet operation, minimal output\n"
+"    -t, --test                 don't do anything, just demonstrate\n"
+"    -?, --help                 print this help screen and exit\n"
+"    -v, --version              output version and exit\n"
+"    -A, --admindir <dir>       directory containing diversions file\n"
+"\n"
+"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"
+, stderr);
+}
+
+static void
+report(const char *verb, struct diversion *noun)
+{
+	if (!strcmp(noun->package, ":"))
+		printf("%slocal diversion of %s to %s\n",
+		       verb, noun->oldname, noun->newname);
+	else
+		printf("%sdiversion of %s to %s by %s\n",
+		       verb, noun->oldname, noun->newname, noun->package);
+}
+
+static void
+clash(const char *what, struct diversion *d1, struct diversion *d2)
+{
+	if (strcmp(d1->package, ":"))
+		printf("Local diversion of %s to %s ",
+		       d1->oldname, d1->newname);
+	else
+		printf("Diversion of %s to %s by %s ",
+		       d1->oldname, d1->newname, d1->package);
+
+	fputs(what, stdout);
+
+	if (strcmp(d2->package, ":"))
+		printf(" local diversion of %s to %s.\n",
+		       d2->oldname, d2->newname);
+	else
+		printf(" diversion of %s to %s by %s.\n",
+		       d2->oldname, d2->newname, d2->package);
+
+	if (verbose) fputs("No action taken.\n", stdout);
+	exit(2);
+}
+
+static void
+badusage(const char *msg)
+{
+	fprintf(stderr, "dpkg-divert: %s\nYou need --help.\n", msg);
+	exit(2);
+}
+
+static void
+quit(const char *msg)
+{
+	fprintf(stderr, "dpkg-divert: %s\n", msg);
+	exit(2);
+}
+
+static void
+errquit(const char *msg)
+{
+	fprintf(stderr, "dpkg-divert: %s: %s\n", msg, strerror(errno));
+	exit(2);
+}
+
+static void
+badfmt(const char *msg)
+{
+	fprintf(stderr,
+		"dpkg-divert: internal error: diversions corrupt: %s\n", msg);
+	exit(2);
+}



Reply to: