diff --git a/linux-update b/linux-update index d183585..988663a 100755 --- a/linux-update +++ b/linux-update @@ -1,181 +1,497 @@ -#!/bin/sh +#!/usr/bin/env ruby -[ 0 = "`id -u`" ] || exec sudo http_proxy="$http_proxy" "$0" "$@" +require 'shellwords' +require 'getoptlong' +require 'json' +require 'pathname' +require 'shell' +require 'uri' -SOURCE_BASE_DIR=/usr/src -SOURCE_BASE_URI=http://www.kernel.org/pub/linux/kernel/v3.x/ +class RequiredGems + attr_reader :requires, :failed + def self.require &block + rg = new + block.call rg, &rg.method(:push) + rg.require + end -usage() { - echo "Usage: $0 $(help | sed -ne '3,$s/^\([^'"`printf '\t'`"']*\)'"`printf '\t'`"'.*$/\1/p' | tr '\n' '|' | sed -e 's/|/ | /g;s/ | $//')" -} + def initialize + @requires, @failed = [], [] + end -help() { #@ Print this help message - cat <( other) version <=> other.version end -latest_fetched() { #@ prints the latest fetched version - fetched | latest_version -} + def to_s + r = "linux-#{version} (#{moniker})" + r += " (EOL)" if end_of_life? + r + end + end -is_listed() { - awk \""${1}"'"==$1{print$0;exit}' -} + class Fetched + attr_reader :dir + def initialize dir + @dir = Pathname.new dir + end -latest_stable_is_fetched() { - fetched | is_listed "`latest_stable`" -} + def make *opts, &block + block ||= lambda {|rd| IO::copy_stream rd, STDOUT } + dir = @dir.to_s + rd, wr = IO.pipe + pid = fork do + STDOUT.reopen wr + rd.close + exec 'make', '-C', dir, *opts + end + wr.close + wr = nil + reader = Thread.new { block.call rd } + Process.waitpid pid + raise Base::MakeFailed, "make #{opts.join ' '}" unless 0 == $?.exitstatus + reader.value + ensure + rd.close if rd + wr.close if wr + end -update_available() { #@ prints the latest stable available version on kernel.org if it isn't fetched yet - [ -z "`latest_stable_is_fetched`" ] -} + def version + @version ||= make '-is', 'kernelversion' do |rd| + Versionomy.parse rd.readlines.join.chomp + end + end -download_uri() { #@ [V] prints the uri of V (default: latest_stable) - echo "${SOURCE_BASE_URI}/linux-${1:-`latest_stable`}.tar.xz" -} + def config + dir + '.config' + end -download() { #@ [V] downloads the kernel of V (default: latest_stable) - echo "Download $1" >&2 - curl `download_uri "${1}"` -} + def configured? + return @configured if @configured + @configured = config.exist? + end -unpack() { #@ simple unpack with unxz and tar in \$SOURCE_BASE_DIR (default: $SOURCE_BASE_DIR) - unxz | tar -C "${SOURCE_BASE_DIR}" -xf - -} + def <=>( other) version <=> other.version end -fetch() { #@ [V] downloads and unpack the kernel of V (default: latest_stable) - download "${1}" | unpack -} + def to_s + r = "#{dir}" + r += " #{version}" if @version + r += " #{configured? ? :configured : 'not configured'}" if nil != @configured + r + end -update() { #@ downloads and unpack the latest_stable kernel if it isn't fetched yet - if update_available - then - echo Update available: `latest_stable` - fetch - else - echo "Upto date." - return 1 - fi -} + def open_config opts = nil, &block + opts ||= 'r' + if block_given? + File.open config, opts, &block + else + File.open config, opts + end + end -configs() { - fetched | while read v d - do - [ -e "$d/.config" ] && echo "$v $d" - done -} + def import_config_from_io( io) open_config('w') {|c| io.each_line {|l| c.print l } } end -has_config() { #@ [V] This kernel has a config? - configs | is_listed "$2" -} + def import_config file_or_io_or_fetched + info "Import config #{file_or_io_or_fetched}" + case file_or_io_or_fetched + when IO then import_config_from_io file_or_io_or_fetched + when Fetched + file_or_io_or_fetched.open_config &method(:import_config_from_io) + else + File.open file_or_io_or_fetched.to_s, &method(:import_config_from_io) + end + end -latest_config() { #@ Which is the latest kernel with config? - configs | latest_version -} + def oldconfig + info 'make oldconfig' + make 'oldconfig' + end -copy_to_dir_config() { - cp "${2:-`latest_config | print_dir`}/.config" "${1:-`latest_fetched | print_dir`}" -} + def menuconfig + info 'make menuconfig' + make 'menuconfig' + end -install_config() { #@ [V] Copys the config of the newest kernel with config to V. - d="${1:-`latest_fetched | print_dir`}" - s="${2:-`latest_config | print_dir`}" - [ -e "$d/.config" ] || copy_to_dir_config "${d}" "${s}" -} + def compile + info 'make all' + make 'all' + end -oldconfig() { #@ [V] Run oldconfig (calls install_config previeusly). - d="${1:-`latest_fetched | print_dir`}" - install_config "$d" && make -C "$d" oldconfig -} + def install + info 'make modules_install install' + make 'modules_install', 'install' + end -install_all() { - echo "=====-- $1 --=====" - d="${1:-`latest_fetched | print_dir`}" - echo "===== $d =====" - oldconfig "$d" && \ - make -j3 -C "$d" all && \ - make -j3 -C "$d" modules_install && \ - make -j3 -C "$d" install -} + def info text + STDERR.puts "[#{version}] #{text}" + end + end -cmd="$1" -shift + class Base + class Error &2 - exit 1 - ;; -esac + def releases_uri=( uri) @releases_uri = URI.parse uri.to_s end + def sources_base_dir=( dir) @sources_base_dir = Pathname.new dir.to_s end + def cache_dir=( dir) @cache_dir = Pathname.new dir.to_s end + + def initialize + self.releases_uri = ENV['LINUX_RELEASE_URI'] || ReleasesURI + self.sources_base_dir = ENV['LINUX_SOURCES_BASE_DIR'] || SourcesBaseDir + self.cache_dir = ENV['CacheDir'] || CacheDir + end + + def info text + STDERR.puts text + end + + def releases + return @releases if @releases + res = Excon.get @releases_uri.to_s, expects: 200 + json = JSON.parse res.body + @releases = json['releases'].map {|r| Release.parse r }.compact + end + + def releases_moniker moniker = nil + moniker ? releases.select {|r| moniker == r.moniker } : releases + end + + def fetched + @fetched ||= Dir[ @sources_base_dir + 'linux-*']. + map( &Pathname.method( :new)). + select( &:directory?). + map {|d| Fetched.new d } + end + + def configured() fetched.select &:configured? end + def unconfigured() fetched.reject &:configured? end + + def find_fetched_version version + case version + when Fetched then version + when Versionomy::Value then fetched.find {|f| version == f.version } + when Release then find_fetched_version version.version + when String then find_fetched_version Versionomy.parse( version) + when nil, false then fetched.max + else raise InvalidVersionType, "I know Fetched, Versionomy, Release and String, but what is #{version.class}?" + end + end + + def exist? file + Pathname.new( file.to_s).exist? + end + + def format_bytes bytes + case bytes + when 0...1.kilobyte then "%6dB" % bytes + when 0...1.megabyte then "%4dKiB" % (bytes / 1.kilobyte) + when 0...1.gigabyte then "%4dMiB" % (bytes / 1.megabyte) + when 0...1.terabyte then "%4dGiB" % (bytes / 1.gigabyte) + when 0...1.petabyte then "%4dTiB" % (bytes / 1.terabyte) + else "%4dEiB" % (bytes / 1.petabyte) + end + end + + def _download uri, file + dest = Pathname.new "#{file}.download" + info "Download #{uri} => #{file}" + if true + raise DownloadFailed, uri unless Kernel.system( 'wget', '-c', '-O', dest.to_s, uri.to_s) + else + done = dest.size + p dest => done + dest.open 'a+' do |fd| + streamer = lambda do |chunk, remaining, total| + fd.write chunk + count = total - remaining + STDERR.print "\rloading %s/%s % 3d%%\e[J" % [ + format_bytes(count), format_bytes(total), 100.0*count/total ] + end + res = Excon.get uri.to_s, + response_block: streamer, + expects: 200, + headers: {'Range' => "#{done}-" } + end + end + dest.rename file + end + + def _unpack tarball, destdir + info "Unpack #{tarball} => #{destdir}" + unless Kernel.system 'tar', '-C', destdir.to_s, '-xf', tarball.to_s + raise UnpackFailed, tarball + end + end + + # returns tarballs-filename (e.g. linux-3.1.0.tar.xz) + # unpack-path will be @sources_base_dir, but sources dir is unknown. + def download release_or_uri + uri = + case release_or_uri + when Release then release_or_uri.source + when URI, String then URI.parse release_or_uri.to_s + else raise UnexpectedThingToDownload, "This is no URI, String or Release" + end + # We do not understand anything else than operating systems with / as separator + @cache_dir.mkdir 0755 unless @cache_dir.exist? + tarball = @cache_dir + File.basename( uri.path) + _download uri, tarball unless tarball.exist? + _unpack tarball, @sources_base_dir + tarball.basename + end + + def oldconfig_prepare version = nil, config = nil + version = find_fetched_version version + config = + case config + when lambda {|x| Pathname.new( config.to_s).exist? } then config + when nil, false then configured.max.config + else find_fetched_version( config).config + end + [version, config] + end + end + + class Cmd < Thor + class Error < Exception + end + class NoAvailableRelease < Error + end + class InvalidVersionType < Error + end + + option :latest, type: :boolean, aliases: '-l', desc: 'Only the most actual linux kernel.' + option :moniker, type: :string, aliases: '-m', desc: 'stable, mainline, longterm (default: no moniker)' + desc 'releases [MONIKER]', 'Prints known linux-kernel releases' + def releases moniker = nil + listing base.releases_moniker( moniker || options[:moniker]) + end + + option :latest, type: :boolean, aliases: '-l', desc: 'Only the most actual linux kernel.' + desc 'fetched', 'Prints all fetched linux-kernel' + def fetched + listing base.fetched + end + + option :print, type: :boolean, aliases: '-p', desc: 'Only print the URI. No fetch.' + option :any, type: :boolean, aliases: '-a', desc: 'Select any versions.' + option :longterm, type: :boolean, aliases: '-o', desc: 'Select long term versions.' + option :stable, type: :boolean, aliases: '-s', desc: 'Select stable versions (default).' + option :mainline, type: :boolean, aliases: '-m', desc: 'Select mainline versions.' + desc 'fetch [VERSION]', 'Download linux-kernel' + def fetch version = nil + rs = nil + if version + version = Versionomy.parse version + rs = base.releases.select! {|r| version == r.version } + else + moniker = :stable + moniker = :mainline if options[:mainline] + moniker = nil if options[:any] + rs = base.releases_moniker moniker.to_s + end + release = rs.max + raise NoAvailableRelease, "There is no available release which matchs your wishes." unless release + if options[:print] + puts release.source + return + end + base.download release + end + + desc 'importconfig [VERSION] [CONFIG]', 'Imports an other config from file or an other source directory. (default: most actual version with config to most actual version).' + def importconfig version = nil, config = nil + version, config = base.oldconfig_prepare( version, options[:config]) + version.import_config config if config + end + + option :config, type: :string, aliases: '-c', default: false, + desc: 'Which pre existing config should be used? Can be an other linux-VERSION with an old config or a config-file. --no-config will prevent copying a config.' + desc 'oldconfig [VERSION]', 'Configure linux-VERSION (default: most actual version).' + long_desc <<-ELD + First it will copy an older config to your sources-directory, if needed and not --no-config. + If you use `--config CONFIG`, the existing config will be replaced by CONFIG! + Second make oldconfig will called. + ELD + def oldconfig version = nil + version, config = base.oldconfig_prepare( version, options[:config]) + version.import_config config if nil != options['config'] and config and not version.config.exist? + version.oldconfig + end + + option :config, type: :string, aliases: '-c', default: false, + desc: 'Which pre existing config should be used? Can be an other linux-VERSION with an old config or a config-file. --no-config will prevent copying a config.' + desc 'oldconfig [VERSION]', 'Configure linux-VERSION (default: most actual version).' + long_desc <<-ELD + First it will copy an older config to your sources-directory, if needed and not --no-config. + If you use `--config CONFIG`, the existing config will be replaced by CONFIG! + Second make oldconfig will called. + ELD + desc 'menuconfig|configure [VERSION]', 'Configure your linux-VERSION. (default: most actual version).' + def menuconfig version = nil + version, config = base.oldconfig_prepare( version, options[:config]) + version.import_config config if nil != options['config'] and config and not version.config.exist? + version.menuconfig + end + map configure: :menuconfig + + desc 'compile [VERSION]', 'Will compile kernel and modules.' + def compile version = nil + version = base.find_fetched_version version + version.compile + end + + desc 'install [VERSION]', 'Will install kernel and modules. It will trigger updating third-party-modules.' + def install version = nil + version = base.find_fetched_version version + version.install + end + + desc 'all [VERSION]', 'Will oldconfig, compile and install kernel and modules. See these methods.' + def all version = nil + version, config = base.oldconfig_prepare( version, options[:config]) + version.import_config config if nil != options['config'] and config and not version.config.exist? + version.oldconfig + version.compile + version.install + end + + option :any, type: :boolean, aliases: '-a', desc: 'Select any versions.' + option :longterm, type: :boolean, aliases: '-o', desc: 'Select long term versions.' + option :stable, type: :boolean, aliases: '-s', desc: 'Select stable versions (default).' + option :mainline, type: :boolean, aliases: '-m', desc: 'Select mainline versions.' + desc 'update [VERSION]', 'Download, compile and install linux-kernel' + def update version = nil + tarball = fetch version + /^linux-(.*)\.tar\./ =~ tarball.basename.to_s + # try it with version in tarball's name: + all $1 + end + + no_commands do + def base + @base ||= Base.new + end + + def listing list + list.each do |e| + e.configured? if e.is_a? Fetched + end + if options[:latest] + puts list.max + else + puts list.sort {|a,b|b<=>a} + end + end + end + end +end + +begin # if __FILE__ == $0 + $debug = true if $DEBUG + LinuxUpdate::Cmd.start ARGV +rescue LinuxUpdate::Cmd::Error, LinuxUpdate::Base::Error + STDERR.puts "Error: #{$!}" + STDERR.puts $!.backtrace.map {|c| "\t#{c}" } if $debug + raise + #exit 1 +rescue Object + STDERR.puts "Unknown and unexpected Error: #{$!} (#{$!.class})" + STDERR.puts $!.backtrace.map {|c| "\t#{c}" } if $debug + raise + #exit 2 +end if __FILE__ == $0 diff --git a/linux-update.rb b/linux-update.rb deleted file mode 100755 index 988663a..0000000 --- a/linux-update.rb +++ /dev/null @@ -1,497 +0,0 @@ -#!/usr/bin/env ruby - -require 'shellwords' -require 'getoptlong' -require 'json' -require 'pathname' -require 'shell' -require 'uri' - -class RequiredGems - attr_reader :requires, :failed - def self.require &block - rg = new - block.call rg, &rg.method(:push) - rg.require - end - - def initialize - @requires, @failed = [], [] - end - - def push lib, gem = nil, name = nil - gem ||= lib - name ||= gem - @requires.push [lib, gem, name] - end - - def try_require lib - require lib - true - rescue LoadError - false - end - - def require lib = nil - return super lib if lib # if lib given, require it. - - @failed = @requires.reject {|(lib, _, _)| try_require lib } - return if @failed.empty? - STDERR.puts <( other) version <=> other.version end - - def to_s - r = "linux-#{version} (#{moniker})" - r += " (EOL)" if end_of_life? - r - end - end - - class Fetched - attr_reader :dir - def initialize dir - @dir = Pathname.new dir - end - - def make *opts, &block - block ||= lambda {|rd| IO::copy_stream rd, STDOUT } - dir = @dir.to_s - rd, wr = IO.pipe - pid = fork do - STDOUT.reopen wr - rd.close - exec 'make', '-C', dir, *opts - end - wr.close - wr = nil - reader = Thread.new { block.call rd } - Process.waitpid pid - raise Base::MakeFailed, "make #{opts.join ' '}" unless 0 == $?.exitstatus - reader.value - ensure - rd.close if rd - wr.close if wr - end - - def version - @version ||= make '-is', 'kernelversion' do |rd| - Versionomy.parse rd.readlines.join.chomp - end - end - - def config - dir + '.config' - end - - def configured? - return @configured if @configured - @configured = config.exist? - end - - def <=>( other) version <=> other.version end - - def to_s - r = "#{dir}" - r += " #{version}" if @version - r += " #{configured? ? :configured : 'not configured'}" if nil != @configured - r - end - - def open_config opts = nil, &block - opts ||= 'r' - if block_given? - File.open config, opts, &block - else - File.open config, opts - end - end - - def import_config_from_io( io) open_config('w') {|c| io.each_line {|l| c.print l } } end - - def import_config file_or_io_or_fetched - info "Import config #{file_or_io_or_fetched}" - case file_or_io_or_fetched - when IO then import_config_from_io file_or_io_or_fetched - when Fetched - file_or_io_or_fetched.open_config &method(:import_config_from_io) - else - File.open file_or_io_or_fetched.to_s, &method(:import_config_from_io) - end - end - - def oldconfig - info 'make oldconfig' - make 'oldconfig' - end - - def menuconfig - info 'make menuconfig' - make 'menuconfig' - end - - def compile - info 'make all' - make 'all' - end - - def install - info 'make modules_install install' - make 'modules_install', 'install' - end - - def info text - STDERR.puts "[#{version}] #{text}" - end - end - - class Base - class Error #{file}" - if true - raise DownloadFailed, uri unless Kernel.system( 'wget', '-c', '-O', dest.to_s, uri.to_s) - else - done = dest.size - p dest => done - dest.open 'a+' do |fd| - streamer = lambda do |chunk, remaining, total| - fd.write chunk - count = total - remaining - STDERR.print "\rloading %s/%s % 3d%%\e[J" % [ - format_bytes(count), format_bytes(total), 100.0*count/total ] - end - res = Excon.get uri.to_s, - response_block: streamer, - expects: 200, - headers: {'Range' => "#{done}-" } - end - end - dest.rename file - end - - def _unpack tarball, destdir - info "Unpack #{tarball} => #{destdir}" - unless Kernel.system 'tar', '-C', destdir.to_s, '-xf', tarball.to_s - raise UnpackFailed, tarball - end - end - - # returns tarballs-filename (e.g. linux-3.1.0.tar.xz) - # unpack-path will be @sources_base_dir, but sources dir is unknown. - def download release_or_uri - uri = - case release_or_uri - when Release then release_or_uri.source - when URI, String then URI.parse release_or_uri.to_s - else raise UnexpectedThingToDownload, "This is no URI, String or Release" - end - # We do not understand anything else than operating systems with / as separator - @cache_dir.mkdir 0755 unless @cache_dir.exist? - tarball = @cache_dir + File.basename( uri.path) - _download uri, tarball unless tarball.exist? - _unpack tarball, @sources_base_dir - tarball.basename - end - - def oldconfig_prepare version = nil, config = nil - version = find_fetched_version version - config = - case config - when lambda {|x| Pathname.new( config.to_s).exist? } then config - when nil, false then configured.max.config - else find_fetched_version( config).config - end - [version, config] - end - end - - class Cmd < Thor - class Error < Exception - end - class NoAvailableRelease < Error - end - class InvalidVersionType < Error - end - - option :latest, type: :boolean, aliases: '-l', desc: 'Only the most actual linux kernel.' - option :moniker, type: :string, aliases: '-m', desc: 'stable, mainline, longterm (default: no moniker)' - desc 'releases [MONIKER]', 'Prints known linux-kernel releases' - def releases moniker = nil - listing base.releases_moniker( moniker || options[:moniker]) - end - - option :latest, type: :boolean, aliases: '-l', desc: 'Only the most actual linux kernel.' - desc 'fetched', 'Prints all fetched linux-kernel' - def fetched - listing base.fetched - end - - option :print, type: :boolean, aliases: '-p', desc: 'Only print the URI. No fetch.' - option :any, type: :boolean, aliases: '-a', desc: 'Select any versions.' - option :longterm, type: :boolean, aliases: '-o', desc: 'Select long term versions.' - option :stable, type: :boolean, aliases: '-s', desc: 'Select stable versions (default).' - option :mainline, type: :boolean, aliases: '-m', desc: 'Select mainline versions.' - desc 'fetch [VERSION]', 'Download linux-kernel' - def fetch version = nil - rs = nil - if version - version = Versionomy.parse version - rs = base.releases.select! {|r| version == r.version } - else - moniker = :stable - moniker = :mainline if options[:mainline] - moniker = nil if options[:any] - rs = base.releases_moniker moniker.to_s - end - release = rs.max - raise NoAvailableRelease, "There is no available release which matchs your wishes." unless release - if options[:print] - puts release.source - return - end - base.download release - end - - desc 'importconfig [VERSION] [CONFIG]', 'Imports an other config from file or an other source directory. (default: most actual version with config to most actual version).' - def importconfig version = nil, config = nil - version, config = base.oldconfig_prepare( version, options[:config]) - version.import_config config if config - end - - option :config, type: :string, aliases: '-c', default: false, - desc: 'Which pre existing config should be used? Can be an other linux-VERSION with an old config or a config-file. --no-config will prevent copying a config.' - desc 'oldconfig [VERSION]', 'Configure linux-VERSION (default: most actual version).' - long_desc <<-ELD - First it will copy an older config to your sources-directory, if needed and not --no-config. - If you use `--config CONFIG`, the existing config will be replaced by CONFIG! - Second make oldconfig will called. - ELD - def oldconfig version = nil - version, config = base.oldconfig_prepare( version, options[:config]) - version.import_config config if nil != options['config'] and config and not version.config.exist? - version.oldconfig - end - - option :config, type: :string, aliases: '-c', default: false, - desc: 'Which pre existing config should be used? Can be an other linux-VERSION with an old config or a config-file. --no-config will prevent copying a config.' - desc 'oldconfig [VERSION]', 'Configure linux-VERSION (default: most actual version).' - long_desc <<-ELD - First it will copy an older config to your sources-directory, if needed and not --no-config. - If you use `--config CONFIG`, the existing config will be replaced by CONFIG! - Second make oldconfig will called. - ELD - desc 'menuconfig|configure [VERSION]', 'Configure your linux-VERSION. (default: most actual version).' - def menuconfig version = nil - version, config = base.oldconfig_prepare( version, options[:config]) - version.import_config config if nil != options['config'] and config and not version.config.exist? - version.menuconfig - end - map configure: :menuconfig - - desc 'compile [VERSION]', 'Will compile kernel and modules.' - def compile version = nil - version = base.find_fetched_version version - version.compile - end - - desc 'install [VERSION]', 'Will install kernel and modules. It will trigger updating third-party-modules.' - def install version = nil - version = base.find_fetched_version version - version.install - end - - desc 'all [VERSION]', 'Will oldconfig, compile and install kernel and modules. See these methods.' - def all version = nil - version, config = base.oldconfig_prepare( version, options[:config]) - version.import_config config if nil != options['config'] and config and not version.config.exist? - version.oldconfig - version.compile - version.install - end - - option :any, type: :boolean, aliases: '-a', desc: 'Select any versions.' - option :longterm, type: :boolean, aliases: '-o', desc: 'Select long term versions.' - option :stable, type: :boolean, aliases: '-s', desc: 'Select stable versions (default).' - option :mainline, type: :boolean, aliases: '-m', desc: 'Select mainline versions.' - desc 'update [VERSION]', 'Download, compile and install linux-kernel' - def update version = nil - tarball = fetch version - /^linux-(.*)\.tar\./ =~ tarball.basename.to_s - # try it with version in tarball's name: - all $1 - end - - no_commands do - def base - @base ||= Base.new - end - - def listing list - list.each do |e| - e.configured? if e.is_a? Fetched - end - if options[:latest] - puts list.max - else - puts list.sort {|a,b|b<=>a} - end - end - end - end -end - -begin # if __FILE__ == $0 - $debug = true if $DEBUG - LinuxUpdate::Cmd.start ARGV -rescue LinuxUpdate::Cmd::Error, LinuxUpdate::Base::Error - STDERR.puts "Error: #{$!}" - STDERR.puts $!.backtrace.map {|c| "\t#{c}" } if $debug - raise - #exit 1 -rescue Object - STDERR.puts "Unknown and unexpected Error: #{$!} (#{$!.class})" - STDERR.puts $!.backtrace.map {|c| "\t#{c}" } if $debug - raise - #exit 2 -end if __FILE__ == $0