#!/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 "fileutils" require "optparse" require "pkgtools" require "uri" def init_global $force = false $noconfig = false #$sanity_check = true $tempdir = "" $upward_recursive = false end COLUMNSIZE = 24 NEXTLINE = "\n%*s" % [5 + COLUMNSIZE, ''] if PkgConfig::OS_MAJOR.to_i >= 8 PKG_SUFFIXES = ['.tbz', '.txz', '.tgz'] elsif PkgConfig::OS_MAJOR.to_i >= 5 PKG_SUFFIXES = ['.tbz', '.tgz'] else PKG_SUFFIXES = ['.tgz', '.tbz'] end def main(argv) usage = <<-"EOF" usage: #{MYNAME} [-hfqRv] {pkgname|URI} ... EOF 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("-f", "--force", "Download a package even if recorded as installed;#{NEXTLINE}Remove existing packages if they are corrupt") { |v| $force = v } # opts.def_option("-O", "--omit-check", "Omit sanity checks for dependencies.") { # $sanity_check = false # } opts.def_option("-q", "--noconfig", "Do not read pkgtools.conf") { |v| $noconfig = v } opts.def_option("-R", "--upward-recursive", "Download the packages required by the given#{NEXTLINE}packages as well") { |v| $upward_recursive = v } opts.def_option("-v", "--verbose", "Be verbose") { |v| $verbose = v } opts.def_tail_option ' pkgname is a full pkgname, a pkgname w/o version followed by an @, or a full URI. Environment Variables [default]: PACKAGEROOT URI of the root of the site [ftp://ftp.FreeBSD.org] PACKAGESITE URI of the directory to fetch packages from [none] (overrides PACKAGEROOT and PKG_SITES) PACKAGES packages directory to save files [$PORTSDIR/packages] PKGTOOLS_CONF configuration file [$PREFIX/etc/pkgtools.conf] PKG_DBDIR packages DB directory [/var/db/pkg] PKG_FETCH command to fetch files [/usr/bin/fetch -ao %2$s %1$s] PKG_SITES list of URIs to fetch packages from [none] PKG_TMPDIR temporary directory for download [$TMPDIR] PORTSDIR ports directory [/usr/ports] TMPDIR temporary directory [/var/tmp]' 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? print opts return 0 end results = PkgResultSet.new if $pkgdb.with_pkgng? system(format("%s update", PkgDB::command(:pkg))) end opts.order(*argv) do |arg| set_uri_base(arg) arg = File.basename(arg).sub(/\.t[bg]z$/, '') fetch_pkg(arg, $upward_recursive, results) end return results.show('downloaded') rescue OptionParser::ParseError => e STDERR.puts "#{MYNAME}: #{e}", usage exit 64 end end end def fetch_pkg(pkgname, recursive, results) downloaded, pkgdep = do_fetch_pkg(pkgname) $subdir = nil results << PkgResult.new(pkgname, downloaded ? :done : :ignored) if pkgdep.is_a?(Array) && recursive pkgdep.each do |dep| results.include?(dep) or fetch_pkg(dep, true, results) end end rescue => e results << PkgResult.new(pkgname, e) end def do_fetch_pkg(pkgname) pkgname = pkgname.dup latest = pkgname.chomp!('@') || !pkgname.index(/-\d/) if !latest && !$force && $pkgdb.installed?(pkgname) progress_message "Skipping #{pkgname} (already installed)" return false, nil end if $pkgdb.with_pkgng? pkgname = backquote!(PkgDB::command(:pkg), 'rquery', '-U', '%n-%v', pkgname).lines.first.chomp end PKG_SUFFIXES.each do |suffix| pkgfilename = pkgname + suffix path = File.join($packages_dir, pkgfilename) File.exist?(path) or next progress_message "Identifying the package #{path}" id_pkgname, origin, pkgdep = identify_pkg(path) return false, pkgdep if not id_pkgname.nil? warning_message "Failed to extract information from #{path}" raise "corrupt package" unless $force warning_message "Removing the corrupt package #{path}" File.unlink(path) end FileUtils.mkdir_p [$tmpdir, $packages_dir] progress_message "Fetching #{pkgname}" if $pkgdb.with_pkgng? temp_path_base = File.join($tmpdir, 'All/', pkgname) else temp_path_base = File.join($tmpdir, pkgname) end temp_path = real_fetch_pkg(pkgname, temp_path_base, latest) if not temp_path warning_message "Failed to fetch #{pkgname}" raise "fetch error" end pkgfilename = File.basename(temp_path) progress_message "Downloaded as #{pkgfilename}" case pkgfilename when /\.tgz$/ if /tar archive$/ =~ `file #{shelljoin(temp_path)}` warning_message "Seems the downloaded file is somehow not compressed despite its file name" progress_message "Compressing #{temp_path} with gzip" tar = temp_path.sub(/\.tgz$/, '.tar') targz = tar + '.gz' system([shelljoin('mv', temp_path, tar), shelljoin('gzip', tar), shelljoin('mv', targz, temp_path)].join(' && ')) end when /\.tbz2?$/ if /tar archive$/ =~ `file #{shelljoin(temp_path)}` warning_message "Seems the downloaded file is somehow not compressed despite its file name" progress_message "Compressing #{temp_path} with bzip2" tar = temp_path.sub(/\.tbz2?$/, '.tar') tarbz2 = tar + '.bz2' system([shelljoin('mv', temp_path, tar), shelljoin('bzip2', tar), shelljoin('mv', tarbz2, temp_path)].join(' && ')) end end progress_message "Identifying the package #{temp_path}" pkgname, origin, pkgdep = identify_pkg(temp_path) if pkgname.nil? warning_message "Failed to extract information from #{temp_path}" raise "corrupt package" end save_path = File.join($packages_dir, pkgname + pkgfilename.sub(/^.*(\.[^.]+)$/, "\\1")) begin FileUtils.mv(temp_path, save_path) rescue => e warning_message "Failed to save the dowloaded tarball as #{save_path}" raise e end progress_message "Saved as #{save_path}" return true, pkgdep end def set_uri_base(uri_s) $subdir = nil begin uri = URI.parse(uri_s) if not uri.scheme.nil? $subdir = File.basename((uri + './').path.chomp('/')) case $subdir when 'All', 'Latest' $pkg_site_uris = [uri + '../'] else $subdir = '.' $pkg_site_uris = [uri] end return true end rescue => e # not a remote URI end if ENV.key?('PACKAGESITE') $pkg_site_uris = [URI.parse(ENV['PACKAGESITE']) + '../'] else $pkg_site_uris = $pkg_sites.map { |str| URI.parse(str) } end return true rescue => e warning_message e.message $pkg_site_uris = [] return false end def real_fetch_pkg(pkgname, path_base, latest = false) if latest subdir = $subdir || 'Latest' else subdir = $subdir || 'All' end if $pkgdb.with_pkgng? suffix = '.txz' uri = pkgname + suffix path = path_base + suffix fetch(uri, path) and return path else if $verbose information_message 'Will try the following sites in the order named:' $pkg_site_uris.each do |site| STDERR.puts "\t#{site}" end end $pkg_site_uris.each do |uri_base| PKG_SUFFIXES.each do |suffix| uri = uri_base + (subdir + '/' + pkgname + suffix) path = path_base + suffix fetch(uri, path) and return path end end end nil end def fetch(uri, path = File.basename(uri.path)) if path.empty? warning_message 'Missing filename' return false end uri_subbed = uri.to_s.gsub("%3A", ":").gsub("%2F", "/") if $pkgdb.with_pkgng? # Path is /tmp/<something>/All cmdline = format("%s fetch -U -y -o '%s' '%s'", PkgDB::command(:pkg), File.dirname(File.dirname(path)), File.basename(uri, '.txz')) else cmdline = format(ENV['PKG_FETCH'] || "/usr/bin/fetch -o '%2$s' '%1$s'", uri_subbed, path) end progress_message "Invoking a command: #{cmdline}" if $verbose system(cmdline) status = $? >> 8 if status.nonzero? warning_message format("The command returned a non-zero exit status: %d", status) end if File.zero?(path) warning_message "Got a zero-sized file #{path} (removing)" File.unlink(path) end if !File.exist?(path) warning_message "Failed to fetch #{uri}" return false end return true end if $0 == __FILE__ set_signal_handlers exit(main(ARGV) || 1) end