lib/memory_io/util.rb
# frozen_string_literal: true require 'ostruct' require 'dentaku' module MemoryIO # Defines utility methods. module Util module_function # Convert input into snake-case. # # This method also converts +'::'+ to +'/'+. # # @param [String] str # String to be converted. # # @return [String] # Converted string. # # @example # Util.underscore('MemoryIO') # #=> 'memory_io' # # Util.underscore('MyModule::MyClass') # #=> 'my_module/my_class' def underscore(str) return '' if str.empty? str = str.gsub('::', '/') str.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2') str.gsub!(/([a-z\d])([A-Z])/, '\1_\2') str.downcase! str end # @api private # # @param [String] file # File name. # # @return [#readable?, #writable?, nil] # Struct with two boolean method. # +nil+ for file not exists or is inaccessible. def file_permission(file) return nil unless File.file?(file) stat = File.stat(file) # we do a trick here because /proc/[pid]/mem might be marked as readable but fails at sysopen. os = OpenStruct.new(readable?: stat.readable_real?, writable?: stat.writable_real?) begin os.readable? && File.open(file, 'rb').close rescue Errno::EACCES os[:readable?] = false end begin os.writable? && File.open(file, 'wb').close rescue Errno::EACCES os[:writable?] = false end os end # Evaluate string safely. # # @param [String] str # String to be evaluated. # @param [{Symbol => Integer}] vars # Predefined variables # # @return [Integer] # Result. # # @example # Util.safe_eval('heap + 0x10 * pp', heap: 0xde00, pp: 8) # #=> 56960 # 0xde80 def safe_eval(str, **vars) return str if str.is_a?(Integer) # dentaku 2 doesn't support hex str = str.gsub(/0x[0-9a-zA-Z]+/) { |c| c.to_i(16) } Dentaku::Calculator.new.store(vars).evaluate(str) end # Unpack a string into an integer. # Little endian is used. # # @param [String] str # String. # # @return [Integer] # Result. # # @example # Util.unpack("\xff") # #=> 255 # Util.unpack("@\xE2\x01\x00") # #=> 123456 def unpack(str) str.bytes.reverse.reduce(0) { |s, c| s * 256 + c } end # Pack an integer into +b+ bytes. # Little endian is used. # # @param [Integer] val # The integer to pack. # If +val+ contains more than +b+ bytes, # only lower +b+ bytes in +val+ will be packed. # # @param [Integer] b # # @return [String] # Packing result with length +b+. # # @example # Util.pack(0x123, 4) # #=> "\x23\x01\x00\x00" def pack(val, b) Array.new(b) { |i| (val >> (i * 8)) & 0xff }.pack('C*') end # Remove extension name (.so) and version in library name. # # @param [String] name # Original library filename. # # @return [String] # Name without version and '.so'. # # @example # Util.trim_libname('libc-2.24.so') # #=> 'libc' # Util.trim_libname('libcrypto.so.1.0.0') # #=> 'libcrypto' # Util.trim_libname('not_a_so') # #=> 'not_a_so' def trim_libname(name) type1 = '(-[\d.]+)?\.so$' type2 = '\.so.\d+[\d.]+$' name.sub(/#{type1}|#{type2}/, '') end end end