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: