diff options
Diffstat (limited to 'scripts/perl-dpkg.pl')
-rwxr-xr-x | scripts/perl-dpkg.pl | 1482 |
1 files changed, 1482 insertions, 0 deletions
diff --git a/scripts/perl-dpkg.pl b/scripts/perl-dpkg.pl new file mode 100755 index 000000000..ba70fc5f5 --- /dev/null +++ b/scripts/perl-dpkg.pl @@ -0,0 +1,1482 @@ +#!/usr/bin/perl -- +# +# dpkg: Debian GNU/Linux package maintenance utility +# +# Copyright (C) 1994 Matt Welsh <mdw@sunsite.unc.edu> +# Copyright (C) 1994 Carl Streeter <streeter@cae.wisc.edu> +# Copyright (C) 1994 Ian Murdock <imurdock@debian.org> +# Copyright (C) 1994 Ian Jackson <iwj10@cus.cam.ac.uk> +# +# dpkg is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2, +# or (at your option) any later version. +# +# dpkg is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with dpkg; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +$version= '0.93.15'; # This line modified by Makefile + +sub version { + print STDERR <<END; +Debian GNU/Linux \`dpkg\' package handling tool version $version. +Copyright (C)1994 Matt Welsh, Carl Streeter, Ian Murdock, Ian Jackson. +This is free software; see the GNU General Public Licence version 2 +or later for copying conditions. There is NO warranty. +END +} + +sub usage { + print STDERR <<END; +Usage: dpkg -i|--install <opts> <.deb file name> ... | -a|--auto <dir> ... + dpkg --unpack <opts> <.deb file name> ... | -a|--auto <dir> ... + dpkg -A|--avail <opts> <.deb file name> ... | -a|--auto <dir> ... + dpkg --configure <opts> <package name> ... | -a|--auto + dpkg -r|--remove <opts> <package name> ... | -a|--auto + dpkg -l|--list <status select> [<regexp> ...] + dpkg -s|--status <status select> [<package-name> ...] + dpkg -S|--search <glob pattern> ... + dpkg -b|--build|-c|--contents|-e|--control|--info|-f|--field| + -x|--extract|-X|--vextract ... (see dpkg-deb --help) +Options: --purge --control-quiet --control-verbose --version --help + -R|--root=<directory> --admindir=<directory> --instdir=<directory> + --no-keep-old-conf --no-keep-new-conf -N|--no-also-select + --ignore-depends=<package name>,... + --conf-(same|diff|all)-(new|old|promptnew|promptold) + --force-<thing>,<thing>,... --no-force-...|--refuse-... +Status selections: --isok-[o][h] (OK, Hold; alternatives are y, n) + --want-[u][i][d][p] (Unknown, Install, Deinstall, Purge) + --stat-[nupircNO] (Not, Unpacked, Postinst-failed, Installed, Removal-failed, + Config-files, Not/Config-files, Not/Config-files/Installed) +Force things: conflicts, depends, downgrade, depends-version, prermfail, + configure-any, hold, extractfail + (default is --no-force everything, except --force-downgrade) +Use \`$dselect\' for user-friendly package management. +END +} + +$instroot= ''; +$controlwarn = 1; +$estatus = 0; +$filename_pattern = "*.deb"; +%force= ( 'conflicts',0, 'depends',0, 'depends-version',0, 'downgrade',1, + 'prermfail',0, 'postrmfail',0, 'hold',0, 'configure-any',0, + 'extractfail',0 ); + +%selectmap_h= ('o','ok', 'h','hold', 'y','ok', 'n','hold'); +%selectmap_w= ('u', 'unknown', 'i', 'install', 'd', 'deinstall', 'p', 'purge'); +%selectmap_s= ('n', 'not-installed', + 'u', 'unpacked', + 'p', 'postinst-failed', + 'i', 'installed', + 'r', 'removal-failed', + 'c', 'config-files', + 'n', 'not-installed,config-files', + 'o', 'not-installed,config-files,installed'); +%selectthings= ('isok','h', 'want','w', 'stat','s'); + +require 'lib.pl'; # This line modified by Makefile +$0 =~ m|[^/]+$|; $name = $dpkg; +$|=1; +umask(022); + +$action= 'none'; + +%myabbrevact= ('i','install', 'r','remove', 'A','avail', + 'S','search', 'l','list', 's','status'); + +# $conf...[0] corresponds to `same', 1 to diff +$confusenew[0]= 0; $confprompt[0]= 0; +$confusenew[1]= 1; $confprompt[1]= 1; +# Ie, default is to prompt only when hashes differ, +# and to use new when hashes differ + +while ($ARGV[0] =~ m/^-/) { + $_= shift(@ARGV); + $noptsdone++; + if (m/^--$/) { + $noptsdone--; last; + } elsif (m/^--(install|remove|unpack|configure|avail|list|status)$/) { + &setaction($1); + } elsif (m/^--(build|contents|control|info|field|extract|vextract)$/) { + $noptsdone--; &backend($1); + } elsif (m/^--ignore-depends=($packagere(,$packagere)*)$/o) { + grep($ignore_depends{$_}=1, split(/,/,$1)); + } elsif (m/^--(force|no-force|refuse)-/) { + $fvalue= ($1 eq 'force'); + for $fv (split(/,/,$')) { + defined($force{$fv}) || &badusage("unknown --force option \`$fv'"); + $force{$fv}= $fvalue; + } + } elsif (m/^--conf-(same|diff|all)-(new|old|promptnew|promptold)$/) { + $new= $2 eq 'new' || $2 eq 'promptnew'; + $prompt= $2 eq 'promptnew' || $2 eq 'promptold'; + if ($1 ne 'same') { $confusenew[1]= $new; $confprompt[1]= $prompt; } + if ($1 ne 'diff') { $confusenew[0]= $new; $confprompt[0]= $prompt; } + } elsif (m/^--(\w+)-(\w+)$/ && defined($selectthings{$1})) { + $thisname= $1; + $thisthing= $selectthings{$thisname}; + $_=$2; + eval '%thismap= %selectmap_'.$thisthing; + while (s/^.//) { + if (!defined($thismap{$&})) { + &badusage("unknown status letter $& for status field $thisname"); + } + $thiscodes= $thismap{$&}; + $selectdo.= "undef \$select_$thisthing;"; + for $v (split(m/,/, $thiscodes)) { + $selectdo .= "\$select_$thisthing{'$v'}=1;"; + } + } + } elsif (m/^--root=/) { + $instroot=$'; &setadmindir("$instroot/$orgadmindir"); + } elsif (m/^--admindir=/) { + &setadmindir("$'"); + } elsif (m/^--instdir=/) { + $instroot=$'; + } elsif (m/^--auto$/) { + $auto= 1; + } elsif (m/^--purge$/) { + $purge= 1; + } elsif (m/^--skip-same-version$/) { + print STDERR + "Warning: dpkg --skip-same-version not implemented, will process\n". + " process even packages the same version of which is installed.\n"; + } elsif (m/^--no-also-select$/) { + $noalsoselect= 1; + } elsif (m/^--control-verbose$/) { + $controlwarn= 1; + } elsif (m/^--control-quiet$/) { + $controlwarn= 0; + } elsif (m/^--no-keep-old-conf$/) { + $nokeepold= 1; + } elsif (m/^--no-keep-new-conf$/) { + $nokeepnew= 1; + } elsif (m/^--succinct-prompts$/) { + $succinct= 1; + } elsif (m/^--debug$/) { + $debug= 1; + } elsif (m/^--help$/) { + &usage; exit(0); + } elsif (m/^--version$/) { + &version; exit(0); + } elsif (m/^--/) { + &badusage("unknown option \`$_'"); + } else { + s/^-//; $noptsdone--; + while (s/^.//) { + $noptsdone++; + if (defined($myabbrevact{$&})) { + &setaction($myabbrevact{$&}); + } elsif (defined($debabbrevact{$&})) { + $noptsdone--; &backend($debabbrevact{$&}); + } elsif ($& eq 'a') { + $auto= 1; + } elsif ($& eq 'D') { + $debug= 1; + } elsif ($& eq 'N') { + $noautoselect= 1; + } elsif ($& eq 'R') { + s/^=// || &badusage("missing value for -R=<dir> option"); + $instroot= $_; &setadmindir("$instroot/$orgadmindir"); $_=''; + } else { + &badusage("unknown option \`-$&'"); + } + } + } +} + +$action eq 'none' && &badusage("an action must be specified"); + +&debug("arguments parsed"); + +#*** list, status or search - the nonactive operations ***# + +if ($action eq 'list' || $action eq 'status') { + &database_start; + if ($action eq 'list' || !@ARGV) { + &selectall(*selectmap_h,*select_h); + &selectall(*selectmap_w,*select_w); + &selectall(*selectmap_s,*select_s); + if (@ARGV) { $select_s{'not-installed'}=0; } + } + $ecode= 0; + if ($action eq 'status') { + for ($i=0;$i<=$#keysortorder;$i++) { + $keysortorder{$keysortorder[$i]}= sprintf("%6d ",$i); +# &debug("set $i: $keysortorder[$i], sortorder ". +# "\`$keysortorder{$keysortorder[$i]}'"); + } + @ARGV= &applyselcrit(sort keys %st_p21) unless @ARGV; + for $p (@ARGV) { + if (!$st_p21{$p}) { + print(STDERR "$name: no information available about package $p\n") + || &bombout("writing to stderr: $!"); + $ecode= 1; + } + print("Package: $p\n", + "Status: $st_p2w{$p} $st_p2h{$p} $st_p2s{$p}\n") || &outerr; + for $k (sort { $keysortorder{$a}.$a cmp $keysortorder{$b}.$b; } + keys %all_k21) { +# &debug("field $k, sortorder \`$keysortorder{$k}'"); + next unless defined($st_pk2v{$p,$k}); + $v= $st_pk2v{$p,$k}; next unless length($v); + if ($k eq 'conffiles' || $k eq 'list') { + $v= sprintf("(%d files, not listed)", + scalar(grep(m/\S/, split(/\n/,$v)))); + } + print("$k: $v\n") || &outerr; + } + if (defined($av_p21{$p})) { + print("\n\`Available' version of package $p, where different:\n") + || &outerr; + for $k (keys %all_k21) { + next unless defined($av_pk2v{$p,$k}); + $v= $st_pk2v{$p,$k}; next unless length($v); + $u= $st_pk2v{$p,$k}; next if $u eq $v; + print("$k: $v\n") || &outerr; + } + print("\n") || &outerr; + } + } + } else { # $action eq 'list' + $listhead=0; + if (@ARGV) { + for $r (@ARGV) { + &listinfo(&applyselcrit(sort grep(m/$r/,keys %st_p21))); + } + } else { + undef $r; + &listinfo(&applyselcrit(sort keys %st_p21)); + } + } + &database_finish; + exit($ecode); +} + +sub listinfo { + if (!@_) { + print(STDERR + defined($r) ? + "No selected packages found matching regexp \`$r'.\n" : + "No packages matched selection criteria.\n") || + &bombout("writing to stderr: $!"); + return; + } + + if (!$listhead) { + print <<END +Err? Name Version Rev Description +| Status=Not/Unpacked/Postinst-failed/Installed/Removal-failed/Config-files +|/ Desired=Unknown/Install/Deinstall/Purge +||/ | | | | ++++-============-==========-===-=============================================== +END + || &outerr; + $listhead= 1; + } + for $p (@_) { + $des= $st_pk2v{$p,'description'}; + $des= $` if $des =~ m/\n/; + printf("%s%.1s%.1s %-12.12s %-10.10s %-3.3s %-47.47s\n", + $st_p2h{$p} eq 'hold' ? 'x' : ' ', $st_p2s{$p}, $st_p2w{$p}, + $p, $st_pk2v{$p,'version'}, $st_pk2v{$p,'package_revision'}, + $des); + } +} + +sub applyselcrit { + &debug("sel from @_"); + for $f (@_) { &debug("$f :$st_p2s{$f},$select_s{$st_p2s{$f}}:$st_p2w{$f},$select_w{$st_p2w{$f}}:$st_p2h{$f},$select_h{$st_p2h{$f}}:"); } + @ascr= grep($select_s{$st_p2s{$_}} && + $select_w{$st_p2w{$_}} && + $select_h{$st_p2h{$_}}, + @_); + &debug("sel gave @ascr"); + @ascr; +} + +sub selectall { + local (*map, *sel) = @_; + local ($v); + for $v (values %map) { + next if m/,/; + $sel{$v}=1; + } +} + +if ($action eq 'search') { + @ARGV || &badusage("need at least one glob pattern for --$action"); + &database_start; + while (@ARGV) { + $orgpat= $_= shift(@ARGV); + s/\W/\\$&/g; + s|\\\*\\\*|.*|g; + s|\\\*|[^/]*|g; + s|\\\?|[^/]|g; + $pat= $_; $f=0; + for $p (sort keys %st_p21) { + $s= $st_p2s{$p}; + next if $s eq 'not-installed' || $s eq 'config-files'; + &filesinpackage($arg, $package); + @ilist= grep(m/^$pat$/,@ilist); + next unless @ilist; + $f=1; + for $_ (@ilist) { print("$p: $_\n") || &outerr; } + } + if (!$f) { + print(STDERR "No packages found containing \`$orgpat'.\n") + || &bombout("writing to stderr: $!"); + $ecode= 1; + } + } + &database_finish; + exit($ecode); +} + +#*** lock and read in databases ***# + +&database_start; +&debug("databases read"); + +#*** derive argument list for --auto ***# + +if ($auto) { + if ($action eq 'install' || $action eq 'unpack' || $action eq 'avail') { + @ARGV || &badusage("need at least one directory for --$action --auto"); + pipe(RP,WP) || &bombout("create pipe for \`find': $!"); + defined($c= fork) || &bombout("fork for \`find': $!"); + if (!$c) { + close(RP); open(STDOUT,">& WP"); close(WP); + exec('find',@ARGV,'-name',$filename_pattern,'-type','f','-print0'); + die "$name: could not exec \`find': $!"; + } + close(WP); + $/="\0"; @ARGV= <RP>; $/="\n"; + eof || &bombout("read filenames from \`find': $!"); + close(RP); + $!=0; waitpid($c,0) eq $c || &bombout("wait for \`find' failed: $!"); + $? && &bombout("\`find' process returned error code ".&ecode); + @ARGV || &bombout("no packages found to $action"); + } else { + @ARGV && &badusage("no package names may be specified with --$action --auto"); + if ($action eq 'remove') { + eval 'sub condition { + $wants eq "deinstall" || $wants eq "purge" || return 0; + $cstatus eq "not-installed" && return 0; + $cstatus eq "config-files" && $wants eq "deinstall" && return 0; + return 1; + } 1;' || &internalerr("sub condition: $@"); + } elsif ($action eq 'configure') { + eval 'sub condition { + $wants eq "install" || return 0; + $cstatus eq "unpacked" || $cstatus eq "postinst-failed" || return 0; + return 1; + } 1;' || &internalerr("sub condition: $@"); + } else { + &internalerr("unknown auto nonames action $action"); + } + for $p (keys %st_p21) { + next if $st_p2h{$p} eq 'hold'; + $wants= $st_p2w{$p}; $cstatus= $st_p2s{$p}; + next unless &condition; + push(@ARGV,$p); + } + } + &debug("auto: arglist @ARGV"); +} else { + @ARGV || &badusage("need a list of packages or filenames"); +} + +if ($action eq 'install' || $action eq 'unpack') { + grep(s:^[^/.]:./$&:, @ARGV); # Sanitise filenames +} + +&debug("action: $action; arglist @ARGV"); + +#*** actually do things ***# + +for $arg (@ARGV) { + $package= ''; @undo=(); + &debug("&do_$action($arg)"); + if (!eval "&do_$action(\$arg); 1;") { &handleerror || last; } + &checkpointstatus; +} +&checkpointstatus; + +if (!$abort) { + &debug("&middle_$action($arg)"); + if (!eval "&middle_$action; 1;") { print STDERR $@; $abort=1; } +} +&checkpointstatus; + +if (!$abort) { + while (@deferred) { + $arg= shift(@deferred); $package= ''; @undo=(); + &debug("&deferred_$action($arg) ($dependtry: $sincenothing)"); + if (!eval "&deferred_$action(\$arg); 1;") { &handleerror || last; } + &checkpointstatus; + } + &checkpointstatus; +} + +if ($errors) { + print STDERR "$name: $errors errors occurred.\n"; + $estatus= 1; +} + +&database_finish; +&cleanup; + +exit($estatus); + +#*** useful subroutines for main control section ***# + +sub handleerror { + print STDERR $@; + if (length($package) && defined($st_p21{$package})) { + $st_p2h{$package}='hold'; &amended_status($package); + } + $errors++; + if ($errors >20) { print STDERR "$name: too many errors, halting\n"; return 0; } + return !$abort; +} + +sub checkpointstatus { + return unless keys %statusupdated; + &amended_status(keys %statusupdated); + undef %statusupdated; +} + +sub backend { + &setaction(''); + ($noptsdone) && &badusage("action \`$_[0]' must be first argument"); + &debug("backend --$_[0]"); + exec($backend, "--$_[0]", @ARGV); + &bombout("unable to run $backend: $!"); +} + +sub setaction { + $action eq 'none' || &badusage("conflicting actions \`$action' and \`$1'"); + $action= $_[0]; +} + +#*** error handlers for use in actions ***# + +sub warn { warn "$name - warning: @_\n"; } +sub subcriterr { warn "$name - subcritical error: @_\n"; $estatus=1; } +sub error { &acleanup; die "$name - error: @_\n"; } +sub internalerr { &acleanup; die "$name - internal error, please report: @_\n"; } +sub fatalerr { &acleanup; die "$name - fatal error, halting: @_\n"; $abort=1; } + +sub corruptingerr { + local ($corruptingerr)= @_; + &acleanup; + die "$name - horrible error: $corruptingerr;\n". + "Package manager data is now out of step with installed system.\n". + "Please re-install \`$package' to ensure system consistency!\n". + "(Seek assistance from an expert if problems persist.)\n"; + $abort=1; +} + +sub forcibleerr { + local ($msg,@forces) = @_; + if (@forces= grep($force{$_},@forces)) { + &warn("$msg (proceeding due to --force-$forces[0])"); + } else { + &error("$msg (skipping this package)"); + } +} + +sub acleanup { + while (@undo) { + eval(pop(@undo)); + $@ && print STDERR "error while cleaning up: $@"; + } +} + +#*** --install ***# + +sub do_install { + &do_unpack($arg); + $arg= $package; + &do_configure($arg); +} + +sub deferred_install { &deferred_configure; } + +sub middle_install { &middle_configure } + +#*** --avail ***# + +sub do_avail { + unlink($controli); + if ($! != &ENOENT) { + system('rm','-rf',$controli); + unlink($controli); + $! == &ENOENT || &fatalerr("unable to get rid of $controli: $!"); + } + &debug("extract control $backend --control $arg $controli"); + $!=0; system($backend,"--control",$arg,$controli); + $? && &error("$arg: could not extract control information (".&ecode.")"); + open(CONTROL,"$controli/control") || + &error("$arg: corrupt - unable to read control file: $!"); + &parse_control("$arg"); + for $k (keys %cf_k2v) { + $av_pk2v{$p,$k}= $cf_k2v{$k}; + } + for $k (@nokeepfields) { + delete $av_pk2v{$p,$k} unless defined($cf_k2v{$k}); + } + &amended_available($p); + $package=$p; +} + +sub deferred_avail { } +sub middle_avail { } + +#*** --unpack ***# + +sub middle_unpack { } + +sub do_unpack { + &do_avail; + $cstatus= $st_p2s{$package}; + if ($st_p2w{$package} ne 'install') { + if (!$noalsoselect) { + $st_p2w{$package}= 'install'; $statusupdated{$package}= 1; + print STDOUT "Selecting previously deselected package $package.\n"; + } else { + print STDOUT "Skipping deselected package $package.\n"; + return; + } + } + for $tp (split(/,/, $av_pk2v{$package,'conflicts'})) { + $tp =~ s/^\s*//; $tp =~ s/\s+$//; + ($tps, $rightver, $inst, $want, $tpp)= &installationstatus($tp); + unless ($tps eq 'not-installed' || $tps eq 'config-files' || !$rightver) { + &forcibleerr("$arg: conflicts with package $tpp ($want),". + " found $inst on system", + 'conflicts'); + } + } + if ($cstatus eq 'installed') { + if (&compare_verrevs($av_pk2v{$package,'version'}, + $av_pk2v{$package,'package_revision'}, + $st_k2v{'version'},$st_k2v{'package_revision'}) <0) { + &forcibleerr("$arg: downgrading installed $package version ". + "$st_k2v{'version'}, ". + "package revision $st_k2v{'package_revision'} ". + "to older version ". + "$av_pk2v{$package,'version'}, ". + "package revision $av_pk2v{$package,'package_revision'}", + 'downgrade'); + } + } + if (open(CONF,"$controli/conffiles")) { + @configf= <CONF>; + eof || &error("$arg: unable to read $controli/conffiles: $!"); + close(CONF); + grep((chop, m,^/, || s,^,/,), @configf); + } elsif ($! != &ENOENT) { + &error("$arg: cannot get config files list: $!"); + } else { + @configf= (); + } + + if ($cstatus eq 'installed' || $cstatus eq 'unpacked' || + $cstatus eq 'postinst-failed' || $cstatus eq 'removal-failed') { + &filesinpackage($arg,$package); + print STDOUT "Preparing to replace $package ...\n"; + } + if ($cstatus eq 'installed') { + if (!eval { + &run_script_ne("$scriptsdir/$package.prerm", 'old pre-removal script', + 'upgrade', + $av_pk2v{$package,'version'}.'-'. + $av_pk2v{$package,'package_revision'}); + 1; + }) { + &warn("$@... trying script from new package instead."); + &run_script("$controli/prerm", 'new prerm script', + 'failed-upgrade', + $st_k2v{'version'}.'-'.$st_k2v{'package_revision'}); + } + push(@undo, + '$st_p2s{$package}= "postinst-failed"; $statusupdated{$package}=1; + &run_script_ne("$scriptsdir/$package.postinst", + "old post-installation script", + "abort-upgrade", + $av_pk2v{$package,"version"}."-". + $av_pk2v{$package,"package_revision"}); + $st_p2s{$package}= "installed"; $statusupdated{$package}=1;'); + } + @fbackups=(); + if ($cstatus eq 'installed' || $cstatus eq 'unpacked' || + $cstatus eq 'postinst-failed' || $cstatus eq 'removal-failed') { + for ($i=0; $i<=$#ilist; $i++) { + next if grep($_ eq $ilist[$i], @configf); + $_= $ilist[$i]; + unless (lstat("$instroot/$_")) { + $! == &ENOENT || &error("old file $_ unstattable: $!"); + next; + } + next if -d _; + rename("$instroot/$_","$instroot/$_.dpkg-tmp") || + &error("couldn't rename old file $_ to $_.dpkg-tmp: $!"); + push(@undo, + '$_=pop(@fbackups); rename("$instroot/$_.dpkg-tmp","$instroot/$_") || + die "unable to undo rename of $_ to $_.dpkg-tmp: $!"'); + push(@fbackups, $_); + } + if (!eval { + &run_script_ne("$scriptsdir/$package.postrm", 'old post-removal script', + 'upgrade', + $av_pk2v{$package,'version'}.'-'. + $av_pk2v{$package,'package_revision'}); + 1; + }) { + &warn("$@... trying script from new package instead."); + &run_script("$controli/postrm", 'new post-removal script', + 'failed-upgrade', + $st_k2v{'version'}.'-'.$st_k2v{'package_revision'}); + } + push(@undo, + '&run_script_ne("$scriptsdir/$package.preinst", + "old pre-installation script", + "abort-upgrade", + $av_pk2v{$package,"version"}. + "-".$av_pk2v{$package,"package_revision"})'); + } + $shortarg= $arg; $shortarg =~ s:.{15,}/:.../:; + print STDOUT "Unpacking $arg, containing $package ...\n"; + &run_script("$controli/preinst", 'pre-installation script', + 'upgrade', $st_k2v{'version'}.'-'.$st_k2v{'package_revision'}); + push(@undo,'&run_script_ne("$controli/postrm", "post-removal script", + "abort-upgrade", + $st_k2v{"version"}."-".$st_k2v{"package_revision"})'); + if ($cstatus ne 'not-installed') { + for $_ (split(/\n/,$st_pk2v{$package,'conffiles'})) { + s/^ //; next unless length($_); + if (!m/^(\S+) (-|newconffile|nonexistent|[0-9a-f]{32})$/) { + &warn("$arg: ignoring bad stuff in old conffiles field \`$_'"); + next; + } + $oldhash{$1}= $2; + } + } + for $f (@configf) { + $drf= &conffderef($f); if (!defined($drf)) { next; } + if (lstat("$instroot/$drf.dpkg-tmp")) { + $undo=1; + } else { + $! == &ENOENT || &error("unable to stat backup config file $_.dpkg-tmp: $!"); + if (lstat("$instroot/$drf")) { + rename("$instroot/$drf","$instroot/$drf.dpkg-tmp") || + &error("couldn't back up config file $f (= $drf): $!"); + $undo=1; + } elsif ($! == &ENOENT) { + $undo=0; + } else { + &error("unable to stat config file $drf: $!"); + } + } + if ($undo) { + push(@undo, + '$_=pop(@undof); rename("$instroot/$_.dpkg-tmp","$instroot/$_") || + die "unable to undo backup of config file $_: $!"'); + push(@undof, $drf); + } + } + + open(NL,">$listsdir/$package.list.new") || + &error("$package: cannot create $listsdir/$package.list.new: $!"); + defined($c= fork) || &error("$package: cannot pipe/fork for $backend --vextract"); + if (!$c) { + if (!open(STDOUT,">&NL")) { + print STDERR "$name: cannot redirect stdout: $!\n"; exit(1); + } + $vexroot= length($instroot) ? $instroot : '/'; + exec($backend,"--vextract",$arg,$vexroot); + print STDERR "$name: cannot exec $backend --vextract $arg $vexroot: $!\n"; + exit(1); + } + $!=0; waitpid($c,0) == $c || &error("could not wait for $backend: $!"); + $? && &forcibleerr("$package: failed to install (".&ecode.")", 'extractfail'); + + rename("$listsdir/$package.list.new","$listsdir/$package.list") || + &error("$package: failed to install new $listsdir/$package.list: $!"); + + $newconff=''; + for $f (@configf) { + $h= $oldhash{$f}; + $h='newconffile' unless length($h); + $newconff.= "\n $f $h"; + &debug("new hash, after unpack, of $f, is $h"); + } + + for $k (keys %all_k21) { + next if $k eq 'binary' || $k eq 'source' || $k eq 'section'; + $st_pk2v{$package,$k}= $av_pk2v{$package,$k}; + } + $st_pk2v{$package,'conffiles'}= $newconff; $all_k21{'conffiles'}= 1; + $st_p2s{$package}= 'unpacked'; $st_p2h{$package}= 'ok'; $st_p21{$package}= 1; + $statusupdated{$package}= 1; + @undo=(); @undof=(); + + for $f (@fbackups) { + unlink("$instroot/$f.dpkg-tmp") || $! == &ENOENT || + &subcriterr("$package: unable to delete saved old file $f.dpkg-tmp: $!\n"); + } + + undef %fordeletion; + opendir(PI,"$scriptsdir") || + &corruptingerr("$package: unable to read $scriptsdir directory ($!)"); + while(defined($_=readdir(PI))) { + next unless substr($_,0,length($package)+1) eq $package.'.'; + $fordeletion{$_}= 1; + } + closedir(PI); + opendir(PI,"$controli") || + &corruptingerr("$package: unable to read $controli". + " new package control information directory ($!)"); + $fordeletion{"$package.list"}= 0; + while(defined($_=readdir(PI))) { + next if m/^\.\.?$/ || m/^conffiles$/ || m/^control$/; + rename("$controli/$_","$scriptsdir/$package.$_") || + &corruptingerr("$package: unable to install new package control". + " information file $scriptsdir/$package.$_ ($!)"); + $fordeletion{"$package.$_"}= 0; + } + closedir(PI); + for $_ (keys %fordeletion) { + next unless $fordeletion{$_}; + unlink("$scriptsdir/$_") || + &corruptingerr("$package: unable to remove old package script". + " $scriptsdir/$_ ($!)"); + } +} + +#*** --configure ***# + +sub do_configure { + $package=$arg; $cstatus= $st_p2s{$package}; + if (!defined($st_p21{$package})) { $cstatus= 'not-installed'; } + unless ($cstatus eq 'unpacked' || $cstatus eq 'postinst-failed') { + if ($cstatus eq 'not-installed') { + &error("no package named \`$package' is installed, cannot configure"); + } else { + &error("$package: is currently in state \`$cstatus', cannot configure"); + } + } + push(@deferred,$package); +} + +sub middle_configure { + $sincenothing=0; $dependtry=1; +} + +sub deferred_configure { + # The algorithm for deciding what to configure first is as follows: + # Loop through all packages doing a `try 1' until we've been round + # and nothing has been done, then do `try 2' and `try 3' likewise. + # Try 1: + # Are all dependencies of this package done ? If so, do it. + # Are any of the dependencies missing or the wrong version ? + # If so, abort (unless --force-depends, in which case defer) + # Will we need to configure a package we weren't given as an + # argument ? If so, abort - except if --force-configure-any, + # in which case we add the package to the argument list. + # If none of the above, defer the package. + # Try 2: + # Find a cycle and break it (see above). + # Do as for try 1. + # Try 3 (only if --force-depends-version). + # Same as for try 2, but don't mind version number in dependencies. + # Try 4 (only if --force-depends). + # Do anyway. + $package= $arg; + if ($sincenothing++ > $#deferred*2+2) { + $dependtry++; $sincenothing=0; + &internalerr("$package: nothing configured, but try was already 4 !") + if $dependtry > 4; + } + if ($dependtry > 1) { &findbreakcycle($package); } + ($ok, @aemsgs) = &dependencies_ok($package,''); + if ($ok == 1) { + push(@deferred,$package); return; + } elsif ($ok == 0) { + $sincenothing= 0; + &error("$arg: dependency problems - not configuring this package:\n ". + join("\n ",@aemsgs)); + } elsif (@aemsgs) { + &warn("$arg: dependency problems, configuring anyway as you request:\n ". + join("\n ",@aemsgs)); + } + $sincenothing= 0; + print STDOUT "Setting up $package ...\n"; + if ($st_p2s{$package} eq 'unpacked') { + &debug("conffiles updating >$st_pk2v{$package,'conffiles'}<"); + undef %oldhash; @configf=(); + for $_ (split(/\n/,$st_pk2v{$package,'conffiles'})) { + s/^ //; next unless length($_); + if (!m/^(\S+) (-|newconffile|nonexistent|[0-9a-f]{32})$/) { + &warn("$arg: ignoring bad stuff in old conffiles field \`$_'"); + next; + } + $oldhash{$1}= $2; push(@configf,$1); + &debug("old hash of $1 is $2"); + } + undef %newhash; + for $file (@configf) { + $drf= &conffderef($file); + if (!defined($drf)) { $newhash{$file}= '-'; next; } + $newhash{$file}= &hash("$instroot/$drf"); + &debug("new hash of $file is $newhash{$file} (old $oldhash{$file})"); + if ($oldhash{$file} eq 'newconffile') { + $usenew= 1; + } else { + if (!&files_not_identical("$instroot/$drf", + "$instroot/$drf.dpkg-tmp")) { + rename("$instroot/$drf.dpkg-tmp",$drf) || $!==&ENOENT || + &error("$package: unable to reinstall ". + "existing conffile $drf.dpkg-tmp: $!"); + &debug("files identical $file"); + } else { + $diff= $newhash{$file} ne $oldhash{$file}; + $usenew= $confusenew[$diff]; + &debug("the decision - diff $diff;". + " usenew $usenew prompt $confpromt[$diff]"); + if ($confprompt[$diff]) { + $symlinked = $drf eq $file ? '' : + $succinct ? " (-> $drf)" : + " (which is a symlink to $drf)"; + for (;;) { + print + $succinct ? " +Package $package, file $file$symlinked, ".($diff ? "CHANGED": "not changed") + : $diff ? " +In package $package, distributed version of configuration +file $file$symlinked has changed +since the last time it was installed. You may: + * Install the new version and edit it later to reflect your wishes. + ". ($nokeepold ? + "(Your old version will not be saved.)" : + "(Your old version will be saved in $drf.dpkg-old.)") . " + * Leave your old version in place, and perhaps check later that + you don't want to update it to take account of new features. + ". ($nokeepnew ? + "(The new version be discarded.)" : + "(The new version will be placed in $drf.dpkg-new.)") + : " +Package $package contains the same distributed version of +configuration file $file$symlinked +as the last time it was installed. You may: + * Install the distributed version, overwriting your changes. + ". ($nokeepold ? + "(Your changed version will not be saved.)" : + "(Your changed version will be saved in $drf.dpkg-old.)") . " + * Leave your own version in place. + ". ($nokeepnew ? + "(The distributed version be discarded.)" : + "(The distributed version will be placed in $drf.dpkg-new.)"); + + print " +$file: install new version ? (y/n, default=". ($usenew?'y':'n'). ") "; + + $!=0; defined($iread= <STDIN>) || + &error("$package: prompting, EOF/error on stdin: $!"); + $_= $iread; s/^\s*//; s/\s+$//; + ($usenew=0, last) if m/^n(o)?$/i; + ($usenew=1, last) if m/^y(es)?$/i; + last if m/^$/; + print "\nPlease answer \`y' or \`n'.\n"; + } + } + &debug("decided, usenew $usenew"); + if ($usenew) { + ©perm("$drf.dpkg-tmp",$drf,$drf); + if ($nokeepold) { + unlink("$instroot/$drf.dpkg-tmp") || $!==&ENOENT || + &error("$package: unable to delete old conffile ". + "$drf.dpkg-tmp: $!"); + unlink("$instroot/$drf.dpkg-old") || $!==&ENOENT || + &error("$package: unable to delete very old ". + "conffile $drf.dpkg-old: $!"); + } else { + rename("$instroot/$drf.dpkg-tmp","$instroot/$drf.dpkg-old") + || $!==&ENOENT || + &error("$package: unable to back up old conffile ". + "$drf.dpkg-tmp as $drf.dpkg-old: $!"); + } + } else { + unlink("$instroot/$drf.dpkg-new") || $!==&ENOENT || + &error("$package: unable to delete old new conffile ". + "$drf.dpkg-new: $!"); + if (!$nokeepnew) { + link("$instroot/$drf","$instroot/$drf.dpkg-new") + || $!==&ENOENT || + &error("$package: unable save new conffile ". + "$drf as $drf.dpkg-new: $!"); + } + if (!rename("$instroot/$drf.dpkg-tmp","$instroot/$drf")) { + $!==&ENOENT || &error("$package: unable reinstall old ". + "conffile $drf.dpkg-tmp as $drf: $!"); + unlink("$instroot/$drf"); + } + } + } + } + } + $newconff=''; + for $f (@configf) { + $h= $newhash{$f}; $newconff.= "\n $f $h"; + } + $st_pk2v{$package,'conffiles'}= $newconff; $all_k21{'conffiles'}= 1; + } + $st_p2s{$package}= 'postinst-failed'; $statusupdated{$package}= 1; + &run_script("$scriptsdir/$package.postinst", + 'post-installation script', 'configure'); + $st_p2s{$package}= 'installed'; + $st_p2h{$package}= 'ok'; $statusupdated{$package}= 1; +} + +#*** --remove ***# + +sub do_remove { + $package=$arg; $cstatus= $st_p2s{$package}; + if (!defined($st_p21{$package}) || + $cstatus eq 'not-installed' || + !$purge && $cstatus eq 'config-files') { + &error("$package: is not installed, cannot remove"); + } + push(@deferred,$package); + if (!$auto) { + $ns= $purge ? 'purge' : 'deinstall'; + if ($st_p2w{$package} ne $ns) { + $st_p2w{$package}= $ns; $statusupdated{$package}= 1; + } + } +} + +sub middle_remove { + $sincenothing=0; $dependtry=1; + for $p (keys %st_p2s) { + $cstatus= $st_p2s{$p}; + next unless $cstatus eq 'installed'; + for $tp (split(/[\|,]/, $st_pk2v{$p,'depends'})) { + $tp =~ s/\s*\(.+\)\s*$//; $tp =~ s/^\s*//; $tp =~ s/\s+$//; + $tp =~ m/^$packagere$/o || + &internalerr("package $p dependency $tp didn't match re"); + $depended{$tp}.= "$p "; + } + } +} + +sub deferred_remove { + $package= $arg; + if ($sincenothing++ > $#deferred*2+2) { + $dependtry++; $sincenothing=0; + &internalerr("$package: nothing configured, but try was already 4 !") + if $dependtry > 4; + } + @raemsgs= (); $rok= 2; + &debug("$package may be depended on by any of >$depended{$package}<"); + for $fp (split(/ /, $depended{$package})) { + next if $fp eq '' || $ignore_depends{$tp}; + $is= $st_p2s{$fp}; + next if $is eq 'not-installed' || $is eq 'unpacked' || + $is eq 'removal-failed' || $is eq 'config-files'; + if ($dependtry > 1) { &findbreakcycle($fp); } + ($ok, @aemsgs) = &dependencies_ok($fp,$package); + if ($rok != 1) { push(@raemsgs,@aemsgs); } + $rok= $ok if $ok < $rok; + } + if ($rok == 1) { + push(@deferred,$package); return; + } elsif ($rok == 0) { + $sincenothing= 0; + &error("$arg: dependency problems - not removing this package:\n ". + join("\n ",@raemsgs)); + } elsif (@raemsgs) { + &warn("$arg: dependency problems, removing anyway as you request:\n ". + join("\n ",@raemsgs)); + } + $sincenothing= 0; + &filesinpackage($arg,$package); + + undef %hash; @configfr= @configf= (); + for $_ (split(/\n/,$st_pk2v{$package,'conffiles'})) { + s/^ //; next unless length($_); + if (!m/^(\S+) (-|newconffile|nonexistent|[0-9a-f]{32})$/) { + &warn("$arg: ignoring bad stuff in old conffiles field \`$_'"); + next; + } + unshift(@configfr,$1); push(@configf,$1); + $hash{$1}= $2; + } + + if ($st_p2s{$package} ne 'config-files') { + print STDOUT "Removing $package ...\n"; + &run_script("$scriptsdir/$package.prerm", 'pre-removal script', 'remove'); + $st_p2s{$package}= 'removal-failed'; $statusupdated{$package}= 1; + for $file (reverse @ilist) { + next if grep($_ eq $file, @configf); + unlink("$instroot/$file.dpkg-tmp") || $! == &ENOENT || + &error("$arg: cannot remove supposed old temp file $file: $!"); + next if unlink("$instroot/$file"); + next if $! == &ENOENT; + &error("$arg: cannot remove file $file: $!") unless $! == &EISDIR; + next if rmdir("$instroot/$file"); + &error("$arg: cannot remove directory $file: $!") unless $! == &ENOTEMPTY; + } + &run_script("$scriptsdir/$package.postrm", 'post-removal script', 'remove'); + opendir(DSD,"$scriptsdir") || + &error("$arg: cannot read directory $scriptsdir: $!"); + for $_ (readdir(DSD)) { + next unless m/\.[^.]$/; + next if $& eq '.postrm' || $& eq '.list'; + # need postrm for --purge, and list has to go last in case it + # goes wrong + next unless $` eq $package; + unlink("$scriptsdir/$_") || + &error("$arg: unable to delete control information $scriptsdir/$_: $!"); + } + closedir(DSD); + $st_p2s{$package}= 'config-files'; + $statusupdated{$package}= 1; + } + if ($purge) { + print STDOUT "Purging configuration files for $package ...\n"; + push(@undo, + '$newconff=""; + for $f (@configf) { $newconff.= "\n $f $hash{$f}"; } + $st_pk2v{$package,"conffiles"}= $newconff; $all_k21{"conffiles"}= 1;'); + for $file (@configfr) { + $drf= &conffderef($file); if (!defined($drf)) { next; } + unlink("$instroot/$drf") || $! == &ENOENT || + &error("$arg: cannot remove old config file $file (= $drf): $!"); + $hash{$file}= 'newconffile'; + unlink("$instroot/$file") || $! == &ENOENT || + &error("$arg: cannot remove old config file $file: $!") + if $file ne $drf; + for $ext ('.dpkg-tmp', '.dpkg-old', '.dpkg-new', '~', '.bak', '%') { + unlink("$instroot/$drf$ext") || $! == &ENOENT || + &error("$arg: cannot remove old config file $drf$ext: $!"); + } + unlink("#$instroot/$drf#") || $! == &ENOENT || + &error("$arg: cannot remove old auto-save file #$drf#: $!"); + $drf =~ m,^(.*)/, || next; $dir= $1; $base= $'; + if (opendir(CFD,"$instroot/$dir")) { + for $_ (readdir(CFD)) { + next unless m/\.~\d+~$/; + next unless $` eq $base; + unlink("$instroot/$dir/$_") || $! == &ENOENT || + &error("$arg: cannot remove old emacs backup file $dir/$_: $!"); + } + closedir(CFD); + if (grep($_ eq $dir, @ilist)) { + rmdir("$instroot/$dir") || $! == &ENOTEMPTY || + &error("$arg: cannot remove config file directory $dir: $!"); + } + } elsif ($! != &ENOENT) { + &error("$arg: cannot read config file dir $dir: $!"); + } + } + &run_script("$scriptsdir/$package.postrm", 'post-removal script for purge', + 'purge'); + unlink("$scriptsdir/$package.postrm") || $! == &ENOENT || + &error("$arg: cannot remove old postrm script: $!"); + &setnotinstalled; + @undo= (); + } elsif (!@configf && !stat("$scripts/$package.postrm")) { + # If there are no config files and no postrm script then we + # go straight into `purge'. However, perhaps the stat didn't + # fail with ENOENT ... + $! == &ENOENT || &error("$package: stat failed on postrm script: $!"); + $st_p2w{$package}= 'purge'; + &setnotinstalled; + } + $st_p2h{$package}= 'ok'; $statusupdated{$package}= 1; +} + +sub setnotinstalled { + unlink("$listsdir/$package.list") || + &error("$arg: unable to delete old file list: $!"); + $st_p2s{$package}= 'not-installed'; + for $k (keys %all_k21) { delete $st_pk2v{$package,$k}; } +} + +#*** dependency processing - common to --configure and --remove ***# + +# The algorithm for deciding what to configure or remove first is as +# follows: +# +# Loop through all packages doing a `try 1' until we've been round and +# nothing has been done, then do `try 2' and `try 3' likewise. +# +# When configuring, in each try we check to see whether all +# dependencies of this package are done. If so we do it. If some of +# the dependencies aren't done yet but will be later we defer the +# package, otherwise it is an error. +# +# When removing, in each try we check to see whether there are any +# packages that would have dependencies missing if we removed this +# one. If not we remove it now. If some of these packages are +# themselves scheduled for removal we defer the package until they +# have been done. +# +# The criteria for satisfying a dependency vary with the various +# tries. In try 1 we treat the dependencies as absolute. In try 2 we +# check break any cycles in the dependency graph involving the package +# we are trying to process before trying to process the package +# normally. In try 3 (which should only be reached if +# --force-depends-version is set) we ignore version number clauses in +# Depends lines. In try 4 (only reached if --force-depends is set) we +# say "ok" regardless. +# +# If we are configuring and one of the packages we depend on is +# awaiting configuration but wasn't specified in the argument list we +# will add it to the argument list if --configure-any is specified. +# In this case we note this as having "done something" so that we +# don't needlessly escalate to higher levels of dependency checking +# and breaking. + +sub dependencies_ok { + local ($dp, $removingp) = @_; + local ($tpo, $however_t, $ok, $found, @aemsgs, @oemsgs); + local ($tp, $rightver, $inst, $want, $thisf, $matched, $tpp); + $ok= 2; # 2=ok, 1=defer, 0=halt + &debug("checking dependencies of $dp (- $removingp)"); + for $tpo (split(/,/, $st_pk2v{$dp,'depends'})) { + $tpo =~ s/^\s*//; $tpo =~ s/\s+$//; + &debug(" checking group $dp -> $tpo"); + $matched= 0; @oemsgs=(); + $found=0; # 0=none, 1=defer, 2=withwarning, 3=ok + for $tp (split(/\|/, $tpo)) { + $tp =~ s/^\s*//; $tp =~ s/\s+$//; + &debug(" checking possibility $dp -> $tp"); + if ($ignore_depends{$tp}) { &debug("ignoring so ok"); $found=3; last; } + if (defined($cyclebreak{$dp,$tp})) { &debug("break cycle"); $found=3; last; } + if ($tp eq $removingp) { + ($tps, $rightver, $inst, $want, $tpp)= ('removing-now', 1, '','', $tp); + $matched= 1; + } else { + ($tps, $rightver, $inst, $want, $tpp)= &installationstatus($tp); + &debug("installationstatus($tp) -> !$tps!$rightver!$inst!$want!$tps|"); + } + if (($tps eq 'installed' || $tps eq 'unpacked' || $tps eq 'postinst-failed') + && !$rightver) { + push(@oemsgs,"version of $tpp on system is $inst (wanted $want)"); + if ($force{'depends'}) { $thisf= $dependtry >= 3 ? 2 : 1; } + } elsif ($tps eq 'unpacked' || $tps eq 'postinst-failed') { + if (grep($_ eq $tpp, @deferred)) { + $thisf=1; + } elsif (!length($removingp) && $force{'configure-any'}) { + &warn("will also configure $tpp"); + push(@deferred,$tpp); $sincenothing=0; $thisf=1; + } else { + push(@oemsgs,"package $tpp is not configured yet"); + if ($force{'depends'}) { $thisf= $dependtry >= 4 ? 2 : 1; } + } + } elsif ($tps eq 'installed') { + $found=3; last; + } elsif ($tps eq 'removing-now') { + push(@oemsgs,"$tpp is to be removed"); + if ($force{'depends'}) { $thisf= $dependtry >= 4 ? 2 : 1; } + } else { + push(@oemsgs,"$tpp ($want) is not installed"); + if ($force{'depends'}) { $thisf= $dependtry >= 4 ? 2 : 1; } + } + &debug(" found $found"); + $found=$thisf if $thisf>$found; + } + &debug(" found $found matched $matched"); + next if length($removingp) && !$matched; + if (length($removingp) && $tpo !~ m/\|/) { + $however_t= ''; + } elsif (@oemsgs > 1) { + $however_t= "\n However, ". join(",\n ", @oemsgs[0..($#oemsgs-1)]). + " and\n ". $oemsgs[$#oemsgs]. "."; + } else { + $however_t= "\n However, @oemsgs."; + } + if ($found == 0) { + push(@aemsgs, "$dp depends on $tpo.$however_t"); + $ok=0; + } elsif ($found == 1) { + $ok=1 if $ok>1; + } elsif ($found == 2) { + push(@aemsgs, "$dp depends on $tpo.$however_t"); + } elsif ($found != 3) { + &internalerr("found value in deferred_configure $found not known"); + } + } + &debug("ok $ok msgs >>@aemsgs<<"); + return ($ok, @aemsgs); +} + +sub findbreakcycle { + # Cycle breaking works recursively down the package dependency + # tree. @sofar is the list of packages we've descended down + # already - if we encounter any of its packages again in a + # dependency we have found a cycle. + # + # Cycles are preferentially broken by ignoring a dependency from + # a package which doesn't have a postinst script. If there isn't + # such a dependency in the cycle we break at the `start' of the + # cycle from the point of view of our package. + # + local ($package,@sofar) = @_; + local ($tp,$tpp,$tps,$rightver,$inst,$want,$i,$dr,$de,@sf); + &debug("findbreakcycle($package; @sofar)"); + push(@sofar,$package); + for $tp (split(/[,|]/, $st_pk2v{$package,'depends'})) { + $tp =~ s/^\s*//; $tp =~ s/\s+$//; + ($tps, $rightver, $inst, $want, $tpp)= &installationstatus($tp); + next unless $tps eq 'config-files' || $tps eq 'unpacked'; + next if $cyclebreak{$package,$tpp}; + if (grep($_ eq $tpp, @sofar)) { + &debug("found cycle $package, $tpp (@sofar)"); + @sf= (@sofar,$tpp); + for ($i=0; + $i<$#sf; + $i++) { + next if stat("$scriptsdir/$sf[$i].postinst"); + last if $! == &ENOENT; + &error("$arg: unable to stat $scriptsdir/$sf[$i].postinst: $!"); + } + $i=0 if $i>=$#sf; + ($dr,$de)= @sf[$i..$i+1]; + if (!defined($cyclebreak{$dr,$de})) { + $sincenothing=0; $cyclebreak{$dr,$de}= 1; + &debug("broken cycle $i (@sf) at $dr -> $de"); + return 1; + } + } else { + return if &findbreakcycle($tpp,@sofar); + } + } + return 0; +} + +#*** useful subroutines for actions ***# + +sub filesinpackage { + # Returns the list in @ilist. + # If error, calls &error("$epfx: ..."); + local ($epfx, $package) = @_; + open(LIST,"$listsdir/$package.list") || + &error("$epfx: database broken for $package - ". + "can't get installed files list: $!"); + @ilist= <LIST>; + eof || &error("$epfx: cannot read $listsdir/$package.list: $!"); + close(LIST); + @ilist= grep((chop, + s|/$||, + m|^/| || s|^|/|, + m/./), + @ilist); +} + +sub installationstatus { + local ($controlstring) = @_; + local ($lversion,$lpackage,$lstatus,$lrevision,$cmp) = @_; + local ($cc); + $lversion= $controlstring; + $lversion =~ s/^($packagere)\s*// || + &internalerr("needed installation status of bogus thing \`$lversion'"); + $lpackage= $1; + $lstatus= defined($st_p2s{$lpackage}) ? $st_p2s{$lpackage} : 'not-installed'; + if ($lstatus ne 'not-installed') { + if (length($lversion)) { + $lversion =~ s/^\s*\(\s*// && $lversion =~ s/\s*\)\s*$// || + &internalerr("failed to strip version \`$lversion'"); + if ($lversion =~ s/^[><=]//) { $cc= $&; } else { $cc= '='; } + $lrevision = ($lversion =~ s/-([^-]+)$//) ? $1 : ''; + $wantedstring= "version $lversion"; + $wantedstring .= ", package revision $lrevision" if length($lrevision); + $cmp= &compare_verrevs($st_pk2v{$lpackage,'version'}, + $st_pk2v{$lpackage,'package_revision'}, + $lversion, + $lrevision); + $installedstring= "version $st_pk2v{$lpackage,'version'}"; + $installedstring .= + ", package revision $st_pk2v{$lpackage,'package_revision'}" + if length($st_pk2v{$lpackage,'package_revision'}); + if ($cc eq '>') { + $rightver= $cmp>=0; $wantedstring.= ' or later'; + } elsif ($cc eq '<') { + $rightver= $cmp<=0; $wantedstring.= ' or earlier'; + } else { + s/^=//; + $rightver= !$cmp; $wantedstring= "exactly $wantedstring"; + } + } else { + $rightver= 1; + $wantedstring= "any version"; + $installedstring= $st_pk2v{$lpackage,'version'}.'-'. + $st_pk2v{$lpackage,'package_revision'}; + } + } else { + $rightver= -1; + $installedstring= "not installed"; + } + return ($lstatus,$rightver,$installedstring,$wantedstring,$lpackage); +} + +sub parse_control { + # reads from fh CONTROL + local ($fn) = @_; + local ($cf,$ln,$l,$k,$v); + defined($cf= &readall('CONTROL')) || &error("read control file $fn: $!"); + close(CONTROL); + $p= &parse_control_entry; + if (@cwarnings) { + &warn("$fn: control file contains oddities: ".join("; ",@cwarnings)) + unless $controlwarn; + } + if (@cerrors) { + &error("$fn: control file contains errors: ".join("; ",@cerrors)); + } +} + +sub run_script_ne { + local ($script,$describe,@args) = @_; + local ($extranewlines) = $script =~ m/postinst/; + &debug("running $describe = $script @args"); + if (!stat("$script")) { + return if $! == &ENOENT; + die "couldn't stat $script: $!\n"; + } + if (! -x _) { + chmod(0755, "$script") || die "couldn't make $script executable: $!\n"; + } + print "\n" if $extranewlines; + &debug("forking now"); + defined($rsc= fork) || die "couldn't fork for running $script: $!\n"; + if (!$rsc) { + if ($instroot !~ m|^/*$| && !chroot($instroot)) { + print STDERR "$name: failed to chroot to $instroot for $describe: $!\n"; + exit(1); + } + exec($script,@args); + print STDERR "$name: failed to exec $script: $!\n"; + exit(1); + } + $!=0; waitpid($rsc,0) == $rsc || die "couldn't wait for $describe: $!\n"; + $? && die "$describe failed (".&ecode.")\n"; + &debug("script done"); + print "\n" if $extranewlines; +} + +sub run_script { + return if eval { &run_script_ne; 1; }; + $rse= $@; chop($rse); &error("$package: $rse"); +} + +sub hash { + local ($file) = @_; # NB: filename must already have $instroot here + local ($c); + if (open(HF,"<$file")) { + defined($c= open(MDP,"-|")) || &error("fork/pipe for hash: $!"); + if (!$c) { + if (!open(STDIN,"<&HF")) { + print STDERR "$name: unable to redirect stdin for hash: $!\n"; exit(1); + } + exec($md5sum); print STDERR "$name: unable to exec $md5sum: $!\n"; exit(1); + } + defined($hash= &readall('MDP')) || &error("unable to read from $md5sum: $!\n"); + $!=0; close(MDP); $? && &error("$md5sum returned error (".&ecode.")"); + $hash =~ s/\n+$//; + $hash =~ m/^[0-9a-f]{32}$/i || &error("$md5sum returned bogus output \`$hash'"); + return $hash; + } elsif ($! == &ENOENT) { + return 'nonexistent'; + } else { + &warn("$arg: unable to open conffile $file for hash: $!"); + return '-'; + } +} + +sub files_not_identical { + local ($file1,$file2) = @_; # NB: filenames must already have $instroot here + if (stat($file1)) { + if (stat($file2)) { + system("cmp","-s",$file1,$file2); + if (&WIFEXITED($?)) { + $es= &WEXITSTATUS($?); + return $es if $es == 0 || $es == 1; + } + &error("cmp $file1 $file2 returned error (".&ecode.")"); + } elsif ($! == &ENOENT) { + return 1; + } else { + &error("failed to stat conffile $file2: $!"); + } + } elsif ($! == &ENOENT) { + if (stat($file2)) { + return 1; + } elsif ($! == &ENOENT) { + return 0; + } else { + &error("failed to stat conffile $file2: $!"); + } + } else { + &error("failed to stat conffile $file1: $!"); + } +} + +sub copyperm { + local ($from,$to,$name) = @_; + if (@statv= stat("$instroot/$from")) { + chown($statv[4],$statv[5],"$instroot/$to") || + $!==&ENOENT || + &warn("$package: unable to preserve ownership of $name"); + chmod($statv[2],"$instroot/$to") || + $!==&ENOENT || + &warn("$package: unable to preserve permissions of $name"); + } elsif ($! != &ENOENT) { + &warn("$package: unable to check permissions and ownership of". + " $name in order to preserve them"); + } +} + +sub conffderef { + local ($file) = @_; + local ($drf, $warning); + $drf= $file; $warning=''; + for (;;) { + if (!lstat("$instroot/$drf")) { + last if $! == &ENOENT; $warning= "unable to lstat: $!"; last; + } elsif (-f _) { + last; + } elsif (-l _) { + if (!defined($lv= readlink("$instroot/$drf"))) { + $warning= "unable to readlink: $!"; last; + } + if ($lv =~ m|^/|) { + $drf= $lv; + } else { + $drf =~ s|/[^/]+$|/$lv|; + } + } else { + $warning= "not a plain file or symlink"; last; + } + } + &debug("conffile $file drf $drf warns \`$warning'"); + if ($warning) { + &warn("$arg: possible problem with configuration file $file (= $drf):\n". + " $warning"); + return undef; + } else { + return $drf; + } +} |