mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	This makes auditing for inadvertant command execution easier. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@61790 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
		
			
				
	
	
		
			243 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			243 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
#
 | 
						|
# iseq_loader.rb - sample of compiler/loader for binary compiled file
 | 
						|
#
 | 
						|
# Usage as a compiler: ruby iseq_loader.rb [file or directory] ...
 | 
						|
#
 | 
						|
#   It compiles and stores specified files.
 | 
						|
#   If directories are specified, then compiles and stores all *.rb files.
 | 
						|
#   (using Dir.glob)
 | 
						|
#
 | 
						|
# TODO: add remove option
 | 
						|
# TODO: add verify option
 | 
						|
#
 | 
						|
# Usage as a loader: simply require this file with the following setting.
 | 
						|
#
 | 
						|
# Setting with environment variables.
 | 
						|
#
 | 
						|
#  * RUBY_ISEQ_LOADER_STORAGE to select storage type
 | 
						|
#    * dbm: use dbm
 | 
						|
#    * fs: [default] use file system. locate a compiled binary files in same
 | 
						|
#      directory of scripts like Rubinius. foo.rb.yarb will be created for foo.rb.
 | 
						|
#    * fs2: use file system. locate compiled file in specified directory.
 | 
						|
#    * nothing: do nothing.
 | 
						|
#
 | 
						|
#  * RUBY_ISEQ_LOADER_STORAGE_DIR to select directory
 | 
						|
#    * default: ~/.ruby_binaries/
 | 
						|
#
 | 
						|
#  * RUBY_ISEQ_LOADER_STORAGE_COMPILE_IF_NOT_COMPILED
 | 
						|
#    * true: store compiled file if compiled data is not available.
 | 
						|
#    * false: [default] do nothing if there is no compiled iseq data.
 | 
						|
 | 
						|
class RubyVM::InstructionSequence
 | 
						|
  $ISEQ_LOADER_LOADED = 0
 | 
						|
  $ISEQ_LOADER_COMPILED = 0
 | 
						|
  $ISEQ_LOADER_IGNORED = 0
 | 
						|
  LAUNCHED_TIME = Time.now
 | 
						|
  COMPILE_FILE_ENABLE = false || true
 | 
						|
  COMPILE_VERBOSE = $VERBOSE || false # || true
 | 
						|
  COMPILE_DEBUG = ENV['RUBY_ISEQ_LOADER_DEBUG']
 | 
						|
  COMPILE_IF_NOT_COMPILED = ENV['RUBY_ISEQ_LOADER_STORAGE_COMPILE_IF_NOT_COMPILED'] == 'true'
 | 
						|
 | 
						|
  at_exit{
 | 
						|
    STDERR.puts "[ISEQ_LOADER] #{Process.pid} time: #{Time.now - LAUNCHED_TIME}, " +
 | 
						|
                "loaded: #{$ISEQ_LOADER_LOADED}, " +
 | 
						|
                "compiled: #{$ISEQ_LOADER_COMPILED}, " +
 | 
						|
                "ignored: #{$ISEQ_LOADER_IGNORED}"
 | 
						|
  } if COMPILE_VERBOSE
 | 
						|
 | 
						|
  unless cf_dir = ENV['RUBY_ISEQ_LOADER_STORAGE_DIR']
 | 
						|
    cf_dir = File.expand_path("~/.ruby_binaries")
 | 
						|
    unless File.exist?(cf_dir)
 | 
						|
      Dir.mkdir(cf_dir)
 | 
						|
    end
 | 
						|
  end
 | 
						|
  CF_PREFIX = "#{cf_dir}/cb."
 | 
						|
 | 
						|
  class NullStorage
 | 
						|
    def load_iseq fname; end
 | 
						|
    def compile_and_save_isq fname; end
 | 
						|
    def unlink_compiled_iseq; end
 | 
						|
  end
 | 
						|
 | 
						|
  class BasicStorage
 | 
						|
    def initialize
 | 
						|
      require 'digest/sha1'
 | 
						|
    end
 | 
						|
 | 
						|
    def load_iseq fname
 | 
						|
      iseq_key = iseq_key_name(fname)
 | 
						|
      if compiled_iseq_exist?(fname, iseq_key) && compiled_iseq_is_younger?(fname, iseq_key)
 | 
						|
        $ISEQ_LOADER_LOADED += 1
 | 
						|
        STDERR.puts "[ISEQ_LOADER] #{Process.pid} load #{fname} from #{iseq_key}" if COMPILE_DEBUG
 | 
						|
        binary = read_compiled_iseq(fname, iseq_key)
 | 
						|
        iseq = RubyVM::InstructionSequence.load_from_binary(binary)
 | 
						|
        # p [extra_data(iseq.path), RubyVM::InstructionSequence.load_from_binary_extra_data(binary)]
 | 
						|
        # raise unless extra_data(iseq.path) == RubyVM::InstructionSequence.load_from_binary_extra_data(binary)
 | 
						|
        iseq
 | 
						|
      elsif COMPILE_IF_NOT_COMPILED
 | 
						|
        compile_and_save_iseq(fname, iseq_key)
 | 
						|
      else
 | 
						|
        $ISEQ_LOADER_IGNORED += 1
 | 
						|
        # p fname
 | 
						|
        nil
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    def extra_data fname
 | 
						|
      "SHA-1:#{::Digest::SHA1.file(fname).digest}"
 | 
						|
    end
 | 
						|
 | 
						|
    def compile_and_save_iseq fname, iseq_key = iseq_key_name(fname)
 | 
						|
      $ISEQ_LOADER_COMPILED += 1
 | 
						|
      STDERR.puts "[RUBY_COMPILED_FILE] compile #{fname}" if COMPILE_DEBUG
 | 
						|
      iseq = RubyVM::InstructionSequence.compile_file(fname)
 | 
						|
 | 
						|
      binary = iseq.to_binary(extra_data(fname))
 | 
						|
      write_compiled_iseq(fname, iseq_key, binary)
 | 
						|
      iseq
 | 
						|
    end
 | 
						|
 | 
						|
    # def unlink_compiled_iseq; nil; end # should implement at sub classes
 | 
						|
 | 
						|
    private
 | 
						|
 | 
						|
    def iseq_key_name fname
 | 
						|
      fname
 | 
						|
    end
 | 
						|
 | 
						|
    # should implement at sub classes
 | 
						|
    # def compiled_iseq_younger? fname, iseq_key; end
 | 
						|
    # def compiled_iseq_exist? fname, iseq_key; end
 | 
						|
    # def read_compiled_file fname, iseq_key; end
 | 
						|
    # def write_compiled_file fname, iseq_key, binary; end
 | 
						|
  end
 | 
						|
 | 
						|
  class FSStorage < BasicStorage
 | 
						|
    def initialize
 | 
						|
      super
 | 
						|
      require 'fileutils'
 | 
						|
      @dir = CF_PREFIX + "files"
 | 
						|
      unless File.directory?(@dir)
 | 
						|
        FileUtils.mkdir_p(@dir)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    def unlink_compiled_iseq
 | 
						|
      File.unlink(compile_file_path)
 | 
						|
    end
 | 
						|
 | 
						|
    private
 | 
						|
 | 
						|
    def iseq_key_name fname
 | 
						|
      "#{fname}.yarb" # same directory
 | 
						|
    end
 | 
						|
 | 
						|
    def compiled_iseq_exist? fname, iseq_key
 | 
						|
      File.exist?(iseq_key)
 | 
						|
    end
 | 
						|
 | 
						|
    def compiled_iseq_is_younger? fname, iseq_key
 | 
						|
      File.mtime(iseq_key) >= File.mtime(fname)
 | 
						|
    end
 | 
						|
 | 
						|
    def read_compiled_iseq fname, iseq_key
 | 
						|
      File.open(iseq_key, 'rb'){|f| f.read}
 | 
						|
    end
 | 
						|
 | 
						|
    def write_compiled_iseq fname, iseq_key, binary
 | 
						|
      File.open(iseq_key, 'wb'){|f| f.write(binary)}
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  class FS2Storage < FSStorage
 | 
						|
    def iseq_key_name fname
 | 
						|
      @dir + fname.gsub(/[^A-Za-z0-9\._-]/){|c| '%02x' % c.ord} # special directory
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  class DBMStorage < BasicStorage
 | 
						|
    def initialize
 | 
						|
      require 'dbm'
 | 
						|
      @db = DBM.open(CF_PREFIX+'db')
 | 
						|
    end
 | 
						|
 | 
						|
    def unlink_compiled_iseq
 | 
						|
      @db.delete fname
 | 
						|
    end
 | 
						|
 | 
						|
    private
 | 
						|
 | 
						|
    def date_key_name fname
 | 
						|
      "date.#{fname}"
 | 
						|
    end
 | 
						|
 | 
						|
    def iseq_key_name fname
 | 
						|
      "body.#{fname}"
 | 
						|
    end
 | 
						|
 | 
						|
    def compiled_iseq_exist? fname, iseq_key
 | 
						|
      @db.has_key? iseq_key
 | 
						|
    end
 | 
						|
 | 
						|
    def compiled_iseq_is_younger? fname, iseq_key
 | 
						|
      date_key = date_key_name(fname)
 | 
						|
      if @db.has_key? date_key
 | 
						|
        @db[date_key].to_i >= File.mtime(fname).to_i
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    def read_compiled_iseq fname, iseq_key
 | 
						|
      @db[iseq_key]
 | 
						|
    end
 | 
						|
 | 
						|
    def write_compiled_iseq fname, iseq_key, binary
 | 
						|
      date_key = date_key_name(fname)
 | 
						|
      @db[iseq_key] = binary
 | 
						|
      @db[date_key] = Time.now.to_i
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  STORAGE = case ENV['RUBY_ISEQ_LOADER_STORAGE']
 | 
						|
            when 'dbm'
 | 
						|
              DBMStorage.new
 | 
						|
            when 'fs'
 | 
						|
              FSStorage.new
 | 
						|
            when 'fs2'
 | 
						|
              FS2Storage.new
 | 
						|
            when 'null'
 | 
						|
              NullStorage.new
 | 
						|
            else
 | 
						|
              FSStorage.new
 | 
						|
            end
 | 
						|
 | 
						|
  STDERR.puts "[ISEQ_LOADER] use #{STORAGE.class} " if COMPILE_VERBOSE
 | 
						|
 | 
						|
  def self.load_iseq fname
 | 
						|
    STORAGE.load_iseq(fname)
 | 
						|
  end
 | 
						|
 | 
						|
  def self.compile_and_save_iseq fname
 | 
						|
    STORAGE.compile_and_save_iseq fname
 | 
						|
  end
 | 
						|
 | 
						|
  def self.unlink_compiled_iseq fname
 | 
						|
    STORAGE.unlink_compiled_iseq fname
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
if __FILE__ == $0
 | 
						|
  ARGV.each{|path|
 | 
						|
    if File.directory?(path)
 | 
						|
      pattern = File.join(path, '**/*.rb')
 | 
						|
      Dir.glob(pattern){|file|
 | 
						|
        begin
 | 
						|
          RubyVM::InstructionSequence.compile_and_save_iseq(file)
 | 
						|
        rescue SyntaxError => e
 | 
						|
          STDERR.puts e
 | 
						|
        end
 | 
						|
      }
 | 
						|
    else
 | 
						|
      RubyVM::InstructionSequence.compile_and_save_iseq(path)
 | 
						|
    end
 | 
						|
  }
 | 
						|
end
 |