source: trunk/debathena/debathena/config-package-dev/dh_configpackage @ 25844

Revision 25844, 17.0 KB checked in by geofft, 12 years ago (diff)
Sync config-package-dev 5.0 from Debian unstable.
  • Property svn:executable set to *
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,
75the original /bin/true would be found at /bin/true.debathena-orig and
76the new version (installed by e.g. dh_install) found at
77/bin/true.debathena.  /bin/true itself would become a symbolic link.
78(For the remainder of this documentation, ".debathena" will be used as
79the displace extension.)
80
81=head1 FILES
82
83=over 4
84
85=item debian/I<package>.displace
86
87List the files to displace, one per line, including the full path and
88displace extension.  For example, to displace /usr/bin/true to
89/usr/bin/true.debathena, you would list "/usr/bin/true.debathena" in
90the file.  (As with other Debhelper commands, you can omit the initial
91leading slash in pathnames in the package, but these examples retain
92it.)
93
94=item debian/I<package>.hide
95
96List the files to hide, one per line, including the full path and
97displace extension.  As noted above, these files won't actually be
98removed, but merely diverted and renamed to a unique path below
99/usr/share/I<package>.
100
101=item debian/I<package>.undisplace
102
103List the files to undisplace, one per line, including the full path and
104displace extension.  B<NOTE:> This is only needed when a new version of
105the package no longer needs to displace a file (for example, if an
106upstream bug was fixed).  Packages automatically undo all operations
107upon removal or deconfiguration.
108
109=item debian/I<package>.unhide
110
111List the files to unhide, one per line, including the full path
112and displace extension.  B<NOTE:> As with undisplace, this is only needed
113when a new version of the package no longer needs to hide a file.
114
115=item debian/I<package>.transform
116
117Each line in the file specifies a transformation.  A transformation
118consists of two space-separated fields: the full path of the
119target file including the displace extension and the transformation
120command itself.  The transformation can either be a single shell
121command, or an executable file in the debian directory.  The
122transformation takes the original source of the file on stdin and prints
123its transformation on stdout.  Transformations are typically performed
124by perl, sed, or awk, but there is no limitation on what can be used as
125a transformation.
126
127For example, to transform /etc/school.conf by replacing all
128occurrences of the word 'Harvard' with the word 'MIT', you might
129specify the following line:
130
131 /etc/school.conf.debathena sed -e 's/Harvard/MIT/g'
132
133Or, storing the command in a separate script:
134
135 /etc/school.conf.debathena debian/transform_school.conf.pl
136
137If the transformation script fails, the package build fails. You can use
138this with e.g. Perl's C<or die> syntax to make sure that the source
139file of the transformation has not changed from what you expected.
140
141I<Transformation sources>: Under normal operation, the source (passed
142on stdin) for the transformation is the name of the diversion without
143the divert extension.  In some cases, you may wish to use a different
144source (e.g. a sample configuration file in /usr/share/doc).  You can
145specify this source as an optional field between the diversion
146filename and the transformation.  This field must begin with a '<'
147immediately followed by the full path to the source.  Taking the
148example above, we might alter it as follows:
149
150 /etc/school.conf.debathena </usr/share/doc/school/conf.example sed -e 's/Harvard/MIT/g'
151
152B<NOTE:> There is no "untransform" operation.  Because a transform
153operation is a special case of a displace operation, the "undisplace"
154operation is the correct way of removing a no-longer-needed
155transformation in future versions of the package.
156
157=item debian/I<package>.displace-extension
158
159This file is used to specify the displace extension for any files
160diverted by this package, if you do not want to accept the default of
161the first word in the package name.  It will not normally be present.
162(See L<"CAVEATS">.)
163
164=back
165
166=head1 OPTIONS
167
168=over 4
169
170=item B<-n>, B<--noscripts>
171
172Do not modify maintainer scripts.  This is a standard debhelper
173option, though you are strongly discouraged from using it except for
174debugging, as these operations rely heavily on the maintainer scripts.
175
176=item B<--displace> I<path>
177
178=item B<--hide> I<path>
179
180=item B<--undisplace> I<path>
181
182=item B<--unhide> I<path>
183
184=item B<--transform> I<transformation>
185
186These options allow for specifying an operation on the command line.
187The argument to the option is the same as a single line of the
188corresponding file, as described above.  You may specify multiple
189occurrences of B<--displace>, or you may invoke B<dh_configpackage>
190repeatedly with different invocations.  The most common use of this
191format is in a rules file when performing conditional operations, in an
192C<override_dh_configpackage> target in the C<rules> file.  See the
193debathena-conffile-example-1.1 package in
194/usr/share/doc/config-package-dev/EXAMPLES for one such use.
195
196=back
197
198=cut
199
200my (@arg_displace, @arg_hide, @arg_undisplace, @arg_unhide, @arg_transform);
201my $args_present = 0;
202
203init(options => {
204    "displace=s" => \@arg_displace,
205    "hide=s" => \@arg_hide,
206    "undisplace=s" => \@arg_undisplace,
207    "unhide=s" => \@arg_unhide,
208    "transform=s" => \@arg_transform,
209});
210
211if (@arg_displace or @arg_hide or @arg_undisplace or @arg_unhide or @arg_transform) {
212    $args_present = 1;
213}
214
215# We default the displace extension to a period followed by the first
216# word of the package name, on the assumption that it is probably the
217# site name (e.g., "debathena-kerberos-config" displaces to
218# ".debathena"). You can set this extension explicitly in
219# debian/$package.displace-extension or debian/displace-extension.
220sub displace_extension {
221    my $package = shift;
222    my $file = pkgfile($package, "displace-extension");
223    if ($file) {
224        open(my $fh, $file);
225        my $ret = <$fh>;
226        chomp $ret;
227        close $fh;
228        return $ret;
229    }
230    $package =~ s/-.*//;
231    return ".$package";
232}
233
234# Replace only the last instance of the displace extension in the
235# filename, to make it possible to displace /path/foo.divert to
236# foo.divert.divert-orig
237sub displace_files_replace_name {
238    my ($package, $filename, $replacement) = @_;
239    my $extension = displace_extension($package);
240    $filename =~ s/(.*)\Q$extension\E/$1$replacement/;
241    return $filename;
242}
243
244# Encode a full path into the path it should be diverted to if it's
245# hidden
246sub hide_files_name {
247    my ($filename, $package) = @_;
248    return "/usr/share/$package/" . encode($filename);
249}
250
251# At compatibility levels 6 and above, prerms take effect in the
252# opposite order from postinsts
253sub reverse_if_6 {
254    if (compat(5)) {
255        return @_;
256    } else {
257        return reverse @_;
258    }
259}
260
261
262# check_file is used to verify that files on local disk have not
263# been modified from the upstream packaged version.
264#
265# We check md5sums from both /var/lib/dpkg/info/$(package).md5sums
266# (the md5sums database for non-conffiles) and the conffiles database
267# used for prompting about conffiles being changed (via dpkg-query).
268#
269# There is some wrangling here because the formats of these sources differ.
270
271sub check_file {
272    my $name = shift;
273    my $truename = `dpkg-divert --truename $name`;
274    chomp $truename;
275    die "$truename missing\n" unless (-e $truename);
276    my $package = `LC_ALL=C dpkg -S $name | sed -n '/^diversion by /! s/: .*\$// p'`;
277    chomp $package;
278    die "$truename is not owned by any package\n" unless ($package);
279
280    my $ctx = Digest::MD5->new;
281    open(my $fh, $truename);
282    binmode $fh;
283    $ctx->addfile($fh);
284    my $digest = $ctx->hexdigest;
285    close $fh;
286
287    my $hassums = 0;
288
289    FINDMD5: {
290        open($fh, "-|", qw(dpkg-query --showformat=${Conffiles}\n --show), $package);
291        while (<$fh>) {
292            next unless /^ \Q$name\E ([0-9a-f]{32})$/;
293            $hassums = 1;
294            if ($1 eq $digest) {
295                last FINDMD5;
296            } else {
297                die "md5sum mismatch on $name\n";
298            }
299        }
300        close $fh;
301
302        open(my $devnull, ">/dev/null");
303        my $pid = open3(undef, my $dpkg_query, $devnull, qw(dpkg-query --control-path), $package, "md5sums");
304        my $md5sums = <$dpkg_query>;
305        chomp $md5sums;
306        close $dpkg_query;
307        close $devnull;
308        waitpid $pid, 0;
309
310        $md5sums ||= "/var/lib/dpkg/info/$package.md5sums";
311
312        if (-e $md5sums) {
313            $hassums = 1;
314            open($fh, $md5sums);
315            my $relname = $name;
316            $relname =~ s|^/||;
317            while (<$fh>) {
318                next unless /^([0-9a-f]{32})  \Q$relname\E$/;
319                if ($1 eq $digest) {
320                    last FINDMD5;
321                } else {
322                    die "md5sum mismatch on $name\n";
323                }
324            }
325            close $fh;
326        }
327
328        if ($hassums) {
329            die "$package contains no md5sums for $name. Is it a generated file?\n";
330        } else {
331            print "config-package-dev: warning: $package does not include md5sums!\n";
332            print "config-package-dev: warning: md5sum for $name not verified.\n";
333        }
334     }
335
336    return $truename;
337}
338
339foreach my $package (@{$dh{DOPACKAGES}}) {
340    my (@displacefiles, @hidefiles, @undisplacefiles, @unhidefiles, @transformfiles);
341
342    if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && $args_present) {
343        @displacefiles = @arg_displace;
344        @hidefiles = @arg_hide;
345        @undisplacefiles = @arg_undisplace;
346        @unhidefiles = @arg_unhide;
347        @transformfiles = map {[split]} @arg_transform;
348    } else {
349        my $displacefile = pkgfile($package, "displace");
350        @displacefiles = filearray($displacefile) if $displacefile;
351        my $hidefile = pkgfile($package, "hide");
352        @hidefiles = filearray($hidefile) if $hidefile;
353        my $undisplacefile = pkgfile($package, "undisplace");
354        @undisplacefiles = filearray($undisplacefile) if $undisplacefile;
355        my $unhidefile = pkgfile($package, "unhide");
356        @unhidefiles = filearray($unhidefile) if $unhidefile;
357        my $transformfile = pkgfile($package, "transform");
358        @transformfiles = filedoublearray($transformfile) if $transformfile;
359    }
360
361    my $tmp = tmpdir($package);
362    my $extension = displace_extension($package);
363
364    if (! $dh{ONLYSCRIPTS} && @hidefiles) {
365        doit("install", "-d", "$tmp/usr/share/$package");
366    }
367
368    foreach my $line (@transformfiles) {
369        my $file = shift @$line;
370        $file =~ s|^/?|/|;
371        my $source;
372        if (@$line[0] =~ /^</) {
373            $source = shift @$line;
374            $source =~ s/^<//;
375        } else {
376            $source = displace_files_replace_name($package, $file, "");
377            if ($source eq $file) {
378                die("Error: '$file' does not contain '$extension'\n");
379            }
380        }
381
382        #if ($rest =~ m|^debian/[^ ]*| && -e $rest) {
383        #    # In case this is a single file in debian/, make sure it's
384        #    # executable, since source-format 1.0 debian/ directories
385        #    # (from .diff.gz) cannot have mode bits
386        #    chmod 0755, $rest;
387        #}
388
389        $source = check_file($source);
390        my $destdir = dirname("$tmp/$file");
391        if (! -d $destdir) {
392            doit("install", "-d", $destdir);
393        }
394        complex_doit(@$line, "<", $source, ">", "$tmp/$file");
395        push @displacefiles, $source;
396    }
397
398    # Add code to postinst to add/remove diversions as appropriate
399    if (! $dh{NOSCRIPTS}) {
400        if (@undisplacefiles || @unhidefiles || @displacefiles || @hidefiles) {
401            my $postinst = escape_shell(join "\\n", (
402                'if [ "$1" = "configure" ]; then',
403                (map {"    check_undisplace_unlink " . displace_files_replace_name($package, $_, " ")} @undisplacefiles),
404                (map {"    check_undisplace_unhide $_ " . hide_files_name($_, $package)} @unhidefiles),
405                (map {"    displace_link " . displace_files_replace_name($package, $_, " ")} @displacefiles),
406                (map {"    displace_hide $_ " . hide_files_name($_, $package)} @hidefiles),
407                'fi'
408            ));
409            autoscript($package, "postinst", "displace.sh.in",
410                "s/#PACKAGE#/$package/g; s/#DEB_DISPLACE_EXTENSION#/$extension/g; \\\$a\"$postinst\"");
411        }
412        if (@displacefiles || @hidefiles) {
413            my $prerm = escape_shell(join "\\n", (
414                'if [ "$1" = "remove" ] || [ "$1" = "deconfigure" ]; then',
415                (map {"    undisplace_unlink " . displace_files_replace_name($package, $_, " ")} reverse_if_6 (@displacefiles)),
416                (map {"    undisplace_unhide $_ $package"} reverse_if_6 (@hidefiles)),
417                'fi'
418            ));
419            autoscript($package, "prerm", "displace.sh.in",
420                "s/#PACKAGE#/$package/g; s/#DEB_DISPLACE_EXTENSION#/$extension/g; \\\$a\"$prerm\"");
421        }
422    }
423
424    # Add an encoding of the names of the diverted files to the Provides:
425    # and Conflicts: lists.  This prevents two packages diverting the same
426    # file from being installed simultaneously (it cannot work, and this
427    # produces a much less ugly error).  Requires in debian/control:
428    #   Provides: ${diverted-files}
429    #   Conflicts: ${diverted-files}
430    foreach my $file (@displacefiles, @hidefiles) {
431        my $encodedfile = encode(displace_files_replace_name($package, $file, ""));
432        addsubstvar($package, "diverted-files", "diverts-$encodedfile");
433    }
434}
435
436=head1 CAVEATS
437
438Because the displace extension is automatically generated from the
439package name, renaming the package can have unintended consequences.
440If you must rename a package such that the first component of the name
441changes, specify the old extension using the C<displace-extension> file
442(see above).
443
444=head1 SEE ALSO
445
446L<debhelper(7)>, L<The config-package-dev
447homepage|http://debathena.mit.edu/config-package-dev>
448
449This program is a part of config-package-dev.
450
451=head1 AUTHOR
452
453config-package-dev was written by Anders Kaseorg <andersk@mit.edu> and
454Tim Abbott <tabbott@mit.edu>. The debhelper port is by Geoffrey Thomas
455<geofft@mit.edu>.  Documentation by Jonathan Reed <jdreed@mit.edu>.
456
457=cut
Note: See TracBrowser for help on using the repository browser.