Index: head/ports-mgmt/pkg_cutleaves/Makefile =================================================================== --- head/ports-mgmt/pkg_cutleaves/Makefile (revision 487621) +++ head/ports-mgmt/pkg_cutleaves/Makefile (revision 487622) @@ -1,40 +1,42 @@ # Created by: Stefan Walter # $FreeBSD$ PORTNAME= pkg_cutleaves -PORTVERSION= 20180108 +PORTVERSION= 20181216 CATEGORIES= ports-mgmt MASTER_SITES= # none DISTFILES= # none MAINTAINER= bofh@FreeBSD.org COMMENT= Interactive script for deinstalling 'leaf' packages LICENSE= BSD2CLAUSE -NO_BUILD= yes USES= perl5 shebangfix + +NO_BUILD= yes SHEBANG_FILES= pkg_cutleaves NO_WRKSUBDIR= yes PLIST_FILES= sbin/pkg_cutleaves man/man1/pkg_cutleaves.1.gz +NO_ARCH= yes do-extract: ${MKDIR} ${WRKSRC} ${CP} ${FILESDIR}/pkg_cutleaves ${FILESDIR}/pkg_cutleaves.1 ${WRKSRC} post-patch: @${REINPLACE_CMD} -e \ 's,/usr/local/etc/pkg_leaves.exclude,${PREFIX}/etc/pkg_leaves.exclude,' \ ${WRKSRC}/pkg_cutleaves.1 @${REINPLACE_CMD} \ -e 's,/usr/local/etc/pkg_leaves.exclude,${PREFIX}/etc/pkg_leaves.exclude,' \ -e 's,/usr/local/sbin/pkg delete -y,${PKG_DELETE},' \ -e 's,/usr/local/sbin/pkg query,${PKG_QUERY},' \ ${WRKSRC}/pkg_cutleaves do-install: ${INSTALL_SCRIPT} ${WRKSRC}/pkg_cutleaves ${STAGEDIR}${PREFIX}/sbin/pkg_cutleaves ${INSTALL_MAN} ${WRKSRC}/pkg_cutleaves.1 ${STAGEDIR}${MAN1PREFIX}/man/man1 .include Index: head/ports-mgmt/pkg_cutleaves/files/pkg_cutleaves =================================================================== --- head/ports-mgmt/pkg_cutleaves/files/pkg_cutleaves (revision 487621) +++ head/ports-mgmt/pkg_cutleaves/files/pkg_cutleaves (revision 487622) @@ -1,366 +1,366 @@ #!/usr/bin/perl # $Id$ # # Copyright (c) 2003 Stefan Walter # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # Interactive script for deinstalling "leaf" packages # # Syntax: pkg_cutleaves [-cgLlRVx] # Options: # -c: Show comments, too; only works with '-l' (ignored otherwise) # -g: Generate exclude list from kept/installed leaf packages # -L: Interpret exclude file as list of packages that *should* be installed # -l: List leaf packages only, don't ask if they should be deinstalled # -R: Autoprune new leaves # -V: Visual mode, invoke $EDITOR # -x: Honor exclude list in $excludefile use File::Temp qw/ tempfile /; use Getopt::Std; use strict; my $excludefile = "/usr/local/etc/pkg_leaves.exclude"; my @pkgdeinstall = (qw{/usr/local/sbin/pkg delete -y}); my @pkgquery = (qw{/usr/local/sbin/pkg query}); my $exclpattern; my %leavestokeep; my %opt; getopts('cgLlRVx', \%opt); set_excl_pattern(); # LIST MODE if ($opt{l}) { # Just print out the list of leaves, one per line my %leaves = get_leaves(); foreach my $leaf (sort keys %leaves) { if ($opt{c}) { print "$leaf - $leaves{$leaf}\n"; } else { print "$leaf\n"; } $leavestokeep{$leaf} = 1; } } # LINT MODE elsif ($opt{L}) { my @excludes = get_excludelist(); my @pkgs = get_packages(); # For each installed package for my $pkg (@pkgs) { my ($file, $required) = @$pkg; # Clobber any exclude patterns that match this package for (my $i = 0; $i < @excludes; $i++) { if ($file =~ /\Q$excludes[$i]\E/) { splice(@excludes, $i--, 1); } } # If matches exclude list and is not a leaf if ($required && ($file =~ $exclpattern)) { print "$file matches exclude list, but is not a leaf\n" } else { $leavestokeep{$file} = 1; } } # In exclude list, but not installed # For each remaining entry in exclude list foreach my $exclude (@excludes) { print "Exclude pattern '$exclude' matches no installed packages\n"; } } # INTERACTIVE MODE else { my @cutleaves; my ($i, $again); # Get list of leaf packages and put them into a hash my %leaves = get_leaves(); # Any leaves to work with? my $nleaves = keys %leaves; if ($nleaves > 0) { # If we don't have superuser rights, notify the user. if ($< != 0) { print "You need to have root permissions for deinstalling packages.\n"; $again = 'n'; } else { $again = 'y'; } } else { # If not, don't go on, there's nothing to do. print "** Didn't find any leaves to work with, exiting.\n"; print "** If this is unexpected, check your exclude file, please.\n"; $again = 'n'; } # Loop while the user wants to ROUND: while($again ne 'n') { # Always start with an empty list of leaves to cut my %leavestocut; # Initialize counter for progress status $i = 1; if ($opt{V}) { # Write list, including descriptions out to temp file my ($fh, $filename) = tempfile() or die "Can't create file: $!"; print $fh "# Delete the lines whose packages you want removed.\n"; print $fh "# Simply save and quit, when you are done.\n"; print $fh "# There are $nleaves (new) leaf packages:\n"; foreach my $leaf (sort keys %leaves) { # XXX Calculate maximum length first instead of 40? printf $fh "%-40s - %s\n", $leaf, $leaves{$leaf}; } close($fh); # Invoke $EDITOR, vi if no EDITOR set my $editor = ($ENV{'EDITOR'} eq "") ? "vi" : $ENV{'EDITOR'}; system "$editor $filename"; if ($? == -1) { print "failed to execute: $!\n"; unlink($filename); exit 1; } # Read back and parse list, the diff to the original list will be removed open($fh, $filename) or die "Can't reopen file: $!"; while(<$fh>) { next if $_ =~ m/\s*#/; next if $_ =~ m/^\s*$/; my ($leaf, $desc) = split(/\s/, $_, 2); if (defined $leaves{$leaf}) { delete $leaves{$leaf}; $leavestokeep{$leaf} = 1; } else { print "Package \"$leaf\" not known, skipping\n"; } } close($fh); unlink($filename); exit 0 if (keys %leaves == 0); # print the lists of packages which will be removed, due to # typos in the editor, the user might have 'removed' a package # he is still interested in. This batch mode, really should get a last # confirmation from the user. print "The following packages will be removed:\n"; # XXX Use map here? # XXX check empty hash FIXME foreach my $leaf (sort keys %leaves) { $leavestocut{$leaf} = 1; print " $leaf\n"; } print "Are you sure? [yes]: "; my ($answer) = (lc() =~ /(\S)/o); print "\n"; exit 0 if $answer =~ /n/; # Fallthrough to actual package removal } else { LEAVESLOOP: foreach my $leaf (sort keys %leaves) { print "Package $i of $nleaves:\n"; print "$leaf - $leaves{$leaf}\n"; print "$leaf - [keep]/(d)elete/(f)lush marked pkgs/(a)bort? "; # Get first character of input, without leading whitespace my ($answer) = (lc() =~ m/(\S)/); if ($answer eq 'd') { print "** Marking $leaf for removal.\n\n"; $leavestocut{$leaf} = 1; } elsif ($answer eq 'f') { print "\n"; last LEAVESLOOP; } elsif ($answer eq 'a') { print "\n"; $opt{aborted} = 1; last ROUND; } else { print "** Keeping $leaf.\n\n"; $leavestokeep{$leaf} = 1; } $i++; } # LEAVESLOOP } AUTOPRUNE: # The -R switch jump # Initialize 'progress meter' my $ncuts = keys %leavestocut; my $noff = 0; # loop through packages marked for removal and pkg_deinstall them foreach my $leaf (sort keys %leavestocut) { $noff++; print "Deleting $leaf (package $noff of $ncuts).\n"; my @deinstall_args = (@pkgdeinstall, $leaf); if ((my $status = system(@deinstall_args) >> 8) != 0) { print STDERR "\n\n$0: pkg_deinstall returned $status - exiting, fix this first.\n\n"; last ROUND; } push @cutleaves, $leaf; } # Get new list of leaf packages and put them into a hash %leaves = get_leaves(); # Ignore all leaves the user already told us to keep foreach my $leaf (keys %leavestokeep) { delete $leaves{$leaf} } # Any leaves left? $nleaves = keys %leaves; if ($nleaves == 0) { # If not, don't go on, there's nothing left to do. print "** Didn't find any new leaves to work with, exiting.\n"; last ROUND; } if ($opt{R}) { # start autopruning new leaves print "\n** Autopruning new leaves (Ctrl-C now to stop!) **\n" x 2; sleep 1; %leavestocut = %leaves; goto AUTOPRUNE; } # AUTOPRUNE print "Go on with new leaf packages ([yes]/no)? "; # Get first character of input, without leading whitespace ($again) = (lc() =~ /(\S)/o); print "\n"; } # ROUND # print list of removed packages, sorted lexically, and their number print "** Deinstalled packages:\n"; foreach my $cutleaf (sort @cutleaves) { print "$cutleaf\n"; } my $noff = @cutleaves; print "** Number of deinstalled packages: $noff\n"; } # Generate exclude file if ($opt{g}) { if ($opt{aborted}) { die "\n** Skipping exclude file generation on aborted session **\n"; } if (-e $excludefile) { print "\nExclude file ($excludefile) exists! Overwrite ((y)es/[no])? "; my $answer = ; unless ($answer =~ /^y(es)?$/io) { exit 0; } } create_excludelist(); } # # Set the exclude pattern # sub set_excl_pattern { my @excludes = get_excludelist(); $exclpattern = @excludes ? join('|', map{qr(\Q$_\E)} @excludes) : ' '; # default non-exclusive $exclpattern = qr{^($exclpattern)$}o; } # # Read the exclude list if the file exists # Parameter: path of the exclude file # sub get_excludelist { my @excludelist; # XXX: Don't check command line params in a subroutine if (($opt{x} || $opt{L}) && -f $excludefile && -T $excludefile) { open(EXCLFILE, $excludefile) or die "Couldn't open $excludefile!"; while(my $exclude = ) { chomp($exclude); # Ignore comments and empty lines, add others to the list unless ($exclude =~ /(^ *#)|(^ *$)/o) { push(@excludelist, $exclude); } } close(EXCLFILE) or warn "Failed to close exclude file ($excludefile): $!\n"; } return @excludelist; } # # Return a list of all packages # sub get_packages { my @pkgs; open(PKGQUERY, '-|', @pkgquery, '-a', '%n\t%n-%v\t%?r\t%c') or die "Couldn't read output from $pkgquery[0]!"; while (my $p = ) { chomp($p); push(@pkgs, [ split(/\t/, $p) ]); } close PKGQUERY; return @pkgs; } # # Get a hash (name => comment) of all leaves # sub get_leaves { my %leaves; my @pkgs = get_packages(); foreach my $pkg (@pkgs) { my ($name, $file, $required, $comment) = @$pkg; unless ($required) { if ($name =~ $exclpattern) { $leavestokeep{$file} = 1; } else { unless($comment) { $comment = 'No short description'; } $leaves{$file} = $comment; } } } return %leaves; } # # Write the list of exclusions to a file # sub create_excludelist { open(EXCLFILE, ">$excludefile") or die "Failed to open exclude list ($excludefile): $!\n"; print EXCLFILE '# Auto-generated exclude list ', scalar localtime, "\n"; for (sort keys %leavestokeep) { - /^(.+)-\d.*$/o + /^(.+)-g?\d.*$/o ? print EXCLFILE $1, "\n" : warn 'Unable to extract exclude pattern from ', $_, "\n"; } print "New exclude list ($excludefile) generated.\n"; }