source: config-package-dev/dh_configpackage @ 354f042

Revision 354f042, 18.4 KB checked in by Geoffrey Thomas <geofft@…>, 7 years ago (diff)
dh_configpackage: Fix leading slashes check for the transform operation
  • Property mode set to 100755
Line 
1#!/usr/bin/perl -w
2# Copyright © 2007-2008 Anders Kaseorg <andersk@mit.edu> and
3#                       Tim Abbott <tabbott@mit.edu>
4# Copyright © 2011-2012 Geoffrey Thomas <geofft@mit.edu>
5#
6# This program is free software; you can redistribute it and/or
7# modify it under the terms of the GNU General Public License as
8# published by the Free Software Foundation; either version 2, or (at
9# your option) any later version.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14# General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19# 02111-1307 USA.
20
21
22=head1 NAME
23
24dh_configpackage - add maintainer script rules to displace, hide, or transform files
25
26=cut
27
28use strict;
29use Debian::Debhelper::Dh_Lib;
30use Debian::Debhelper::config_package;
31use Digest::MD5;
32use IPC::Open3;
33
34
35=head1 SYNOPSIS
36
37B<dh_configpackage> [B<--displace> I<path>] [B<--hide> I<path>] [B<--undisplace> I<path>] [B<--unhide> I<file>] [B<--transform> I<transformation>] [S<I<debhelper options>>] [B<-n>]
38
39=head1 DESCRIPTION
40
41B<dh_configpackage> is a debhelper program to create "configuration
42packages".  These packages provide an ideal way to distribute
43configurations to target systems while still affording local system
44administrators a degree of control over their workstations.  The
45motivation and philosophy behind this style of packaging is described
46in detail on the config-package-dev website.  Configuration packages
47make use of dpkg diversions and maintainer script snippets to provide
48three primary operations: displacing, hiding, and transforming files.
49
50The I<displace> operation consists of replacing a file on the target
51system.  The original file is renamed out of the way and diverted in the
52dpkg database.  The replacement file is then installed by the package,
53and the config-package-dev maintainer script snippets create a symlink
54from the original name.  A common use of this is to install a wrapper
55script for an executable.
56
57The I<transform> operation is a special case of the displace operation.
58At build time, a "transform script" is applied to the original source,
59and the result is used as the replacement in the displace operation.  A
60common use of this is to change one value in a config file without
61needing to re-type the entire config file (and risk bit-rot).
62
63The I<hide> operation is yet another special case of the displace
64operation, namely that there is no replacement or symlink.  Instead, the
65file is diverted to a unique path on the target system, thus preserving
66its contents.  A common use of this is to suppress a snippet file in a
67configuration directory (e.g. /etc/foo.d), thus disabling a specific
68operation or configuration.
69
70The I<displace extension> is a suffix appended to the diverted versions
71of files, and this suffix plus the string "-orig" is appended to the
72original versions of the files.  The default value is the first word of
73the package name.  For example, the extension for debathena-bin-example
74would be ".debathena".  So if debathena-bin-example displaced /bin/true,
75dh_configpackage would create a diversion from /bin/true to
76/bin/true.debathena.orig, and create a symbolic link from /bin/true to
77/bin/true.debathena.  The package should then install its modified
78version at /bin/true.debathena, using e.g. B<dh_install>.
79(For the remainder of this documentation, ".debathena" will be used as
80the displace extension.)
81
82=head1 FILES
83
84=over 4
85
86=item debian/I<package>.displace
87
88List the files to displace, one per line, including the full path and
89displace extension.  For example, to displace /usr/bin/true to
90/usr/bin/true.debathena, you would list "/usr/bin/true.debathena" in
91the file.  (As with other Debhelper commands, you can omit the initial
92leading slash in pathnames in the package, but these examples retain
93it.)
94
95=item debian/I<package>.hide
96
97List the files to hide, one per line, including the full path and
98displace extension.  As noted above, these files won't actually be
99removed, but merely diverted and renamed to a unique path below
100/usr/share/I<package>.
101
102=item debian/I<package>.undisplace
103
104List the files to undisplace, one per line, including the full path and
105displace extension.  B<NOTE:> This is only needed when a new version of
106the package no longer needs to displace a file (for example, if an
107upstream bug was fixed).  Packages automatically undo all operations
108upon removal or deconfiguration.
109
110=item debian/I<package>.unhide
111
112List the files to unhide, one per line, including the full path
113and displace extension.  B<NOTE:> As with undisplace, this is only needed
114when a new version of the package no longer needs to hide a file.
115
116=item debian/I<package>.transform
117
118Each line in the file specifies a transformation.  A transformation
119consists of two space-separated fields: the full path of the
120target file including the displace extension and the transformation
121command itself.  The transformation can either be a single shell
122command, or an executable file in the debian directory.  The
123transformation takes the original source of the file on stdin and prints
124its transformation on stdout.  Transformations are typically performed
125by perl, sed, or awk, but there is no limitation on what can be used as
126a transformation.
127
128For example, to transform /etc/school.conf by replacing all
129occurrences of the word 'Harvard' with the word 'MIT', you might
130specify the following line:
131
132 /etc/school.conf.debathena sed -e 's/Harvard/MIT/g'
133
134Or, storing the command in a separate script:
135
136 /etc/school.conf.debathena debian/transform_school.conf.pl
137
138If the transformation script fails, the package build fails. You can use
139this with e.g. Perl's C<or die> syntax to make sure that the source
140file of the transformation has not changed from what you expected.
141
142I<Transformation sources>: Under normal operation, the source (passed
143on stdin) for the transformation is the name of the target file without
144the displace extension. B<dh_configpackage> will check to make sure that
145the source file is owned by a Debian package and has not been modified
146locally, in order to ensure that unwanted changes do not show up in the
147result of the transformation. (If the file is already displaced or
148transformed by a config-package-dev package, it will check the original
149version of the file and use that as the transformation source, allowing
150a config-package-dev package to be built correctly on a running system
151where a previous version of the same package was already installed.)
152
153In some cases, you may wish to use a different
154source (e.g. a sample configuration file in /usr/share/doc).  You can
155specify this source as an optional field between the diversion
156filename and the transformation.  This field must begin with a '<'
157immediately followed by the full path to the source.  Taking the
158example above, we might alter it as follows:
159
160 /etc/school.conf.debathena </usr/share/doc/school/conf.example sed -e 's/Harvard/MIT/g'
161
162The full path should generally be an absolute path; however, in rare
163cases, it is useful to use a file in the current package's build
164directory as the transformation source.  In this case, the package's
165build directory is assumed to be clean, and no dpkg ownership check is
166performed.
167
168B<NOTE:> There is no "untransform" operation.  Because a transform
169operation is a special case of a displace operation, the "undisplace"
170operation is the correct way of removing a no-longer-needed
171transformation in future versions of the package.
172
173=item debian/I<package>.displace-extension
174
175This file is used to specify the displace extension for any files
176diverted by this package, if you do not want to accept the default of
177the first word in the package name.  It will not normally be present.
178(See L<"CAVEATS">.)
179
180=back
181
182=head1 OPTIONS
183
184=over 4
185
186=item B<-n>, B<--noscripts>
187
188Do not modify maintainer scripts.  This is a standard debhelper
189option, though you are strongly discouraged from using it except for
190debugging, as these operations rely heavily on the maintainer scripts.
191
192=item B<--displace> I<path>
193
194=item B<--hide> I<path>
195
196=item B<--undisplace> I<path>
197
198=item B<--unhide> I<path>
199
200=item B<--transform> I<transformation>
201
202These options allow for specifying an operation on the command line.
203The argument to the option is the same as a single line of the
204corresponding file, as described above.  You may specify multiple
205occurrences of B<--displace>, or you may invoke B<dh_configpackage>
206repeatedly with different invocations.  The most common use of this
207format is in a rules file when performing conditional operations, in an
208C<override_dh_configpackage> target in the C<rules> file.  See the
209debathena-conffile-example-1.1 package in
210/usr/share/doc/config-package-dev/EXAMPLES for one such use.
211
212=back
213
214=cut
215
216my (@arg_displace, @arg_hide, @arg_undisplace, @arg_unhide, @arg_transform);
217my $args_present = 0;
218
219init(options => {
220    "displace=s" => \@arg_displace,
221    "hide=s" => \@arg_hide,
222    "undisplace=s" => \@arg_undisplace,
223    "unhide=s" => \@arg_unhide,
224    "transform=s" => \@arg_transform,
225});
226
227if (@arg_displace or @arg_hide or @arg_undisplace or @arg_unhide or @arg_transform) {
228    $args_present = 1;
229}
230
231# We default the displace extension to a period followed by the first
232# word of the package name, on the assumption that it is probably the
233# site name (e.g., "debathena-kerberos-config" displaces to
234# ".debathena"). You can set this extension explicitly in
235# debian/$package.displace-extension or debian/displace-extension.
236sub displace_extension {
237    my $package = shift;
238    my $file = pkgfile($package, "displace-extension");
239    if ($file) {
240        open(my $fh, $file);
241        my $ret = <$fh>;
242        close $fh;
243
244        chomp $ret;
245        $ret = ".$ret" unless $ret =~ /^\./; # must start with .
246        return $ret;
247    }
248    $package =~ s/-.*//;
249    return ".$package";
250}
251
252# Replace only the last instance of the displace extension in the
253# filename, to make it possible to displace /path/foo.divert to
254# foo.divert.divert-orig
255sub displace_files_replace_name {
256    my ($package, $filename, $replacement) = @_;
257    my $extension = displace_extension($package);
258    $filename =~ s/(.*)\Q$extension\E/$1$replacement/;
259    return $filename;
260}
261
262# Encode a full path into the path it should be diverted to if it's
263# hidden
264sub hide_files_name {
265    my ($filename, $package) = @_;
266    return "/usr/share/$package/" . encode($filename);
267}
268
269# At compatibility levels 6 and above, prerms take effect in the
270# opposite order from postinsts
271sub reverse_if_6 {
272    if (compat(5)) {
273        return @_;
274    } else {
275        return reverse @_;
276    }
277}
278
279
280# check_file is used to verify that files on local disk have not
281# been modified from the upstream packaged version.
282#
283# We check md5sums from both /var/lib/dpkg/info/$(package).md5sums
284# (the md5sums database for non-conffiles) and the conffiles database
285# used for prompting about conffiles being changed (via dpkg-query).
286#
287# There is some wrangling here because the formats of these sources differ.
288
289sub check_file {
290    my $name = shift;
291    my $truename = `dpkg-divert --truename $name`;
292    chomp $truename;
293    die "$truename missing\n" unless (-e $truename);
294    my $package = `LC_ALL=C dpkg -S $name | sed -n '/^diversion by /! s/: .*\$// p'`;
295    chomp $package;
296    die "$truename is not owned by any package\n" unless ($package);
297
298    my $ctx = Digest::MD5->new;
299    open(my $fh, $truename);
300    binmode $fh;
301    $ctx->addfile($fh);
302    my $digest = $ctx->hexdigest;
303    close $fh;
304
305    my $hassums = 0;
306
307    FINDMD5: {
308        open($fh, "-|", qw(dpkg-query --showformat=${Conffiles}\n --show), $package);
309        while (<$fh>) {
310            next unless /^ \Q$name\E ([0-9a-f]{32})$/;
311            $hassums = 1;
312            if ($1 eq $digest) {
313                last FINDMD5;
314            } else {
315                die "md5sum mismatch on $name\n";
316            }
317        }
318        close $fh;
319
320        open(my $devnull, ">/dev/null");
321        my $pid = open3(undef, my $dpkg_query, $devnull, qw(dpkg-query --control-path), $package, "md5sums");
322        my $md5sums = <$dpkg_query>;
323        chomp $md5sums;
324        close $dpkg_query;
325        close $devnull;
326        waitpid $pid, 0;
327
328        $md5sums ||= "/var/lib/dpkg/info/$package.md5sums";
329
330        if (-e $md5sums) {
331            $hassums = 1;
332            open($fh, $md5sums);
333            my $relname = $name;
334            $relname =~ s|^/||;
335            while (<$fh>) {
336                next unless /^([0-9a-f]{32})  \Q$relname\E$/;
337                if ($1 eq $digest) {
338                    last FINDMD5;
339                } else {
340                    die "md5sum mismatch on $name\n";
341                }
342            }
343            close $fh;
344        }
345
346        if ($hassums) {
347            die "$package contains no md5sums for $name. Is it a generated file?\n";
348        } else {
349            print "config-package-dev: warning: $package does not include md5sums!\n";
350            print "config-package-dev: warning: md5sum for $name not verified.\n";
351        }
352     }
353
354    return $truename;
355}
356
357foreach my $package (@{$dh{DOPACKAGES}}) {
358    my (@displacefiles, @hidefiles, @undisplacefiles, @unhidefiles, @transformfiles);
359
360    if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && $args_present) {
361        @displacefiles = @arg_displace;
362        @hidefiles = @arg_hide;
363        @undisplacefiles = @arg_undisplace;
364        @unhidefiles = @arg_unhide;
365        @transformfiles = map {[split]} @arg_transform;
366    } else {
367        my $displacefile = pkgfile($package, "displace");
368        @displacefiles = filearray($displacefile) if $displacefile;
369        my $hidefile = pkgfile($package, "hide");
370        @hidefiles = filearray($hidefile) if $hidefile;
371        my $undisplacefile = pkgfile($package, "undisplace");
372        @undisplacefiles = filearray($undisplacefile) if $undisplacefile;
373        my $unhidefile = pkgfile($package, "unhide");
374        @unhidefiles = filearray($unhidefile) if $unhidefile;
375        my $transformfile = pkgfile($package, "transform");
376        @transformfiles = filedoublearray($transformfile) if $transformfile;
377    }
378
379    foreach my $listref (\@displacefiles, \@hidefiles, \@undisplacefiles, \@unhidefiles) {
380        foreach my $file (@$listref) {
381            $file =~ s|^/?|/|;
382        }
383    }
384
385
386    my $tmp = tmpdir($package);
387    my $extension = displace_extension($package);
388
389    if (! $dh{ONLYSCRIPTS} && @hidefiles) {
390        doit("install", "-d", "$tmp/usr/share/$package");
391    }
392
393    foreach my $line (@transformfiles) {
394        my $file = shift @$line;
395        $file =~ s|^/?|/|;
396        my $source;
397        my $source_is_local = 0;
398        if (@$line[0] =~ /^</) {
399            $source = shift @$line;
400            $source =~ s/^<//;
401            if ($source !~ m!^/!) {
402                $source_is_local = 1;
403            }
404        } else {
405            $source = displace_files_replace_name($package, $file, "");
406            if ($source eq $file) {
407                die("Error: '$file' does not contain '$extension'\n");
408            }
409        }
410
411        #if ($rest =~ m|^debian/[^ ]*| && -e $rest) {
412        #    # In case this is a single file in debian/, make sure it's
413        #    # executable, since source-format 1.0 debian/ directories
414        #    # (from .diff.gz) cannot have mode bits
415        #    chmod 0755, $rest;
416        #}
417
418        # Let users use local files as input
419        if (! $source_is_local) {
420            $source = check_file($source);
421        }
422        my $destdir = dirname("$tmp/$file");
423        if (! -d $destdir) {
424            doit("install", "-d", $destdir);
425        }
426        complex_doit(@$line, "<", $source, ">", "$tmp/$file");
427        push @displacefiles, $file;
428    }
429
430    # Add code to postinst to add/remove diversions as appropriate
431    if (! $dh{NOSCRIPTS}) {
432        if (@undisplacefiles || @unhidefiles || @displacefiles || @hidefiles) {
433            my $postinst = escape_shell(join "\\n", (
434                'if [ "$1" = "configure" ] || [ "$1" = "abort-remove" ]; then',
435                (map {"    check_undisplace_unlink " . displace_files_replace_name($package, $_, " ")} @undisplacefiles),
436                (map {"    check_undisplace_unhide $_ " . hide_files_name($_, $package)} @unhidefiles),
437                (map {"    displace_link " . displace_files_replace_name($package, $_, " ")} @displacefiles),
438                (map {"    displace_hide $_ " . hide_files_name($_, $package)} @hidefiles),
439                'fi'
440            ));
441            autoscript($package, "postinst", "displace.sh.in",
442                "s/#PACKAGE#/$package/g; s/#DEB_DISPLACE_EXTENSION#/$extension/g; \\\$a\"$postinst\"");
443        }
444        if (@displacefiles || @hidefiles) {
445            my $prerm = escape_shell(join "\\n", (
446                'if [ "$1" = "remove" ] || [ "$1" = "deconfigure" ]; then',
447                (map {"    undisplace_unlink " . displace_files_replace_name($package, $_, " ")} reverse_if_6 (@displacefiles)),
448                (map {"    undisplace_unhide $_ $package"} reverse_if_6 (@hidefiles)),
449                'fi'
450            ));
451            autoscript($package, "prerm", "displace.sh.in",
452                "s/#PACKAGE#/$package/g; s/#DEB_DISPLACE_EXTENSION#/$extension/g; \\\$a\"$prerm\"");
453        }
454    }
455
456    # Add an encoding of the names of the diverted files to the Provides:
457    # and Conflicts: lists.  This prevents two packages diverting the same
458    # file from being installed simultaneously (it cannot work, and this
459    # produces a much less ugly error).  Requires in debian/control:
460    #   Provides: ${diverted-files}
461    #   Conflicts: ${diverted-files}
462    foreach my $file (@displacefiles, @hidefiles) {
463        my $encodedfile = encode(displace_files_replace_name($package, $file, ""));
464        addsubstvar($package, "diverted-files", "diverts-$encodedfile");
465    }
466}
467
468=head1 CAVEATS
469
470Because the displace extension is automatically generated from the
471package name, renaming the package can have unintended consequences.
472If you must rename a package such that the first component of the name
473changes, specify the old extension using the C<displace-extension> file
474(see above).
475
476=head1 SEE ALSO
477
478L<debhelper(7)>, L<The config-package-dev
479homepage|http://debathena.mit.edu/config-package-dev>
480
481This program is a part of config-package-dev.
482
483=head1 AUTHOR
484
485config-package-dev was written by Anders Kaseorg <andersk@mit.edu> and
486Tim Abbott <tabbott@mit.edu>. The debhelper port is by Geoffrey Thomas
487<geofft@mit.edu>.  Documentation by Jonathan Reed <jdreed@mit.edu>.
488
489=cut
Note: See TracBrowser for help on using the repository browser.