#!/usr/bin/env ruby # # builtin # require 'rbconfig' major, minor, teeny = %w(MAJOR MINOR TEENY).map{|k| Config::CONFIG[k].to_i} unless major >= 1 and minor >= 8 STDERR.puts "ruby-1.8.0 or greater is required, you are running " STDERR.puts "check your PATH setting or install ruby-1.8.0 or greater" exit 1 end require 'optparse' require 'logger' require 'yaml' require 'pp' # # RAA # require 'lockfile.rb' class Main #{{{ VERSION = '0.0.0' PROGNAM = File.basename(File.expand_path($0)) USAGE = #{{{ <<-usage NAME #{ PROGNAM } v#{ VERSION } SYNOPSIS #{ PROGNAM } [options]+ file.lock [program [-- [options]+] [args]+] DESCRIPTTION #{ PROGNAM } creates NFS resistent lockfiles ENVIRONMENT LOCKFILE_DEBUG=1 will show internal actions of the library DIAGNOSTICS success => $? == 0 failure => $? != 0 AUTHOR ara.t.howard@noaa.gov BUGS > 1 OPTIONS usage #}}} EXAMPLES = #{{{ <<-examples EXAMPLES 0) simple usage - just create a file.lock in an atomic fashion ~ > #{ PROGNAM } file.lock 1) safe usage - create a file.lock, execute a command, and remove file.lock ~ > #{ PROGNAM } file.lock ls file.lock 2) same as above, but logging verbose messages ~ > #{ PROGNAM } -v4 file.lock ls file.lock 3) same as above, but logging verbose messages and showing actions internal to lockfile library ~ > #{ PROGNAM } -v4 -d file.lock ls file.lock 4) same as above ~ > LOCKFILE_DEBUG=1 #{ PROGNAM } -v4 file.lock ls file.lock 5) same as above ~ > export LOCKFILE_DEBUG=1 ~ > #{ PROGNAM } -v4 file.lock ls file.lock 6) note that you need to tell the option parser to stop parsing #{ PROGNAM } options if you intend to pass options to 'program' ~ > #{ PROGNAM } -v4 -d file.lock -- ls -ltar file.lock without the '--' #{ PROGNAM } would consume the '-ltar' options, parsing it as the logfile name 'tar' 7) lock file.lock and exec 'program' - remove the file.lock if it is older than 4242 seconds ~ > #{ PROGNAM } --max_age=4242 file.lock program 8) lock file.lock and exec 'program' - remove the file.lock if it is older than 4242 seconds, also spawn a background thread which will refresh file.lock every 8 seonds will 'program' is executing ~ > #{ PROGNAM } --max_age=4242 --refresh=8 file.lock program 9) same as above, but fail if file.lock cannot be obtained within 1 minute ~ > #{ PROGNAM } --max_age=4242 --refresh=8 --timeout=60 file.lock program examples #}}} EXIT_SUCCESS = 0 EXIT_FAILURE = 1 attr :argv attr :op attr :logger attr :config def initialize argv = cp(ARGV) #{{{ @argv = argv parse_opts parse_argv #}}} end def run #{{{ usage and exit EXIT_SUCCESS if @opt_help init_logging debug{ "lockpath <#{ @lockpath }>" } opts = {} %w(retries max_age sleep_inc max_sleep suspend timeout refresh).each do |opt| if((val = eval("opt_#{ opt }"))) begin opts[opt] = Integer val logger.debug{ "<#{ opt }> <#{ val }>" } rescue logger.fatal{ "illegal value <#{ val.inspect }> for opt <#{ opt }>" } exit EXIT_FAILURE end end end opts['debug'] = true if opt_debug begin case @argv.size when 0 opts['dont_clean'] = true logger.debug{ "opts <#{ pretty opts }>" } logger.debug{ "aquiring lock <#{ @lockpath }>..." } # # simple usage - just create the lockfile with opts # lockfile = ::Lockfile.new @lockpath, opts lockfile.lock logger.debug{ "aquired lock <#{ @lockpath }>" } else logger.debug{ "opts <#{ pretty opts }>" } logger.debug{ "aquiring lock <#{ @lockpath }>..." } # # block usage - create the lockfile with opts, run block, rm lockfile # lockfile = ::Lockfile.new @lockpath, opts lockfile.lock do logger.debug{ "aquired lock <#{ @lockpath }>" } cmd = @argv.join ' ' logger.debug{ "cmd <#{ cmd }>" } v = nil begin v = $VERBOSE $VERBOSE = nil system cmd ensure $VERBOSE = v end logger.debug{ "status <#{ $? }>" } end exit $? end rescue => e logger.fatal{ e } exit EXIT_FAILURE end exit EXIT_SUCCESS #}}} end def parse_opts #{{{ @op = OptionParser.new @op.banner = '' define_options #begin @op.parse! argv #rescue OptionParser::InvalidOption => e # preverve unknown options #e.recover(argv) #rescue Exception => e #STDERR.puts PROGNAM #STDERR.puts @op #exit 2 #end #}}} end def parse_argv #{{{ usage and exit EXIT_FAILURE if @argv.empty? @lockpath = @argv.shift #}}} end def usage io = STDOUT #{{{ io << USAGE io << "\n" io << @op io << "\n" io << EXAMPLES if defined? EXAMPLES self #}}} end def define_options #{{{ options = [ %w(--retries=n -r), %w(--max_age=n -a), %w(--sleep_inc=n -s), %w(--max_sleep=n -p), %w(--suspend=n -u), %w(--timeout=n -t), %w(--refresh=n -f), %w(--debug -d), %w(--verbosity=0-4|debug|info|warn|error|fatal -v), %w(--log=path -l), %w(--log_age=log_age), %w(--log_size=log_size), %w(--help -h), ] options.each do |option| opt = option.first.gsub(%r/(?:--)|(?:=.*$)/o,'').strip get, set = opt_attr opt @op.def_option(*option){|v| self.send(set, (v or true))} end #}}} end %w(debug info warn error fatal).each do |m| eval "def #{ m }(*args,&block);@logger.#{ m }(*args,&block);end" end def init_logging #{{{ if @opt_log_age @opt_log_age = @opt_log_age.to_i if @opt_log_age =~ /\d/ end if @opt_log_size @opt_log_size = @opt_log_size.to_i if @opt_log_size =~ /\d/ end $logger = @logger = Logger.new(@opt_log || $stdout, @opt_log_age, @opt_log_size) level = nil @opt_verbosity ||= 'info' @opt_verbosity = case @opt_verbosity when /^\s*(?:4|d|debug)\s*$/io level = 'Logging::DEBUG' 4 when /^\s*(?:3|i|info)\s*$/io level = 'Logging::INFO' 3 when /^\s*(?:2|w|warn)\s*$/io level = 'Logging::WARN' 2 when /^\s*(?:1|e|error)\s*$/io level = 'Logging::ERROR' 1 when /^\s*(?:0|f|fatal)\s*$/io level = 'Logging::FATAL' 0 else abort "illegal verbosity setting <#{ @opt_verbosity }>" end @logger.level = 2 - ((@opt_verbosity % 5) - 2) debug {"logging level <#{ level }>"} #}}} end def opt_attr opt #{{{ get = "opt_#{ opt }" set = "#{ get }=" code = <<-code class << self def #{ get }; defined?(@#{ get }) ? @#{ get } : nil; end def #{ set } value; @#{ get } = value; end end code instance_eval code [get, set] #}}} end def cp obj #{{{ Marshal.load(Marshal.dump(obj)) #}}} end def pretty obj #{{{ PP::pp obj, '' #}}} end #}}} end Main.new(ARGV).run if $0 == __FILE__