source: config-package-dev/dh_configpackage @ 61c7bc8

Revision 61c7bc8, 17.5 KB checked in by Geoffrey Thomas <geofft@…>, 7 years ago (diff)
transform: let users pick local files as source In some case it's desirable to use a local file as the source of the transform operation. For example, an upstream file from the same repository that could be sourced at built time. Skip dpkg checks on the source file if it does not start with /
  • 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 diversion without
144the divert extension.  In some cases, you may wish to use a different
145source (e.g. a sample configuration file in /usr/share/doc).  You can
146specify this source as an optional field between the diversion
147filename and the transformation.  This field must begin with a '<'
148immediately followed by the full path to the source.  Taking the
149example above, we might alter it as follows:
150
151 /etc/school.conf.debathena </usr/share/doc/school/conf.example sed -e 's/Harvard/MIT/g'
152
153B<NOTE:> There is no "untransform" operation.  Because a transform
154operation is a special case of a displace operation, the "undisplace"
155operation is the correct way of removing a no-longer-needed
156transformation in future versions of the package.
157
158=item debian/I<package>.displace-extension
159
160This file is used to specify the displace extension for any files
161diverted by this package, if you do not want to accept the default of
162the first word in the package name.  It will not normally be present.
163(See L<"CAVEATS">.)
164
165=back
166
167=head1 OPTIONS
168
169=over 4
170
171=item B<-n>, B<--noscripts>
172
173Do not modify maintainer scripts.  This is a standard debhelper
174option, though you are strongly discouraged from using it except for
175debugging, as these operations rely heavily on the maintainer scripts.
176
177=item B<--displace> I<path>
178
179=item B<--hide> I<path>
180
181=item B<--undisplace> I<path>
182
183=item B<--unhide> I<path>
184
185=item B<--transform> I<transformation>
186
187These options allow for specifying an operation on the command line.
188The argument to the option is the same as a single line of the
189corresponding file, as described above.  You may specify multiple
190occurrences of B<--displace>, or you may invoke B<dh_configpackage>
191repeatedly with different invocations.  The most common use of this
192format is in a rules file when performing conditional operations, in an
193C<override_dh_configpackage> target in the C<rules> file.  See the
194debathena-conffile-example-1.1 package in
195/usr/share/doc/config-package-dev/EXAMPLES for one such use.
196
197=back
198
199=cut
200
201my (@arg_displace, @arg_hide, @arg_undisplace, @arg_unhide, @arg_transform);
202my $args_present = 0;
203
204init(options => {
205    "displace=s" => \@arg_displace,
206    "hide=s" => \@arg_hide,
207    "undisplace=s" => \@arg_undisplace,
208    "unhide=s" => \@arg_unhide,
209    "transform=s" => \@arg_transform,
210});
211
212if (@arg_displace or @arg_hide or @arg_undisplace or @arg_unhide or @arg_transform) {
213    $args_present = 1;
214}
215
216# We default the displace extension to a period followed by the first
217# word of the package name, on the assumption that it is probably the
218# site name (e.g., "debathena-kerberos-config" displaces to
219# ".debathena"). You can set this extension explicitly in
220# debian/$package.displace-extension or debian/displace-extension.
221sub displace_extension {
222    my $package = shift;
223    my $file = pkgfile($package, "displace-extension");
224    if ($file) {
225        open(my $fh, $file);
226        my $ret = <$fh>;
227        close $fh;
228
229        chomp $ret;
230        $ret = ".$ret" unless $ret =~ /^\./; # must start with .
231        return $ret;
232    }
233    $package =~ s/-.*//;
234    return ".$package";
235}
236
237# Replace only the last instance of the displace extension in the
238# filename, to make it possible to displace /path/foo.divert to
239# foo.divert.divert-orig
240sub displace_files_replace_name {
241    my ($package, $filename, $replacement) = @_;
242    my $extension = displace_extension($package);
243    $filename =~ s/(.*)\Q$extension\E/$1$replacement/;
244    return $filename;
245}
246
247# Encode a full path into the path it should be diverted to if it's
248# hidden
249sub hide_files_name {
250    my ($filename, $package) = @_;
251    return "/usr/share/$package/" . encode($filename);
252}
253
254# At compatibility levels 6 and above, prerms take effect in the
255# opposite order from postinsts
256sub reverse_if_6 {
257    if (compat(5)) {
258        return @_;
259    } else {
260        return reverse @_;
261    }
262}
263
264
265# check_file is used to verify that files on local disk have not
266# been modified from the upstream packaged version.
267#
268# We check md5sums from both /var/lib/dpkg/info/$(package).md5sums
269# (the md5sums database for non-conffiles) and the conffiles database
270# used for prompting about conffiles being changed (via dpkg-query).
271#
272# There is some wrangling here because the formats of these sources differ.
273
274sub check_file {
275    my $name = shift;
276    my $truename = `dpkg-divert --truename $name`;
277    chomp $truename;
278    die "$truename missing\n" unless (-e $truename);
279    my $package = `LC_ALL=C dpkg -S $name | sed -n '/^diversion by /! s/: .*\$// p'`;
280    chomp $package;
281    die "$truename is not owned by any package\n" unless ($package);
282
283    my $ctx = Digest::MD5->new;
284    open(my $fh, $truename);
285    binmode $fh;
286    $ctx->addfile($fh);
287    my $digest = $ctx->hexdigest;
288    close $fh;
289
290    my $hassums = 0;
291
292    FINDMD5: {
293        open($fh, "-|", qw(dpkg-query --showformat=${Conffiles}\n --show), $package);
294        while (<$fh>) {
295            next unless /^ \Q$name\E ([0-9a-f]{32})$/;
296            $hassums = 1;
297            if ($1 eq $digest) {
298                last FINDMD5;
299            } else {
300                die "md5sum mismatch on $name\n";
301            }
302        }
303        close $fh;
304
305        open(my $devnull, ">/dev/null");
306        my $pid = open3(undef, my $dpkg_query, $devnull, qw(dpkg-query --control-path), $package, "md5sums");
307        my $md5sums = <$dpkg_query>;
308        chomp $md5sums;
309        close $dpkg_query;
310        close $devnull;
311        waitpid $pid, 0;
312
313        $md5sums ||= "/var/lib/dpkg/info/$package.md5sums";
314
315        if (-e $md5sums) {
316            $hassums = 1;
317            open($fh, $md5sums);
318            my $relname = $name;
319            $relname =~ s|^/||;
320            while (<$fh>) {
321                next unless /^([0-9a-f]{32})  \Q$relname\E$/;
322                if ($1 eq $digest) {
323                    last FINDMD5;
324                } else {
325                    die "md5sum mismatch on $name\n";
326                }
327            }
328            close $fh;
329        }
330
331        if ($hassums) {
332            die "$package contains no md5sums for $name. Is it a generated file?\n";
333        } else {
334            print "config-package-dev: warning: $package does not include md5sums!\n";
335            print "config-package-dev: warning: md5sum for $name not verified.\n";
336        }
337     }
338
339    return $truename;
340}
341
342foreach my $package (@{$dh{DOPACKAGES}}) {
343    my (@displacefiles, @hidefiles, @undisplacefiles, @unhidefiles, @transformfiles);
344
345    if (($package eq $dh{FIRSTPACKAGE} || $dh{PARAMS_ALL}) && $args_present) {
346        @displacefiles = @arg_displace;
347        @hidefiles = @arg_hide;
348        @undisplacefiles = @arg_undisplace;
349        @unhidefiles = @arg_unhide;
350        @transformfiles = map {[split]} @arg_transform;
351    } else {
352        my $displacefile = pkgfile($package, "displace");
353        @displacefiles = filearray($displacefile) if $displacefile;
354        my $hidefile = pkgfile($package, "hide");
355        @hidefiles = filearray($hidefile) if $hidefile;
356        my $undisplacefile = pkgfile($package, "undisplace");
357        @undisplacefiles = filearray($undisplacefile) if $undisplacefile;
358        my $unhidefile = pkgfile($package, "unhide");
359        @unhidefiles = filearray($unhidefile) if $unhidefile;
360        my $transformfile = pkgfile($package, "transform");
361        @transformfiles = filedoublearray($transformfile) if $transformfile;
362    }
363
364    foreach my $listref (\@displacefiles, \@hidefiles, \@undisplacefiles, \@unhidefiles, \@transformfiles) {
365        foreach my $file (@$listref) {
366            $file =~ s|^/?|/|;
367        }
368    }
369
370
371    my $tmp = tmpdir($package);
372    my $extension = displace_extension($package);
373
374    if (! $dh{ONLYSCRIPTS} && @hidefiles) {
375        doit("install", "-d", "$tmp/usr/share/$package");
376    }
377
378    foreach my $line (@transformfiles) {
379        my $file = shift @$line;
380        my $source;
381        my $source_is_local = 0;
382        if (@$line[0] =~ /^</) {
383            $source = shift @$line;
384            $source =~ s/^<//;
385            if ($source !~ m!^/!) {
386                $source_is_local = 1;
387            }
388        } else {
389            $source = displace_files_replace_name($package, $file, "");
390            if ($source eq $file) {
391                die("Error: '$file' does not contain '$extension'\n");
392            }
393        }
394
395        #if ($rest =~ m|^debian/[^ ]*| && -e $rest) {
396        #    # In case this is a single file in debian/, make sure it's
397        #    # executable, since source-format 1.0 debian/ directories
398        #    # (from .diff.gz) cannot have mode bits
399        #    chmod 0755, $rest;
400        #}
401
402        # Let users use local files as input
403        if (! $source_is_local) {
404            $source = check_file($source);
405        }
406        my $destdir = dirname("$tmp/$file");
407        if (! -d $destdir) {
408            doit("install", "-d", $destdir);
409        }
410        complex_doit(@$line, "<", $source, ">", "$tmp/$file");
411        push @displacefiles, $file;
412    }
413
414    # Add code to postinst to add/remove diversions as appropriate
415    if (! $dh{NOSCRIPTS}) {
416        if (@undisplacefiles || @unhidefiles || @displacefiles || @hidefiles) {
417            my $postinst = escape_shell(join "\\n", (
418                'if [ "$1" = "configure" ] || [ "$1" = "abort-remove" ]; then',
419                (map {"    check_undisplace_unlink " . displace_files_replace_name($package, $_, " ")} @undisplacefiles),
420                (map {"    check_undisplace_unhide $_ " . hide_files_name($_, $package)} @unhidefiles),
421                (map {"    displace_link " . displace_files_replace_name($package, $_, " ")} @displacefiles),
422                (map {"    displace_hide $_ " . hide_files_name($_, $package)} @hidefiles),
423                'fi'
424            ));
425            autoscript($package, "postinst", "displace.sh.in",
426                "s/#PACKAGE#/$package/g; s/#DEB_DISPLACE_EXTENSION#/$extension/g; \\\$a\"$postinst\"");
427        }
428        if (@displacefiles || @hidefiles) {
429            my $prerm = escape_shell(join "\\n", (
430                'if [ "$1" = "remove" ] || [ "$1" = "deconfigure" ]; then',
431                (map {"    undisplace_unlink " . displace_files_replace_name($package, $_, " ")} reverse_if_6 (@displacefiles)),
432                (map {"    undisplace_unhide $_ $package"} reverse_if_6 (@hidefiles)),
433                'fi'
434            ));
435            autoscript($package, "prerm", "displace.sh.in",
436                "s/#PACKAGE#/$package/g; s/#DEB_DISPLACE_EXTENSION#/$extension/g; \\\$a\"$prerm\"");
437        }
438    }
439
440    # Add an encoding of the names of the diverted files to the Provides:
441    # and Conflicts: lists.  This prevents two packages diverting the same
442    # file from being installed simultaneously (it cannot work, and this
443    # produces a much less ugly error).  Requires in debian/control:
444    #   Provides: ${diverted-files}
445    #   Conflicts: ${diverted-files}
446    foreach my $file (@displacefiles, @hidefiles) {
447        my $encodedfile = encode(displace_files_replace_name($package, $file, ""));
448        addsubstvar($package, "diverted-files", "diverts-$encodedfile");
449    }
450}
451
452=head1 CAVEATS
453
454Because the displace extension is automatically generated from the
455package name, renaming the package can have unintended consequences.
456If you must rename a package such that the first component of the name
457changes, specify the old extension using the C<displace-extension> file
458(see above).
459
460=head1 SEE ALSO
461
462L<debhelper(7)>, L<The config-package-dev
463homepage|http://debathena.mit.edu/config-package-dev>
464
465This program is a part of config-package-dev.
466
467=head1 AUTHOR
468
469config-package-dev was written by Anders Kaseorg <andersk@mit.edu> and
470Tim Abbott <tabbott@mit.edu>. The debhelper port is by Geoffrey Thomas
471<geofft@mit.edu>.  Documentation by Jonathan Reed <jdreed@mit.edu>.
472
473=cut
Note: See TracBrowser for help on using the repository browser.