Backport to squeeze
[hcoop/zz_old/debian/config-package-dev.git] / dh_configpackage
CommitLineData
51ae82e4
JR
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, $file;
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" ] || [ "$1" = "abort-remove" ]; 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