diff options
Diffstat (limited to '')
-rw-r--r-- | src/t/dpkg_divert.t | 644 | ||||
-rw-r--r-- | src/trigcmd.c | 256 | ||||
-rw-r--r-- | src/trigproc.c | 575 |
3 files changed, 1475 insertions, 0 deletions
diff --git a/src/t/dpkg_divert.t b/src/t/dpkg_divert.t new file mode 100644 index 0000000..cf118e2 --- /dev/null +++ b/src/t/dpkg_divert.t @@ -0,0 +1,644 @@ +#!/usr/bin/perl +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +use strict; +use warnings; + +use Test::More; + +use File::Spec; + +use Dpkg::File; +use Dpkg::IPC; + +# Cleanup environment from variables that pollute the test runs. +delete $ENV{DPKG_MAINTSCRIPT_PACKAGE}; +delete $ENV{DPKG_MAINTSCRIPT_ARCH}; + +my $srcdir = $ENV{srcdir} || '.'; +my $builddir = $ENV{builddir} || '.'; +my $tmpdir = 't.tmp/dpkg_divert'; +my $admindir = File::Spec->rel2abs("$tmpdir/admindir"); +my $testdir = File::Spec->rel2abs("$tmpdir/testdir"); + +my @dd = ("$builddir/../src/dpkg-divert"); + +if (! -x "@dd") { + plan skip_all => 'dpkg-divert not available'; + exit(0); +} + +plan tests => 257; + +sub cleanup { + # On FreeBSD «rm -rf» cannot traverse a directory with mode 000. + system("test -d $testdir/nadir && rmdir $testdir/nadir"); + system("rm -rf $tmpdir && mkdir -p $testdir"); + system("mkdir -p $admindir/updates"); + system("rm -f $admindir/status && touch $admindir/status"); + system("rm -rf $admindir/info && mkdir -p $admindir/info"); +} + +sub install_diversions { + my ($txt) = @_; + open(my $db_fh, '>', "$admindir/diversions") + or die "cannot create $admindir/diversions"; + print { $db_fh } $txt; + close($db_fh); +} + +sub install_filelist { + my ($pkg, $arch, @files) = @_; + open(my $fileslist_fh, '>', "$admindir/info/$pkg.list") + or die "cannot create $admindir/info/$pkg.list"; + for my $file (@files) { + print { $fileslist_fh } "$file\n"; + } + close($fileslist_fh); + # Only installed packages have their files list considered. + open(my $status_fh, '>>', "$admindir/status") + or die "cannot append to $admindir/status"; + print { $status_fh } <<"EOF"; +Package: $pkg +Status: install ok installed +Version: 0 +Architecture: $arch +Maintainer: dummy +Description: dummy + +EOF + close($status_fh); +} + +sub call { + my ($prog, $args, %opts) = @_; + + my ($output, $error); + spawn(exec => [@$prog, @$args], wait_child => 1, nocheck => 1, + to_pipe => \$output, error_to_pipe => \$error, %opts); + + if ($opts{expect_failure}) { + ok($? != 0, "@$args should fail"); + } else { + ok($? == 0, "@$args should not fail"); + } + + if (defined $opts{expect_stdout}) { + my (@output) = <$output>; + my (@expect) = split(/^/, $opts{expect_stdout}); + if (defined $opts{expect_sorted_stdout}) { + @output = sort @output; + @expect = sort @expect; + } + is(join('', @output), join('', @expect), "@$args stdout"); + } + if (defined $opts{expect_stdout_like}) { + like(file_slurp($output), $opts{expect_stdout_like}, "@$args stdout"); + } + if (defined $opts{expect_stderr}) { + is(file_slurp($error), $opts{expect_stderr}, "@$args stderr"); + } + if (defined $opts{expect_stderr_like}) { + like(file_slurp($error), $opts{expect_stderr_like}, "@$args stderr"); + } + + close($output); + close($error); +} + +sub call_divert { + my ($params, %opts) = @_; + call([@dd, '--admindir', $admindir], $params, %opts); +} + +sub call_divert_sort { + my ($params, %opts) = @_; + $opts{expect_sorted_stdout} = 1; + call_divert($params, %opts); +} + +sub diversions_pack { + my (@data) = @_; + my @data_packed; + + ## no critic (ControlStructures::ProhibitCStyleForLoops) + for (my ($i) = 0; $i < $#data; $i += 3) { + push @data_packed, [ @data[$i .. $i + 2] ]; + } + ## use critic + my @list = sort { $a->[0] cmp $b->[0] } @data_packed; + + return @list; +} + +sub diversions_eq { + my (@expected) = split /^/, shift; + open(my $db_fh, '<', "$admindir/diversions") + or die "cannot open $admindir/diversions"; + my (@contents) = <$db_fh>; + close($db_fh); + + my (@expected_pack) = diversions_pack(@expected); + my (@contents_pack) = diversions_pack(@contents); + + is_deeply(\@contents_pack, \@expected_pack, 'diversions contents'); +} + +### Tests + +cleanup(); + +note('Command line parsing testing'); + +my $usagere = qr/.*Usage.*dpkg-divert.*Commands.*Options.*/s; + +sub call_divert_badusage { + my ($args, $err) = @_; + call_divert($args, expect_failure => 1, expect_stderr_like => $err); +} + +call_divert(['--help'], expect_stdout_like => $usagere, + expect_stderr => ''); +call_divert(['--version'], expect_stdout_like => qr/.*dpkg-divert.*free software.*/s, + expect_stderr => ''); + +call_divert_badusage(['--jachsmitbju'], qr/unknown option/); +call_divert_badusage(['--add', '--remove'], qr/(conflicting|two).*remove.*add.*/s); +call_divert_badusage(['--divert'], qr/(takes a value|needs.*argument)/); +call_divert_badusage(['--divert', 'foo'], qr/absolute/); +call_divert_badusage(['--divert', "/foo\nbar"], qr/newline/); +call_divert_badusage(['--package'], qr/(takes a value|needs.*argument)/); +call_divert_badusage(['--package', "foo\nbar"], qr/newline/); + +install_diversions(''); + +call_divert_badusage(['--add',], qr/needs a single argument/); +call_divert_badusage(['--add', 'foo'], qr/absolute/); +call_divert_badusage(['--add', "/foo\nbar"], qr/newline/); +call_divert_badusage(['--add', "$testdir"], qr/director(y|ies)/); +call_divert_badusage(['--add', '--divert', 'bar', '/foo/bar'], qr/absolute/); +call_divert_badusage(['--remove'], qr/needs a single argument/); +call_divert_badusage(['--remove', 'foo'], qr/absolute/); +call_divert_badusage(['--remove', "/foo\nbar"], qr/newline/); +call_divert_badusage(['--listpackage'], qr/needs a single argument/); +call_divert_badusage(['--listpackage', 'foo'], qr/absolute/); +call_divert_badusage(['--listpackage', "/foo\nbar"], qr/newline/); +call_divert_badusage(['--truename'], qr/needs a single argument/); +call_divert_badusage(['--truename', 'foo'], qr/absolute/); +call_divert_badusage(['--truename', "/foo\nbar"], qr/newline/); +call([@dd, '--admindir'], [], + expect_failure => 1, expect_stderr_like => qr/(takes a value|needs.*argument)/); + +cleanup(); + +note('Querying information from diverts db (empty one)'); + +install_diversions(''); + +call_divert_sort(['--list'], expect_stdout => '', expect_stderr => ''); +call_divert_sort(['--list', '*'], expect_stdout => '', expect_stderr => ''); +call_divert_sort(['--list', 'baz'], expect_stdout => '', expect_stderr => ''); + +cleanup(); + +note('Querying information from diverts db (1)'); + +install_diversions(<<'EOF'); +/bin/sh +/bin/sh.distrib +dash +/usr/share/man/man1/sh.1.gz +/usr/share/man/man1/sh.distrib.1.gz +dash +/usr/bin/nm +/usr/bin/nm.single +binutils-multiarch +EOF + +my $di_dash = "diversion of /bin/sh to /bin/sh.distrib by dash\n"; +my $di_dashman = "diversion of /usr/share/man/man1/sh.1.gz to /usr/share/man/man1/sh.distrib.1.gz by dash\n"; +my $di_nm = "diversion of /usr/bin/nm to /usr/bin/nm.single by binutils-multiarch\n"; + +my $all_di = $di_dash . $di_dashman . $di_nm; + +call_divert_sort(['--list'], expect_stdout => $all_di, expect_stderr => ''); +call_divert_sort(['--list', '*'], expect_stdout => $all_di, expect_stderr => ''); +call_divert_sort(['--list', ''], expect_stdout => '', expect_stderr => ''); + +call_divert_sort(['--list', '???????'], expect_stdout => $di_dash, expect_stderr => ''); +call_divert_sort(['--list', '*/sh'], expect_stdout => $di_dash, expect_stderr => ''); +call_divert_sort(['--list', '/bin/*'], expect_stdout => $di_dash, expect_stderr => ''); +call_divert_sort(['--list', 'binutils-multiarch'], expect_stdout => $di_nm, expect_stderr => ''); +call_divert_sort(['--list', '/bin/sh'], expect_stdout => $di_dash, expect_stderr => ''); +call_divert_sort(['--list', '--', '/bin/sh'], expect_stdout => $di_dash, expect_stderr => ''); +call_divert_sort(['--list', '/usr/bin/nm.single'], expect_stdout => $di_nm, expect_stderr => ''); +call_divert_sort(['--list', '/bin/sh', '/usr/share/man/man1/sh.1.gz'], expect_stdout => $di_dash . $di_dashman, + expect_stderr => ''); + +cleanup(); + +note('Querying information from diverts db (2)'); + +install_diversions(<<'EOF'); +/bin/sh +/bin/sh.distrib +dash +/bin/true +/bin/true.coreutils +: +EOF + +call_divert(['--listpackage', 'foo', 'bar'], expect_failure => 1); +call_divert(['--listpackage', '/bin/sh'], expect_stdout => "dash\n", expect_stderr => ''); +call_divert(['--listpackage', '/bin/true'], expect_stdout => "LOCAL\n", expect_stderr => ''); +call_divert(['--listpackage', '/bin/false'], expect_stdout => '', expect_stderr => ''); + +call_divert(['--truename', '/bin/sh'], expect_stdout => "/bin/sh.distrib\n", expect_stderr => ''); +call_divert(['--truename', '/bin/sh.distrib'], expect_stdout => "/bin/sh.distrib\n", expect_stderr => ''); +call_divert(['--truename', '/bin/something'], expect_stdout => "/bin/something\n", expect_stderr => ''); + +cleanup(); + +note('Adding diversion'); + +my $diversions_added_foo_local = <<"EOF"; +$testdir/foo +$testdir/foo.distrib +: +EOF + +install_diversions(''); + +system("touch $testdir/foo"); +call_divert(['--rename', '--add', "$testdir/foo"], + expect_stdout_like => qr{ + Adding.*local.*diversion.* + \Q$testdir\E/foo.* + \Q$testdir\E/foo.distrib + }x, + expect_stderr => ''); +ok(-e "$testdir/foo.distrib", 'foo diverted'); +ok(!-e "$testdir/foo", 'foo diverted'); +diversions_eq($diversions_added_foo_local); + +cleanup(); + +note('Adding diversion (2)'); + +install_diversions(''); + +system("touch $testdir/foo"); +call_divert(['--no-rename', '--add', "$testdir/foo"], + expect_stdout_like => qr{ + Adding.*local.*diversion.* + \Q$testdir\E/foo.* + \Q$testdir\E/foo.distrib + }x, + expect_stderr => ''); +ok(!-e "$testdir/foo.distrib", 'foo diverted'); +ok(-e "$testdir/foo", 'foo diverted'); +diversions_eq($diversions_added_foo_local); + +cleanup(); + +note('Adding diversion (3)'); + +install_diversions(''); + +system("touch $testdir/foo"); +call_divert(['--quiet', '--rename', '--add', "$testdir/foo"], + expect_stdout => '', expect_stderr => ''); +ok(-e "$testdir/foo.distrib", 'foo diverted'); +ok(!-e "$testdir/foo", 'foo diverted'); +diversions_eq($diversions_added_foo_local); + +cleanup(); + +note('Adding diversion (4)'); + +install_diversions(''); +system("touch $testdir/foo"); +call_divert(['--quiet', '--rename', '--test', "$testdir/foo"], + expect_stdout => '', expect_stderr => ''); +ok(-e "$testdir/foo", 'foo not diverted'); +ok(!-e "$testdir/foo.distrib", 'foo diverted'); +diversions_eq(''); + +cleanup(); + +note('Adding diversion (5)'); + +install_diversions(''); +call_divert(['--quiet', '--rename', "$testdir/foo"], + expect_stdout => '', expect_stderr => ''); +ok(!-e "$testdir/foo", 'foo does not exist'); +ok(!-e "$testdir/foo.distrib", 'foo was not created out of thin air'); + +cleanup(); + +note('Adding diversion (6)'); + +install_diversions(''); +system("touch $testdir/foo"); +call_divert(['--quiet', '--local', '--rename', "$testdir/foo"], + expect_stdout => '', expect_stderr => ''); + +ok(-e "$testdir/foo.distrib", 'foo diverted'); +ok(!-e "$testdir/foo", 'foo diverted'); +diversions_eq($diversions_added_foo_local); + +cleanup(); + +note('Adding diversion (7)'); + +install_diversions(''); +call_divert(['--quiet', '--rename', '--package', 'bash', "$testdir/foo"], + expect_stdout => '', expect_stderr => ''); +diversions_eq(<<"EOF"); +$testdir/foo +$testdir/foo.distrib +bash +EOF + +note('Adding diversion (8)'); + +install_diversions(''); +system("touch $testdir/foo; ln $testdir/foo $testdir/foo.distrib"); +call_divert(['--rename', "$testdir/foo"]); +diversions_eq($diversions_added_foo_local); +ok(!-e "$testdir/foo", 'foo diverted'); +ok(-e "$testdir/foo.distrib", 'foo diverted'); + +cleanup(); + +note('Adding diversion (9)'); + +install_diversions(''); +system("touch $testdir/foo $testdir/foo.distrib"); +call_divert(['--rename', "$testdir/foo"], expect_failure => 1, + expect_stderr_like => qr/overwriting/); +diversions_eq(''); + +cleanup(); + +note('Adding second diversion'); + +install_diversions(''); +call_divert(["$testdir/foo"]); + +call_divert(["$testdir/foo"], expect_stdout_like => qr/Leaving/); +call_divert(['--quiet', "$testdir/foo"], expect_stdout => ''); +call_divert(['--divert', "$testdir/foo.bar", "$testdir/foo"], + expect_failure => 1, expect_stderr_like => qr/clashes/); +call_divert(['--package', 'foobar', "$testdir/foo"], expect_failure => 1, + expect_stderr_like => qr/clashes/); +call_divert(['--divert', "$testdir/foo.distrib", "$testdir/bar"], + expect_failure => 1, expect_stderr_like => qr/clashes/); +call_divert(["$testdir/foo.distrib"], + expect_failure => 1, expect_stderr_like => qr/clashes/); +call_divert(['--divert', "$testdir/foo", "$testdir/bar"], + expect_failure => 1, expect_stderr_like => qr/clashes/); + +cleanup(); + +note('Adding third diversion'); + +install_diversions(''); +call_divert(["$testdir/foo"]); +call_divert(["$testdir/bar"]); + +call_divert(['--quiet', "$testdir/foo"], expect_stdout => ''); +call_divert(['--package', 'foobar', "$testdir/bar"], expect_failure => 1, + expect_stderr_like => qr/clashes/); + +cleanup(); + +note('Adding diversion in non-existing directory'); + +install_diversions(''); + +call_divert(['--quiet', '--rename', '--add', "$testdir/zoo/foo"], + expect_stderr => '', expect_stdout => ''); +diversions_eq(<<"EOF"); +$testdir/zoo/foo +$testdir/zoo/foo.distrib +: +EOF + +cleanup(); + +note('Adding diversion of file owned by --package'); + +install_filelist('coreutils', 'i386', "$testdir/foo"); +install_diversions(''); +system("touch $testdir/foo"); + +call_divert(['--quiet', '--rename', '--add', '--package', 'coreutils', "$testdir/foo"], + expect_stderr => '', expect_stdout => ''); +ok(-e "$testdir/foo", 'foo not renamed'); +ok(!-e "$testdir/foo.distrib", 'foo renamed'); +diversions_eq(<<"EOF"); +$testdir/foo +$testdir/foo.distrib +coreutils +EOF + +cleanup(); + +note('Remove diversions'); + +install_diversions(''); + +call_divert(['--no-rename', '--remove', '/bin/sh'], expect_stdout_like => qr/No diversion/, expect_stderr => ''); +call_divert(['--no-rename', '--remove', '--quiet', '/bin/sh'], expect_stdout => '', expect_stderr => ''); + +cleanup(); + +note('Remove diversion (2)'); + +install_diversions(''); +call_divert(["$testdir/foo"]); +call_divert(["$testdir/bar"]); +call_divert(["$testdir/baz"]); + +call_divert(['--divert', "$testdir/foo.my", '--remove', "$testdir/foo"], + expect_failure => 1, expect_stderr_like => qr/mismatch on divert-to/); +call_divert(['--package', 'baz', '--remove', "$testdir/foo"], + expect_failure => 1, expect_stderr_like => qr/mismatch on package/); +call_divert(['--package', 'baz', '--divert', "$testdir/foo.my", '--remove', "$testdir/foo"], + expect_failure => 1, expect_stderr_like => qr/mismatch on (package|divert-to)/); + +call_divert(['--divert', "$testdir/foo.distrib", '--remove', "$testdir/foo"], + expect_stdout_like => qr{Removing.*\Q$testdir\E/foo}); +diversions_eq(<<"EOF"); +$testdir/bar +$testdir/bar.distrib +: +$testdir/baz +$testdir/baz.distrib +: +EOF + +cleanup(); + +note('Remove diversion (3)'); + +install_diversions(''); + +call_divert(["$testdir/foo"]); +call_divert(["$testdir/bar"]); +call_divert(["$testdir/baz"]); + +call_divert(['--remove', "$testdir/bar"], + expect_stdout_like => qr{Removing.*\Q$testdir\E/bar}); +diversions_eq(<<"EOF"); +$testdir/foo +$testdir/foo.distrib +: +$testdir/baz +$testdir/baz.distrib +: +EOF + +cleanup(); + +note('Remove diversion (4)'); + +install_diversions(''); + +call_divert(["$testdir/foo"]); +call_divert(["$testdir/bar"]); +call_divert(['--package', 'bash', "$testdir/baz"]); + +call_divert(['--no-rename', '--quiet', '--package', 'bash', + '--remove', "$testdir/baz"], + expect_stdout => '', expect_stderr => ''); +diversions_eq(<<"EOF"); +$testdir/foo +$testdir/foo.distrib +: +$testdir/bar +$testdir/bar.distrib +: +EOF + +cleanup(); + +note('Remove diversion(5)'); + +install_diversions(''); +system("touch $testdir/foo"); +call_divert(['--rename', "$testdir/foo"]); + +call_divert(['--test', '--rename', '--remove', "$testdir/foo"], + expect_stdout_like => qr{Removing.*\Q$testdir\E/foo}, expect_stderr => ''); +ok(-e "$testdir/foo.distrib", 'foo diversion not removed'); +ok(!-e "$testdir/foo", 'foo diversion not removed'); +diversions_eq($diversions_added_foo_local); + +call_divert(['--quiet', '--rename', '--remove', "$testdir/foo"], + expect_stdout => '', expect_stderr => ''); +ok(-e "$testdir/foo", 'foo diversion removed'); +ok(!-e "$testdir/foo.distrib", 'foo diversion removed'); +diversions_eq(''); + +cleanup(); + +note('Corrupted diversions db handling'); + +SKIP: { + skip 'running as root or similar', 3, if (defined($ENV{FAKEROOTKEY}) or $> == 0); + + # An inexistent diversions db file should not be considered a failure, + # but a failure to open it should be. + install_diversions(''); + system("chmod 000 $admindir/diversions"); + call_divert_sort(['--list'], expect_failure => 1, + expect_stderr_like => qr/(cannot|failed).*open/, expect_stdout => ''); + system("chmod 644 $admindir/diversions"); +} + +install_diversions(<<'EOF'); +/bin/sh +EOF + +call_divert_sort(['--list'], expect_failure => 1, + expect_stderr_like => qr/(corrupt|unexpected end of file)/, + expect_stdout => ''); + +install_diversions(<<'EOF'); +/bin/sh +bash +EOF + +call_divert_sort(['--list'], expect_failure => 1, + expect_stderr_like => qr/(corrupt|unexpected end of file)/, + expect_stdout => ''); + +cleanup(); + +SKIP: { + skip 'running as root or similar', 10, if (defined($ENV{FAKEROOTKEY}) or $> == 0); + + note('R/O directory'); + + install_diversions(''); + system("mkdir $testdir/rodir && touch $testdir/rodir/foo $testdir/bar && chmod 500 $testdir/rodir"); + call_divert(['--rename', '--add', "$testdir/rodir/foo"], + expect_failure => 1, expect_stderr_like => qr/error/); + call_divert(['--rename', '--divert', "$testdir/rodir/bar", '--add', "$testdir/bar"], + expect_failure => 1, expect_stderr_like => qr/error/); + diversions_eq(''); + + system("chmod 755 $testdir/rodir"); + cleanup(); + + note('Unavailable file'); + + install_diversions(''); + system("mkdir $testdir/nadir && chmod 000 $testdir/nadir"); + call_divert(['--rename', '--add', "$testdir/nadir/foo"], + expect_failure => 1, expect_stderr_like => qr/Permission denied/); + system("touch $testdir/foo"); + call_divert(['--rename', '--divert', "$testdir/nadir/foo", '--add', "$testdir/foo"], + expect_failure => 1, expect_stderr_like => qr/Permission denied/); + diversions_eq(''); + + cleanup(); +} + +note('Errors during saving diversions db'); + +install_diversions(''); + +SKIP: { + skip 'running as root or similar', 4, if (defined($ENV{FAKEROOTKEY}) or $> == 0); + + system("chmod 500 $admindir"); + call_divert(["$testdir/foo"], expect_failure => 1, expect_stderr_like => qr/create.*new/); + + system("chmod 755 $admindir"); + + SKIP: { + skip 'device /dev/full is not available', 2 if not -c '/dev/full'; + + system("ln -s /dev/full $admindir/diversions-new"); + call_divert(["$testdir/foo"], expect_failure => 1, + expect_stderr_like => qr/(write|flush|close).*new/); + } +} + +system("rm -f $admindir/diversions-new; mkdir $admindir/diversions-old"); +call_divert(["$testdir/foo"], expect_failure => 1, expect_stderr_like => qr/remov.*old/); diff --git a/src/trigcmd.c b/src/trigcmd.c new file mode 100644 index 0000000..af8119c --- /dev/null +++ b/src/trigcmd.c @@ -0,0 +1,256 @@ +/* + * dpkg-trigger - trigger management utility + * + * Copyright © 2007 Canonical Ltd. + * Written by Ian Jackson <ijackson@chiark.greenend.org.uk> + * Copyright © 2008-2014 Guillem Jover <guillem@debian.org> + * + * This 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 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <compat.h> + +#include <sys/types.h> + +#include <fcntl.h> +#if HAVE_LOCALE_H +#include <locale.h> +#endif +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +#include <dpkg/i18n.h> +#include <dpkg/dpkg.h> +#include <dpkg/dpkg-db.h> +#include <dpkg/options.h> +#include <dpkg/trigdeferred.h> +#include <dpkg/triglib.h> +#include <dpkg/pkg-spec.h> + +static const char printforhelp[] = N_( +"Type dpkg-trigger --help for help about this utility."); + +static void DPKG_ATTR_NORET +printversion(const struct cmdinfo *ci, const char *value) +{ + printf(_("Debian %s package trigger utility version %s.\n"), + dpkg_get_progname(), PACKAGE_RELEASE); + + printf(_( +"This is free software; see the GNU General Public License version 2 or\n" +"later for copying conditions. There is NO warranty.\n")); + + m_output(stdout, _("<standard output>")); + + exit(0); +} + +static void DPKG_ATTR_NORET +usage(const struct cmdinfo *ci, const char *value) +{ + printf(_( +"Usage: %s [<options> ...] <trigger-name>\n" +" %s [<options> ...] <command>\n" +"\n"), dpkg_get_progname(), dpkg_get_progname()); + + printf(_( +"Commands:\n" +" --check-supported Check if the running dpkg supports triggers.\n" +"\n")); + + printf(_( +" -?, --help Show this help message.\n" +" --version Show the version.\n" +"\n")); + + printf(_( +"Options:\n" +" --admindir=<directory> Use <directory> instead of %s.\n" +" --by-package=<package> Override trigger awaiter (normally set\n" +" by dpkg).\n" +" --await Package needs to await the processing.\n" +" --no-await No package needs to await the processing.\n" +" --no-act Just test - don't actually change anything.\n" +"\n"), ADMINDIR); + + m_output(stdout, _("<standard output>")); + + exit(0); +} + +static const char *admindir; +static int f_noact, f_check; +static int f_await = 1; + +static const char *bypackage, *activate; +static bool done_trig, ctrig; + +static void +yespackage(const char *awname) +{ + trigdef_update_printf(" %s", awname); +} + +static const char * +parse_awaiter_package(void) +{ + struct dpkg_error err = DPKG_ERROR_INIT; + struct pkginfo *pkg; + + if (!f_await) + bypackage = "-"; + + if (bypackage == NULL) { + const char *pkgname, *archname; + + pkgname = getenv("DPKG_MAINTSCRIPT_PACKAGE"); + archname = getenv("DPKG_MAINTSCRIPT_ARCH"); + if (pkgname == NULL || archname == NULL) + badusage(_("must be called from a maintainer script" + " (or with a --by-package option)")); + + pkg = pkg_spec_find_pkg(pkgname, archname, &err); + } else if (strcmp(bypackage, "-") == 0) { + pkg = NULL; + } else { + pkg = pkg_spec_parse_pkg(bypackage, &err); + } + + /* Normalize the bypackage name if there was no error. */ + if (pkg) + bypackage = pkg_name(pkg, pnaw_nonambig); + + return err.str; +} + +static void +tdm_add_trig_begin(const char *trig) +{ + ctrig = strcmp(trig, activate) == 0; + trigdef_update_printf("%s", trig); + if (!ctrig || done_trig) + return; + yespackage(bypackage); + done_trig = true; +} + +static void +tdm_add_package(const char *awname) +{ + if (ctrig && strcmp(awname, bypackage) == 0) + return; + yespackage(awname); +} + +static void +tdm_add_trig_end(void) +{ + trigdef_update_printf("\n"); +} + +static const struct trigdefmeths tdm_add = { + .trig_begin = tdm_add_trig_begin, + .package = tdm_add_package, + .trig_end = tdm_add_trig_end, +}; + +static int +do_check(void) +{ + enum trigdef_update_status uf; + + uf = trigdef_update_start(TDUF_NO_LOCK_OK); + switch (uf) { + case TDUS_ERROR_NO_DIR: + notice(_("triggers data directory not yet created")); + return 1; + case TDUS_ERROR_NO_DEFERRED: + notice(_("trigger records not yet in existence")); + return 1; + case TDUS_OK: + case TDUS_ERROR_EMPTY_DEFERRED: + return 0; + default: + internerr("unknown trigdef_update_start return value '%d'", uf); + } +} + +static const struct cmdinfo cmdinfos[] = { + { "admindir", 0, 1, NULL, &admindir }, + { "by-package", 'f', 1, NULL, &bypackage }, + { "await", 0, 0, &f_await, NULL, NULL, 1 }, + { "no-await", 0, 0, &f_await, NULL, NULL, 0 }, + { "no-act", 0, 0, &f_noact, NULL, NULL, 1 }, + { "check-supported", 0, 0, &f_check, NULL, NULL, 1 }, + { "help", '?', 0, NULL, NULL, usage }, + { "version", 0, 0, NULL, NULL, printversion }, + { NULL } +}; + +int +main(int argc, const char *const *argv) +{ + const char *badname; + enum trigdef_update_flags tduf; + enum trigdef_update_status tdus; + + dpkg_locales_init(PACKAGE); + dpkg_program_init("dpkg-trigger"); + dpkg_options_parse(&argv, cmdinfos, printforhelp); + + admindir = dpkg_db_set_dir(admindir); + + if (f_check) { + if (*argv) + badusage(_("--%s takes no arguments"), + "check-supported"); + return do_check(); + } + + if (!*argv || argv[1]) + badusage(_("takes one argument, the trigger name")); + + badname = parse_awaiter_package(); + if (badname) + badusage(_("illegal awaited package name '%.250s': %.250s"), + bypackage, badname); + + fsys_hash_init(); + + activate = argv[0]; + badname = trig_name_is_illegal(activate); + if (badname) + badusage(_("invalid trigger name '%.250s': %.250s"), + activate, badname); + + trigdef_set_methods(&tdm_add); + + tduf = TDUF_NO_LOCK_OK; + if (!f_noact) + tduf |= TDUF_WRITE | TDUF_WRITE_IF_EMPTY; + tdus = trigdef_update_start(tduf); + if (tdus >= 0) { + trigdef_parse(); + if (!done_trig) + trigdef_update_printf("%s %s\n", activate, bypackage); + trigdef_process_done(); + } + + dpkg_program_done(); + + return 0; +} diff --git a/src/trigproc.c b/src/trigproc.c new file mode 100644 index 0000000..54b15bb --- /dev/null +++ b/src/trigproc.c @@ -0,0 +1,575 @@ +/* + * dpkg - main program for package management + * trigproc.c - trigger processing + * + * Copyright © 2007 Canonical Ltd + * written by Ian Jackson <ijackson@chiark.greenend.org.uk> + * Copyright © 2008-2014 Guillem Jover <guillem@debian.org> + * + * This 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 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <compat.h> + +#include <sys/stat.h> + +#include <fcntl.h> +#include <stdlib.h> + +#include <dpkg/i18n.h> +#include <dpkg/dpkg.h> +#include <dpkg/dpkg-db.h> +#include <dpkg/pkg.h> +#include <dpkg/pkg-queue.h> +#include <dpkg/db-ctrl.h> +#include <dpkg/db-fsys.h> +#include <dpkg/triglib.h> + +#include "main.h" + +/* + * Trigger processing algorithms: + * + * + * There is a separate queue (‘deferred trigproc list’) for triggers + * ‘relevant’ to what we just did; when we find something triggered ‘now’ + * we add it to that queue (unless --no-triggers). + * + * + * We want to prefer configuring packages where possible to doing trigger + * processing, although it would be better to prefer trigger processing + * to cycle-breaking we need to do the latter first or we might generate + * artificial trigger cycles, but we always want to prefer trigger + * processing to dependency forcing. This is achieved as follows: + * + * Each time during configure processing where a package D is blocked by + * only (ie Depends unsatisfied but would be satisfied by) a t-awaiter W + * we make a note of (one of) W's t-pending, T. (Only the last such T.) + * (If --no-triggers and nonempty argument list and package isn't in + * argument list then we don't do this.) + * + * Each time in process_queue() where we increment dependtry, we instead + * see if we have encountered such a t-pending T. If we do and are in + * a trigger processing try, we trigproc T instead of incrementing + * dependtry and this counts as having done something so we reset + * sincenothing. + * + * + * For --triggers-only and --configure, we go through each thing in the + * argument queue (the enqueue_package queue) and check what its state is + * and if appropriate we trigproc it. If we didn't have a queue (or had + * just --pending) we search all triggers-pending packages and add them + * to the deferred trigproc list. + * + * + * Before quitting from most operations, we trigproc each package in the + * deferred trigproc list. This may (if not --no-triggers) of course add + * new things to the deferred trigproc list. + * + * + * Note that ‘we trigproc T’ must involve trigger cycle detection and + * also automatic setting of t-awaiters to t-pending or installed. In + * particular, we do cycle detection even for trigger processing in the + * configure dependtry loop (and it is OK to do it for explicitly + * specified packages from the command line arguments; duplicates are + * removed by packages.c:process_queue). + */ + +/*========== Deferred trigger queue. ==========*/ + +static struct pkg_queue deferred = PKG_QUEUE_INIT; + +static void +trigproc_enqueue_deferred(struct pkginfo *pend) +{ + if (f_triggers < 0) + return; + ensure_package_clientdata(pend); + if (pend->clientdata->trigprocdeferred) + return; + pend->clientdata->trigprocdeferred = pkg_queue_push(&deferred, pend); + debug(dbg_triggers, "trigproc_enqueue_deferred pend=%s", + pkg_name(pend, pnaw_always)); +} + +/** + * Populate the deferred trigger queue. + * + * When dpkg is called with a specific set of packages to act on, we might + * have packages pending trigger processing. But because there are frontends + * that do not perform a final «dpkg --configure --pending» call (i.e. apt), + * the system is left in a state with packages not fully installed. + * + * We have to populate the deferred trigger queue from the entire package + * database, so that we might try to do opportunistic trigger processing + * when going through the deferred trigger queue, because a fixed apt will + * not request the necessary processing anyway. + * + * XXX: This can be removed once apt is fixed in the next stable release. + */ +void +trigproc_populate_deferred(void) +{ + struct pkg_hash_iter *iter; + struct pkginfo *pkg; + + iter = pkg_hash_iter_new(); + while ((pkg = pkg_hash_iter_next_pkg(iter))) { + if (!pkg->trigpend_head) + continue; + + if (pkg->status != PKG_STAT_TRIGGERSAWAITED && + pkg->status != PKG_STAT_TRIGGERSPENDING) + continue; + + if (pkg->want != PKG_WANT_INSTALL) + continue; + + trigproc_enqueue_deferred(pkg); + } + pkg_hash_iter_free(iter); +} + +void +trigproc_run_deferred(void) +{ + jmp_buf ejbuf; + + debug(dbg_triggers, "trigproc_run_deferred"); + while (!pkg_queue_is_empty(&deferred)) { + struct pkginfo *pkg; + + pkg = pkg_queue_pop(&deferred); + if (!pkg) + continue; + + if (setjmp(ejbuf)) { + pop_error_context(ehflag_bombout); + continue; + } + push_error_context_jump(&ejbuf, print_error_perpackage, + pkg_name(pkg, pnaw_nonambig)); + + ensure_package_clientdata(pkg); + pkg->clientdata->trigprocdeferred = NULL; + trigproc(pkg, TRIGPROC_TRY_DEFERRED); + + pop_error_context(ehflag_normaltidy); + } +} + +/* + * Called by modstatdb_note. + */ +void +trig_activate_packageprocessing(struct pkginfo *pkg) +{ + debug(dbg_triggersdetail, "trigproc_activate_packageprocessing pkg=%s", + pkg_name(pkg, pnaw_always)); + + trig_parse_ci(pkg_infodb_get_file(pkg, &pkg->installed, TRIGGERSCIFILE), + NULL, trig_cicb_statuschange_activate, + pkg, &pkg->installed); +} + +/*========== Actual trigger processing. ==========*/ + +struct trigcyclenode { + struct trigcyclenode *next; + struct trigcycleperpkg *pkgs; + struct pkginfo *then_processed; +}; + +struct trigcycleperpkg { + struct trigcycleperpkg *next; + struct pkginfo *pkg; + struct trigpend *then_trigs; +}; + +static bool tortoise_advance; +static struct trigcyclenode *tortoise, *hare; + +void +trigproc_reset_cycle(void) +{ + tortoise_advance = false; + tortoise = hare = NULL; +} + +static bool +tortoise_in_hare(struct pkginfo *processing_now, + struct trigcycleperpkg *tortoise_pkg) +{ + const char *processing_now_name, *tortoise_name; + struct trigpend *hare_trig, *tortoise_trig; + + processing_now_name = pkg_name(processing_now, pnaw_nonambig); + tortoise_name = pkg_name(tortoise_pkg->pkg, pnaw_nonambig); + + debug(dbg_triggersdetail, "%s pnow=%s tortoise=%s", __func__, + processing_now_name, tortoise_name); + for (tortoise_trig = tortoise_pkg->then_trigs; + tortoise_trig; + tortoise_trig = tortoise_trig->next) { + debug(dbg_triggersdetail, + "%s pnow=%s tortoise=%s tortoisetrig=%s", __func__, + processing_now_name, tortoise_name, tortoise_trig->name); + + /* hare is now so we can just look up in the actual data. */ + for (hare_trig = tortoise_pkg->pkg->trigpend_head; + hare_trig; + hare_trig = hare_trig->next) { + debug(dbg_triggersstupid, "%s pnow=%s tortoise=%s" + " tortoisetrig=%s haretrig=%s", __func__, + processing_now_name, tortoise_name, + tortoise_trig->name, hare_trig->name); + if (strcmp(hare_trig->name, tortoise_trig->name) == 0) + break; + } + + if (hare_trig == NULL) { + /* Not found in hare, yay! */ + debug(dbg_triggersdetail, "%s pnow=%s tortoise=%s OK", + __func__, processing_now_name, tortoise_name); + return false; + } + } + + return true; +} + +static struct trigcyclenode * +trigproc_new_cyclenode(struct pkginfo *processing_now) +{ + struct trigcyclenode *tcn; + struct trigcycleperpkg *tcpp; + struct pkginfo *pkg; + struct pkg_hash_iter *iter; + + tcn = nfmalloc(sizeof(*tcn)); + tcn->pkgs = NULL; + tcn->next = NULL; + tcn->then_processed = processing_now; + + iter = pkg_hash_iter_new(); + while ((pkg = pkg_hash_iter_next_pkg(iter))) { + if (!pkg->trigpend_head) + continue; + tcpp = nfmalloc(sizeof(*tcpp)); + tcpp->pkg = pkg; + tcpp->then_trigs = pkg->trigpend_head; + tcpp->next = tcn->pkgs; + tcn->pkgs = tcpp; + } + pkg_hash_iter_free(iter); + + return tcn; +} + +/* + * Returns package we are to give up on. + */ +static struct pkginfo * +check_trigger_cycle(struct pkginfo *processing_now) +{ + struct trigcyclenode *tcn; + struct trigcycleperpkg *tortoise_pkg; + struct trigpend *tortoise_trig; + struct pkginfo *giveup; + const char *sep; + + debug(dbg_triggers, "check_triggers_cycle pnow=%s", + pkg_name(processing_now, pnaw_always)); + + tcn = trigproc_new_cyclenode(processing_now); + + if (!hare) { + debug(dbg_triggersdetail, "check_triggers_cycle pnow=%s first", + pkg_name(processing_now, pnaw_always)); + hare = tortoise = tcn; + return NULL; + } + + hare->next = tcn; + hare = hare->next; + if (tortoise_advance) + tortoise = tortoise->next; + tortoise_advance = !tortoise_advance; + + /* Now we compare hare to tortoise. + * We want to find a trigger pending in tortoise which is not in hare + * if we find such a thing we have proved that hare isn't a superset + * of tortoise and so that we haven't found a loop (yet). */ + for (tortoise_pkg = tortoise->pkgs; + tortoise_pkg; + tortoise_pkg = tortoise_pkg->next) { + if (!tortoise_in_hare(processing_now, tortoise_pkg)) + return NULL; + } + /* Oh dear. hare is a superset of tortoise. We are making no + * progress. */ + notice(_("cycle found while processing triggers:\n" + " chain of packages whose triggers are or may be responsible:")); + sep = " "; + for (tcn = tortoise; tcn; tcn = tcn->next) { + fprintf(stderr, "%s%s", sep, + pkg_name(tcn->then_processed, pnaw_nonambig)); + sep = " -> "; + } + fprintf(stderr, _("\n" " packages' pending triggers which are" + " or may be unresolvable:\n")); + for (tortoise_pkg = tortoise->pkgs; + tortoise_pkg; + tortoise_pkg = tortoise_pkg->next) { + fprintf(stderr, " %s", + pkg_name(tortoise_pkg->pkg, pnaw_nonambig)); + sep = ": "; + for (tortoise_trig = tortoise_pkg->then_trigs; + tortoise_trig; + tortoise_trig = tortoise_trig->next) { + fprintf(stderr, "%s%s", sep, tortoise_trig->name); + } + fprintf(stderr, "\n"); + } + + /* We give up on the _earliest_ package involved. */ + giveup = tortoise->pkgs->pkg; + debug(dbg_triggers, "check_triggers_cycle pnow=%s giveup=%s", + pkg_name(processing_now, pnaw_always), + pkg_name(giveup, pnaw_always)); + if (giveup->status != PKG_STAT_TRIGGERSAWAITED && + giveup->status != PKG_STAT_TRIGGERSPENDING) + internerr("package %s in non-trigger state %s", + pkg_name(giveup, pnaw_always), + pkg_status_name(giveup)); + giveup->clientdata->istobe = PKG_ISTOBE_NORMAL; + pkg_set_status(giveup, PKG_STAT_HALFCONFIGURED); + modstatdb_note(giveup); + print_error_perpackage(_("triggers looping, abandoned"), + pkg_name(giveup, pnaw_nonambig)); + + return giveup; +} + +/* + * Does cycle checking. Doesn't mind if pkg has no triggers pending - in + * that case does nothing but fix up any stale awaiters. + */ +void +trigproc(struct pkginfo *pkg, enum trigproc_type type) +{ + static struct varbuf namesarg; + + struct varbuf depwhynot = VARBUF_INIT; + struct trigpend *tp; + struct pkginfo *gaveup; + + debug(dbg_triggers, "trigproc %s", pkg_name(pkg, pnaw_always)); + + ensure_package_clientdata(pkg); + if (pkg->clientdata->trigprocdeferred) + pkg->clientdata->trigprocdeferred->pkg = NULL; + pkg->clientdata->trigprocdeferred = NULL; + + if (pkg->trigpend_head) { + enum dep_check ok; + + if (pkg->status != PKG_STAT_TRIGGERSPENDING && + pkg->status != PKG_STAT_TRIGGERSAWAITED) + internerr("package %s in non-trigger state %s", + pkg_name(pkg, pnaw_always), + pkg_status_name(pkg)); + + if (dependtry < DEPEND_TRY_TRIGGERS && + type == TRIGPROC_TRY_QUEUED) { + /* We are not yet in a triggers run, so postpone this + * package completely. */ + enqueue_package(pkg); + return; + } + + if (dependtry >= DEPEND_TRY_CYCLES) { + if (findbreakcycle(pkg)) + sincenothing = 0; + } + + ok = dependencies_ok(pkg, NULL, &depwhynot); + if (ok == DEP_CHECK_DEFER) { + if (dependtry >= DEPEND_TRY_TRIGGERS_CYCLES) { + gaveup = check_trigger_cycle(pkg); + if (gaveup == pkg) + return; + } + + varbuf_destroy(&depwhynot); + enqueue_package(pkg); + return; + } else if (ok == DEP_CHECK_HALT) { + /* When doing opportunistic deferred trigger processing, + * nothing requires us to be able to make progress; + * skip the package and silently ignore the error due + * to unsatisfiable dependencies. And because we can + * end up here repeatedly, if this package is required + * to make progress for other packages, we need to + * reset the trigger cycle tracking to avoid detecting + * bogus cycles*/ + if (type == TRIGPROC_TRY_DEFERRED) { + trigproc_reset_cycle(); + + varbuf_destroy(&depwhynot); + return; + } + + sincenothing = 0; + varbuf_end_str(&depwhynot); + notice(_("dependency problems prevent processing " + "triggers for %s:\n%s"), + pkg_name(pkg, pnaw_nonambig), depwhynot.buf); + varbuf_destroy(&depwhynot); + ohshit(_("dependency problems - leaving triggers unprocessed")); + } else if (depwhynot.used) { + varbuf_end_str(&depwhynot); + notice(_("%s: dependency problems, but processing " + "triggers anyway as you requested:\n%s"), + pkg_name(pkg, pnaw_nonambig), depwhynot.buf); + varbuf_destroy(&depwhynot); + } + + gaveup = check_trigger_cycle(pkg); + if (gaveup == pkg) + return; + + printf(_("Processing triggers for %s (%s) ...\n"), + pkg_name(pkg, pnaw_nonambig), + versiondescribe(&pkg->installed.version, vdew_nonambig)); + log_action("trigproc", pkg, &pkg->installed); + + varbuf_reset(&namesarg); + for (tp = pkg->trigpend_head; tp; tp = tp->next) { + varbuf_add_char(&namesarg, ' '); + varbuf_add_str(&namesarg, tp->name); + } + varbuf_end_str(&namesarg); + + /* Setting the status to half-configured + * causes modstatdb_note to clear pending triggers. */ + pkg_set_status(pkg, PKG_STAT_HALFCONFIGURED); + modstatdb_note(pkg); + + if (!f_noact) { + sincenothing = 0; + maintscript_postinst(pkg, "triggered", + namesarg.buf + 1, NULL); + } + + post_postinst_tasks(pkg, PKG_STAT_INSTALLED); + } else { + /* In other branch is done by modstatdb_note(), from inside + * post_postinst_tasks(). */ + trig_clear_awaiters(pkg); + } +} + +/*========== Transitional global activation. ==========*/ + +static void +transitional_interest_callback_ro(const char *trig, struct pkginfo *pkg, + struct pkgbin *pkgbin, enum trig_options opts) +{ + struct pkginfo *pend = pkg; + struct pkgbin *pendbin = pkgbin; + + debug(dbg_triggersdetail, + "trig_transitional_interest_callback trig=%s pend=%s", + trig, pkgbin_name(pend, pendbin, pnaw_always)); + if (pend->status >= PKG_STAT_TRIGGERSAWAITED) + trig_note_pend(pend, nfstrsave(trig)); +} + +static void +transitional_interest_callback(const char *trig, struct pkginfo *pkg, + struct pkgbin *pkgbin, enum trig_options opts) +{ + struct pkginfo *pend = pkg; + struct pkgbin *pendbin = pkgbin; + + trig_cicb_interest_add(trig, pend, pendbin, opts); + transitional_interest_callback_ro(trig, pend, pendbin, opts); +} + +/* + * cstatus might be msdbrw_readonly if we're in --no-act mode, in which + * case we don't write out all of the interest files etc. but we do + * invent all of the activations for our own benefit. + */ +static void +trig_transitional_activate(enum modstatdb_rw cstatus) +{ + struct pkg_hash_iter *iter; + struct pkginfo *pkg; + + iter = pkg_hash_iter_new(); + while ((pkg = pkg_hash_iter_next_pkg(iter))) { + if (pkg->status <= PKG_STAT_HALFINSTALLED) + continue; + debug(dbg_triggersdetail, "trig_transitional_activate %s %s", + pkg_name(pkg, pnaw_always), + pkg_status_name(pkg)); + pkg->trigpend_head = NULL; + trig_parse_ci(pkg_infodb_get_file(pkg, &pkg->installed, + TRIGGERSCIFILE), + cstatus >= msdbrw_write ? + transitional_interest_callback : + transitional_interest_callback_ro, NULL, + pkg, &pkg->installed); + /* Ensure we're not creating incoherent data that can't + * be written down. This should never happen in theory but + * can happen if you restore an old status file that is + * not in sync with the infodb files. */ + if (pkg->status < PKG_STAT_TRIGGERSAWAITED) + continue; + + if (pkg->trigaw.head) + pkg_set_status(pkg, PKG_STAT_TRIGGERSAWAITED); + else if (pkg->trigpend_head) + pkg_set_status(pkg, PKG_STAT_TRIGGERSPENDING); + else + pkg_set_status(pkg, PKG_STAT_INSTALLED); + } + pkg_hash_iter_free(iter); + + if (cstatus >= msdbrw_write) { + modstatdb_checkpoint(); + trig_file_interests_save(); + } +} + +/*========== Hook setup. ==========*/ + +TRIGHOOKS_DEFINE_NAMENODE_ACCESSORS + +static const struct trig_hooks trig_our_hooks = { + .enqueue_deferred = trigproc_enqueue_deferred, + .transitional_activate = trig_transitional_activate, + .namenode_find = th_nn_find, + .namenode_interested = th_nn_interested, + .namenode_name = th_nn_name, +}; + +void +trigproc_install_hooks(void) +{ + trig_override_hooks(&trig_our_hooks); +} |