source: config-package-dev/dh_configpackage @ b231458

Revision b231458, 17.3 KB checked in by Geoffrey Thomas <geofft@…>, 8 years ago (diff)
<package>.displace-extension can now contain ".extension" or "extension" Previously the former was assumed, and the latter would silently fail
  • 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,
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        close $fh;
227
228        chomp $ret;
229        $ret = ".$ret" unless $ret =~ /^\./; # must start with .
230        return $ret;
231    }
232    $package =~ s/-.*//;
233    return ".$package";
234}
235
236# Replace only the last instance of the displace extension in the
237# filename, to make it possible to displace /path/foo.divert to
238# foo.divert.divert-orig
239sub displace_files_replace_name {
240    my ($package, $filename, $replacement) = @_;
241    my $extension = displace_extension($package);
242    $filename =~ s/(.*)\Q$extension\E/$1$replacement/;
243    return $filename;
244}
245
246# Encode a full path into the path it should be diverted to if it's
247# hidden
248sub hide_files_name {
249    my ($filename, $package) = @_;
250    return "/usr/share/$package/" . encode($filename);
251}
252
253# At compatibility levels 6 and above, prerms take effect in the
254# opposite order from postinsts
255sub reverse_if_6 {
256    if (compat(5)) {
257        return @_;
258    } else {
259        return reverse @_;
260    }
261}
262
263
264# check_file is used to verify that files on local disk have not
265# been modified from the upstream packaged version.
266#
267# We check md5sums from both /var/lib/dpkg/info/$(package).md5sums
268# (the md5sums database for non-conffiles) and the conffiles database
269# used for prompting about conffiles being changed (via dpkg-query).
270#
271# There is some wrangling here because the formats of these sources differ.
272
273sub check_file {
274    my $name = shift;
275    my $truename = `dpkg-divert --truename $name`;
276    chomp $truename;
277    die "$truename missing\n" unless (-e $truename);
278    my $package = `LC_ALL=C dpkg -S $name | sed -n '/^diversion by /! s/: .*\$// p'`;
279    chomp $package;
280    die "$truename is not owned by any package\n" unless ($package);
281
282    my $ctx = Digest::MD5->new;
283    open(my $fh, $truename);
284    binmode $fh;
285    $ctx->addfile($fh);
286    my $digest = $ctx->hexdigest;
287    close $fh;
288
289    my $hassums = 0;
290
291    FINDMD5: {
292        open($fh, "-|", qw(dpkg-query --showformat=${Conffiles}\n --show), $package);
293        while (<$fh>) {
294            next unless /^ \Q$name\E ([0-9a-f]{32})$/;
295            $hassums = 1;
296            if ($1 eq $digest) {
297                last FINDMD5;
298            } else {
299                die "md5sum mismatch on $name\n";
300            }
301        }
302        close $fh;
303
304        open(my $devnull, ">/dev/null");
305        my $pid = open3(undef, my $dpkg_query, $devnull, qw(dpkg-query --control-path), $package, "md5sums");
306        my $md5sums = <$dpkg_query>;
307        chomp $md5sums;
308        close $dpkg_query;
309        close $devnull;
310        waitpid $pid, 0;
311
312        $md5sums ||= "/var/lib/dpkg/info/$package.md5sums";
313
314        if (-e $md5sums) {
315            $hassums = 1;
316            open($fh, $md5sums);
317            my $relname = $name;
318            $relname =~ s|^/||;
319            while (<$fh>) {
320                next unless /^([0-9a-f]{32})  \Q$relname\E$/;
321                if ($1 eq $digest) {
322                    last FINDMD5;
323                } else {
324                    die "md5sum mismatch on $name\n";
325                }
326            }
327            close $fh;
328        }
329
330        if ($hassums) {
331            die "$package contains no md5sums for $name. Is it a generated file?\n";
332        } else {
333            print "config-package-dev: warning: $package does not include md5sums!\n";
334            print "config-package-dev: warning: md5sum for $name not verified.\n";
335        }
336     }
337
338    return $truename;
339}
340
341foreach my $package (@{$dh{DOPACKAGES}}) {
342    my (@displacefiles, @hidefiles, @undisplacefiles, @unhidefiles, @transformfiles);
343
344    if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && $args_present) {
345        @displacefiles = @arg_displace;
346        @hidefiles = @arg_hide;
347        @undisplacefiles = @arg_undisplace;
348        @unhidefiles = @arg_unhide;
349        @transformfiles = map {[split]} @arg_transform;
350    } else {
351        my $displacefile = pkgfile($package, "displace");
352        @displacefiles = filearray($displacefile) if $displacefile;
353        my $hidefile = pkgfile($package, "hide");
354        @hidefiles = filearray($hidefile) if $hidefile;
355        my $undisplacefile = pkgfile($package, "undisplace");
356        @undisplacefiles = filearray($undisplacefile) if $undisplacefile;
357        my $unhidefile = pkgfile($package, "unhide");
358        @unhidefiles = filearray($unhidefile) if $unhidefile;
359        my $transformfile = pkgfile($package, "transform");
360        @transformfiles = filedoublearray($transformfile) if $transformfile;
361    }
362
363    foreach my $listref (\@displacefiles, \@hidefiles, \@undisplacefiles, \@unhidefiles, \@transformfiles) {
364        foreach my $file (@$listref) {
365            $file =~ s|^/?|/|;
366        }
367    }
368
369
370    my $tmp = tmpdir($package);
371    my $extension = displace_extension($package);
372
373    if (! $dh{ONLYSCRIPTS} && @hidefiles) {
374        doit("install", "-d", "$tmp/usr/share/$package");
375    }
376
377    foreach my $line (@transformfiles) {
378        my $file = shift @$line;
379        my $source;
380        if (@$line[0] =~ /^</) {
381            $source = shift @$line;
382            $source =~ s/^<//;
383        } else {
384            $source = displace_files_replace_name($package, $file, "");
385            if ($source eq $file) {
386                die("Error: '$file' does not contain '$extension'\n");
387            }
388        }
389
390        #if ($rest =~ m|^debian/[^ ]*| && -e $rest) {
391        #    # In case this is a single file in debian/, make sure it's
392        #    # executable, since source-format 1.0 debian/ directories
393        #    # (from .diff.gz) cannot have mode bits
394        #    chmod 0755, $rest;
395        #}
396
397        $source = check_file($source);
398        my $destdir = dirname("$tmp/$file");
399        if (! -d $destdir) {
400            doit("install", "-d", $destdir);
401        }
402        complex_doit(@$line, "<", $source, ">", "$tmp/$file");
403        push @displacefiles, $file;
404    }
405
406    # Add code to postinst to add/remove diversions as appropriate
407    if (! $dh{NOSCRIPTS}) {
408        if (@undisplacefiles || @unhidefiles || @displacefiles || @hidefiles) {
409            my $postinst = escape_shell(join "\\n", (
410                'if [ "$1" = "configure" ] || [ "$1" = "abort-remove" ]; then',
411                (map {"    check_undisplace_unlink " . displace_files_replace_name($package, $_, " ")} @undisplacefiles),
412                (map {"    check_undisplace_unhide $_ " . hide_files_name($_, $package)} @unhidefiles),
413                (map {"    displace_link " . displace_files_replace_name($package, $_, " ")} @displacefiles),
414                (map {"    displace_hide $_ " . hide_files_name($_, $package)} @hidefiles),
415                'fi'
416            ));
417            autoscript($package, "postinst", "displace.sh.in",
418                "s/#PACKAGE#/$package/g; s/#DEB_DISPLACE_EXTENSION#/$extension/g; \\\$a\"$postinst\"");
419        }
420        if (@displacefiles || @hidefiles) {
421            my $prerm = escape_shell(join "\\n", (
422                'if [ "$1" = "remove" ] || [ "$1" = "deconfigure" ]; then',
423                (map {"    undisplace_unlink " . displace_files_replace_name($package, $_, " ")} reverse_if_6 (@displacefiles)),
424                (map {"    undisplace_unhide $_ $package"} reverse_if_6 (@hidefiles)),
425                'fi'
426            ));
427            autoscript($package, "prerm", "displace.sh.in",
428                "s/#PACKAGE#/$package/g; s/#DEB_DISPLACE_EXTENSION#/$extension/g; \\\$a\"$prerm\"");
429        }
430    }
431
432    # Add an encoding of the names of the diverted files to the Provides:
433    # and Conflicts: lists.  This prevents two packages diverting the same
434    # file from being installed simultaneously (it cannot work, and this
435    # produces a much less ugly error).  Requires in debian/control:
436    #   Provides: ${diverted-files}
437    #   Conflicts: ${diverted-files}
438    foreach my $file (@displacefiles, @hidefiles) {
439        my $encodedfile = encode(displace_files_replace_name($package, $file, ""));
440        addsubstvar($package, "diverted-files", "diverts-$encodedfile");
441    }
442}
443
444=head1 CAVEATS
445
446Because the displace extension is automatically generated from the
447package name, renaming the package can have unintended consequences.
448If you must rename a package such that the first component of the name
449changes, specify the old extension using the C<displace-extension> file
450(see above).
451
452=head1 SEE ALSO
453
454L<debhelper(7)>, L<The config-package-dev
455homepage|http://debathena.mit.edu/config-package-dev>
456
457This program is a part of config-package-dev.
458
459=head1 AUTHOR
460
461config-package-dev was written by Anders Kaseorg <andersk@mit.edu> and
462Tim Abbott <tabbott@mit.edu>. The debhelper port is by Geoffrey Thomas
463<geofft@mit.edu>.  Documentation by Jonathan Reed <jdreed@mit.edu>.
464
465=cut
Note: See TracBrowser for help on using the repository browser.