#!/usr/bin/env ruby # -*- ruby -*- # vim: set sts=2 sw=2 ts=8 et: # # Copyright (c) 2000-2004 Akinori MUSHA # Copyright (c) 2006-2008 Sergey Matveychuk <[email protected]> # Copyright (c) 2009-2012 Stanislav Sedov <[email protected]> # Copyright (c) 2012 Bryan Drewery <[email protected]> # # 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 AUTHOR 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 AUTHOR 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. # # MYNAME = File.basename($0) require "optparse" require "pkgtools" case MYNAME when 'pkg_deinstall' $service = :deinstall else $service = :list end $service_verb = $service.to_s COLUMNSIZE = 22 NEXTLINE = "\n%*s" % [5 + COLUMNSIZE, ''] def init_global $all = false $collate = false $exclude_packages = [] $force = false $interactive = false $noconfig = false $noexecute = false $pkg_delete_args = [] $preserve_shlib = false $recursive = false $sanity_check = true $tempdir = "" $upward_recursive = false $verbose = false end def main(argv) case $service when :deinstall usage = <<-"EOF" usage: #{MYNAME} [-hacdDfinOPqrRv] [-e args] [-p prefix] [-x pkgname_glob] [pkgname_glob ...] EOF else usage = <<-"EOF" usage: #{MYNAME} [-haOqrR] [-x pkgname_glob] [pkgname_glob ...] EOF end banner = <<-"EOF" #{MYNAME} #{Version} (#{PkgTools::DATE}) #{usage} EOF dry_parse = true OptionParser.new(banner, COLUMNSIZE) do |opts| opts.def_option("-h", "--help", "Show this message") { print opts exit 0 } opts.def_option("-a", "--all", "#{$service_verb.capitalize} all the installed packages") { |v| $all = v $recursive = false $upward_recursive = false } opts.def_option("-c", "--collate", "Check if any of the installed files of each package#{NEXTLINE}has been overwritten by other packages, and do not#{NEXTLINE}deinstall if any") { $collate = true unless $force } if $service == :deinstall opts.def_option("-d", "--rmdir", "Remove empty directories created by file cleanup [*]") { $pkg_delete_args.push '-d' } if $service == :deinstall opts.def_option("-D", "--noscripts", "Do not execute deinstallation scripts [*]") { $pkg_delete_args.push '-D' } if $service == :deinstall opts.def_option("-f", "--force", "Force removal of the packages [*]") { |v| $force = v $pkg_delete_args.push '-f' $collate = false } if $service == :deinstall opts.def_option("-i", "--interactive", "Request confirmation for each package removal [*]") { |v| $interactive = v if $interactive $verbose = true end $pkg_delete_args.push '-i' } if $service == :deinstall opts.def_option("-n", "--noexecute", "Do not actually deinstall a package, just report the#{NEXTLINE}steps that would be taken if it were [*]") { |v| $noexecute = v if $noexecute $verbose = true $interactive = false end $pkg_delete_args.push '-n' } if $service == :deinstall opts.def_option("-O", "--omit-check", "Omit sanity checks for dependencies.") { $sanity_check = false } opts.def_option("-p", "--prefix=PREFIX", "Set prefix [*]") { |prefix| $pkg_delete_args.push '-p', prefix } if $service == :deinstall opts.def_option("-P", "--preserve", "Preserve shared libraries (Save *.so.n)") { |v| $preserve_shlib = v } if $service == :deinstall opts.def_option("-q", "--noconfig", "Do not read pkgtools.conf") { |v| $noconfig = v } opts.def_option("-r", "--recursive", "#{$service_verb.capitalize} all those depending on the given#{NEXTLINE}packages as well") { $recursive = true unless $all } opts.def_option("-R", "--upward-recursive", "#{$service_verb.capitalize} all those required by the given#{NEXTLINE}packages as well") { $upward_recursive = true unless $all } opts.def_option("-v", "--verbose", "Turn on verbose output [*]") { |v| $verbose = v $pkg_delete_args.push '-v' } if $service == :deinstall opts.def_tail_option <<-"EOF".chomp if $service == :deinstall [*] These options are transparently passed to pkg_delete(1). EOF opts.def_option("-x", "--exclude=GLOB", "Exclude packages matching the specified glob#{NEXTLINE}pattern") { |arg| begin pattern = parse_pattern(arg) rescue RegexpError => e warning_message e.message.capitalize break end $exclude_packages |= $pkgdb.glob(pattern, false) unless dry_parse } opts.def_tail_option ' pkgname_glob is one of these: a full pkgname, a pkgname w/o version, a shell glob pattern in which you can use wildcards *, ?, and [..], an extended regular expression preceded by a colon (:), or a date range specification preceded by either < or >. See pkg_glob(1) for details. Environment Variables [default]: PKGTOOLS_CONF configuration file [$PREFIX/etc/pkgtools.conf] PKG_DBDIR packages DB directory [/var/db/pkg]' tasks = [] begin init_global init_pkgtools_global rest = opts.order(*argv) unless $noconfig init_global load_config else argv = rest end dry_parse = false opts.order!(argv) if argv.empty? && !$all print opts return 0 end all = '*' argv << all opts.order(*argv) do |arg| if arg.equal? all next unless $all pattern = arg else pattern = $pkgdb.strip(arg) next if pattern.nil? begin pattern = parse_pattern(pattern) rescue RegexpError => e warning_message e.message.capitalize next end end begin $pkgdb.glob(pattern, false).each do |pkgname| tasks |= $pkgdb.recurse(pkgname, $recursive, $upward_recursive, $sanity_check) end rescue => e raise e if e.class == PkgDB::NeedsPkgNGSupport STDERR.puts e.message exit 1 end end rescue OptionParser::ParseError => e STDERR.puts "#{MYNAME}: #{e}", usage exit 64 end ## Cleanup pkgng options if $pkgdb.with_pkgng? unless $interactive $pkg_delete_args.push '-y' else # pkgng is interactive by default $pkg_delete_args.delete '-i' end end tasks -= $exclude_packages case $service when :deinstall if tasks.empty? warning_message "No matching package found." exit 255 end $pkgdb.sort!(tasks) results = PkgResultSet.new result_proc = proc { $pkgdb.update_db results.show('deinstalled') } $interrupt_proc = result_proc $compatlibdir = File.join($portsdb.localbase, 'lib/compat/pkg') tasks.reverse_each do |pkgname| begin if deinstall_pkg(pkgname) results << PkgResult.new(pkgname, :done) else results << PkgResult.new(pkgname, :skipped) end rescue => e raise e if e.class == PkgDB::NeedsPkgNGSupport results << PkgResult.new(pkgname, e) end end return result_proc.call else puts(*tasks) unless tasks.empty? end return 0 end end def get_beforedeinstall_command(origin) commands = [] commands[commands.size, 0] = config_beforedeinstall(origin) # maybe nil commands.uniq! commands.each { |cmd| cmd.sub!(/^[;\s]+/, '') if !cmd.nil? } commands.reject! { |cmd| cmd.nil? || cmd.empty? } if commands.empty? nil else commands.join('; ') end end def deinstall_pkg(pkgname) preserved_files = [] progress_message "Deinstalling '#{pkgname}'" pkg = PkgInfo.new(pkgname) filelist = pkg.files if $collate progress_message "Checking for overwritten files" possible_successors = [] begin filelist.each do |path| owners = $pkgdb.which_m(path) or raise PkgDB::DBError, "The file #{path} is not properly recorded as installed" i = owners.index(pkgname) or raise PkgDB::DBError, "The file #{path} is not properly recorded as installed by #{pkgname}" if i != owners.size - 1 overwriters = owners[(i + 1)..-1] puts "\t#{path}: overwritten by: #{overwriters.join(' ')}" # possible_successors |= overwriters end end rescue => e raise e if e.class == PkgDB::NeedsPkgNGSupport STDERR.puts e.message end if possible_successors.empty? puts " -> None." else puts " -> The package may have been succeeded by some of the following package(s)." possible_successors.each do |s| puts "\t#{s}" end progress_message "Skipping '#{pkgname}'" return nil end end if $preserve_shlib unless $noexecute if !system('/bin/mkdir', '-p', $compatlibdir) STDERR.puts "Cannot create directory: #{$compatlibdir}" raise "mkdir failed" end end IO.popen("/usr/bin/file -bnf-", "r+") do |pipe| filelist.each do |file| %r"^[^@\s]\S*/([^/\s]+\.so(?:\.\d+)+)$" =~ file or next base = $1 pipe.puts(file) filetype = pipe.gets if /\bELF\b.*\bshared object\b.*\bFreeBSD\b/ !~ filetype and /\bsymbolic link to\b/ !~ filetype next end dest = File.join($compatlibdir, base) progress_message "Preserving #{file} as #{dest}" next if $noexecute if system('/bin/cp', '-Rpf', file, dest) preserved_files << dest else STDERR.puts "Copy failed!" raise "copy failed" end end end end if origin = $pkgdb.origin(pkgname) and command = get_beforedeinstall_command(origin) progress_message "Executing a command before deinstalling '#{origin}': " + command unless $noexecute system('/bin/sh', '-c', command) end end return true if $noexecute if $pkgdb.with_pkgng? $pkg_delete_args.push '-f' cmdargs = [PkgDB::command(:pkg), 'delete', *$pkg_delete_args] << pkgname else cmdargs = [PkgDB::command(:pkg_delete), *$pkg_delete_args] << pkgname end $pkgdb.close_db sleep 1 # timestamp hack - let PkgDB detect the update if !system(*cmdargs) preserved_files.each do |file| progress_message "Removing #{file}" system('/bin/rm', '-f', file) end raise "pkg_delete failed" end true ensure if !preserved_files.empty? system('/sbin/ldconfig', '-m', $compatlibdir) end end if $0 == __FILE__ set_signal_handlers exit(main(ARGV) || 1) end