Backport to squeeze
[hcoop/zz_old/debian/config-package-dev.git] / dh_configpackage
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
24 dh_configpackage - add maintainer script rules to displace, hide, or transform files
25
26 =cut
27
28 use strict;
29 use Debian::Debhelper::Dh_Lib;
30 use Debian::Debhelper::config_package;
31 use Digest::MD5;
32 use IPC::Open3;
33
34
35 =head1 SYNOPSIS
36
37 B<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
41 B<dh_configpackage> is a debhelper program to create "configuration
42 packages". These packages provide an ideal way to distribute
43 configurations to target systems while still affording local system
44 administrators a degree of control over their workstations. The
45 motivation and philosophy behind this style of packaging is described
46 in detail on the config-package-dev website. Configuration packages
47 make use of dpkg diversions and maintainer script snippets to provide
48 three primary operations: displacing, hiding, and transforming files.
49
50 The I<displace> operation consists of replacing a file on the target
51 system. The original file is renamed out of the way and diverted in the
52 dpkg database. The replacement file is then installed by the package,
53 and the config-package-dev maintainer script snippets create a symlink
54 from the original name. A common use of this is to install a wrapper
55 script for an executable.
56
57 The I<transform> operation is a special case of the displace operation.
58 At build time, a "transform script" is applied to the original source,
59 and the result is used as the replacement in the displace operation. A
60 common use of this is to change one value in a config file without
61 needing to re-type the entire config file (and risk bit-rot).
62
63 The I<hide> operation is yet another special case of the displace
64 operation, namely that there is no replacement or symlink. Instead, the
65 file is diverted to a unique path on the target system, thus preserving
66 its contents. A common use of this is to suppress a snippet file in a
67 configuration directory (e.g. /etc/foo.d), thus disabling a specific
68 operation or configuration.
69
70 The I<displace extension> is a suffix appended to the diverted versions
71 of files, and this suffix plus the string "-orig" is appended to the
72 original versions of the files. The default value is the first word of
73 the package name. For example, the extension for debathena-bin-example
74 would be ".debathena". So if debathena-bin-example displaced /bin/true,
75 the original /bin/true would be found at /bin/true.debathena-orig and
76 the 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
79 the displace extension.)
80
81 =head1 FILES
82
83 =over 4
84
85 =item debian/I<package>.displace
86
87 List the files to displace, one per line, including the full path and
88 displace extension. For example, to displace /usr/bin/true to
89 /usr/bin/true.debathena, you would list "/usr/bin/true.debathena" in
90 the file. (As with other Debhelper commands, you can omit the initial
91 leading slash in pathnames in the package, but these examples retain
92 it.)
93
94 =item debian/I<package>.hide
95
96 List the files to hide, one per line, including the full path and
97 displace extension. As noted above, these files won't actually be
98 removed, but merely diverted and renamed to a unique path below
99 /usr/share/I<package>.
100
101 =item debian/I<package>.undisplace
102
103 List the files to undisplace, one per line, including the full path and
104 displace extension. B<NOTE:> This is only needed when a new version of
105 the package no longer needs to displace a file (for example, if an
106 upstream bug was fixed). Packages automatically undo all operations
107 upon removal or deconfiguration.
108
109 =item debian/I<package>.unhide
110
111 List the files to unhide, one per line, including the full path
112 and displace extension. B<NOTE:> As with undisplace, this is only needed
113 when a new version of the package no longer needs to hide a file.
114
115 =item debian/I<package>.transform
116
117 Each line in the file specifies a transformation. A transformation
118 consists of two space-separated fields: the full path of the
119 target file including the displace extension and the transformation
120 command itself. The transformation can either be a single shell
121 command, or an executable file in the debian directory. The
122 transformation takes the original source of the file on stdin and prints
123 its transformation on stdout. Transformations are typically performed
124 by perl, sed, or awk, but there is no limitation on what can be used as
125 a transformation.
126
127 For example, to transform /etc/school.conf by replacing all
128 occurrences of the word 'Harvard' with the word 'MIT', you might
129 specify the following line:
130
131 /etc/school.conf.debathena sed -e 's/Harvard/MIT/g'
132
133 Or, storing the command in a separate script:
134
135 /etc/school.conf.debathena debian/transform_school.conf.pl
136
137 If the transformation script fails, the package build fails. You can use
138 this with e.g. Perl's C<or die> syntax to make sure that the source
139 file of the transformation has not changed from what you expected.
140
141 I<Transformation sources>: Under normal operation, the source (passed
142 on stdin) for the transformation is the name of the diversion without
143 the divert extension. In some cases, you may wish to use a different
144 source (e.g. a sample configuration file in /usr/share/doc). You can
145 specify this source as an optional field between the diversion
146 filename and the transformation. This field must begin with a '<'
147 immediately followed by the full path to the source. Taking the
148 example 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
152 B<NOTE:> There is no "untransform" operation. Because a transform
153 operation is a special case of a displace operation, the "undisplace"
154 operation is the correct way of removing a no-longer-needed
155 transformation in future versions of the package.
156
157 =item debian/I<package>.displace-extension
158
159 This file is used to specify the displace extension for any files
160 diverted by this package, if you do not want to accept the default of
161 the 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
172 Do not modify maintainer scripts. This is a standard debhelper
173 option, though you are strongly discouraged from using it except for
174 debugging, 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
186 These options allow for specifying an operation on the command line.
187 The argument to the option is the same as a single line of the
188 corresponding file, as described above. You may specify multiple
189 occurrences of B<--displace>, or you may invoke B<dh_configpackage>
190 repeatedly with different invocations. The most common use of this
191 format is in a rules file when performing conditional operations, in an
192 C<override_dh_configpackage> target in the C<rules> file. See the
193 debathena-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
200 my (@arg_displace, @arg_hide, @arg_undisplace, @arg_unhide, @arg_transform);
201 my $args_present = 0;
202
203 init(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
211 if (@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.
220 sub 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
237 sub 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
246 sub 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
253 sub 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
271 sub 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
339 foreach 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
438 Because the displace extension is automatically generated from the
439 package name, renaming the package can have unintended consequences.
440 If you must rename a package such that the first component of the name
441 changes, specify the old extension using the C<displace-extension> file
442 (see above).
443
444 =head1 SEE ALSO
445
446 L<debhelper(7)>, L<The config-package-dev
447 homepage|http://debathena.mit.edu/config-package-dev>
448
449 This program is a part of config-package-dev.
450
451 =head1 AUTHOR
452
453 config-package-dev was written by Anders Kaseorg <andersk@mit.edu> and
454 Tim 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