Commit | Line | Data |
---|---|---|
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 | ||
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 |