require 'binary_struct' require 'util/miq-hash_struct' require 'db/MiqBdb/MiqBdb' require 'util/miq-unicode' # RPM Specification located at: http://jrpm.sourceforge.net/rpmspec/index.html class MiqRpmPackages # # The data types we support. # RPM_INT32_TYPE = 4 RPM_STRING_TYPE = 6 RPM_STRING_ARRAY_TYPE = 8 RPM_I18NSTRING_TYPE = 9 # # The things we care about. # NAME = 1000 VERSION = 1001 RELEASE = 1002 SUMMARY = 1004 DESCRIPTION = 1005 BUILDTIME = 1006 INSTALLTIME = 1008 VENDOR = 1011 GROUP = 1016 URL = 1020 ARCH = 1022 REQUIRES = 1049 TAGIDS = { 1000 => "name", 1001 => "version", 1002 => "release", 1004 => "summary", 1005 => "description", 1006 => "buildtime", 1008 => "installtime", 1011 => "vendor", 1016 => "category", # group 1020 => "url", 1022 => "arch", 1049 => "depends", # requires } # # Nubbers on disk are in network byte order. # RPML_HEADER = BinaryStruct.new([ 'N', 'num_index', "N", 'num_data' ]) RPML_HEADER_LEN = RPML_HEADER.size ENTRY_INFO = BinaryStruct.new([ 'N', 'tag', 'N', 'ttype', 'N', 'offset', 'N', 'count' ]) ENTRY_INFO_LEN = ENTRY_INFO.size def initialize(fs, dbFile) @pkgDb = MiqBerkeleyDB::MiqBdb.new(dbFile, fs) # Pre-read all pages into the bdb cache, as we will be processing all of them anyway. @pkgDb.readAllPages end def each @pkgDb.each_value do |v| next if v.length <= RPML_HEADER_LEN hdr = RPML_HEADER.decode(v) offset = RPML_HEADER_LEN + (ENTRY_INFO_LEN * hdr['num_index']) if v.length != offset + hdr['num_data'] $log.debug "record length = #{v.length}" $log.debug "num_index = #{hdr['num_index']}" $log.debug "num_data = #{hdr['num_data']}" $log.error "Invalid or corrupt RPM database record" next end data = v[offset, hdr['num_data']] pkg = {} eis = ENTRY_INFO.decode(v[RPML_HEADER_LEN..-1], hdr['num_index']) eis.each do |ei| tag = TAGIDS[ei['tag']] next if tag.nil? pkg[tag] = getVal(data, ei) pkg[tag] = convert(tag, pkg[tag]) end pkg['installed'] = true unless pkg.empty? yield(MiqHashStruct.new(pkg)) end end # def each def close @pkgDb.close end private def time_tag?(tag) ['installtime', 'buildtime'].include?(tag) end def convert(tag, val) time_tag?(tag) ? Time.at(val).utc : val end def getVal(data, ei) case ei['ttype'] when RPM_INT32_TYPE then return(getInt32Val(data, ei['offset'])) when RPM_STRING_TYPE then return(getStringVal(data, ei['offset'])) when RPM_STRING_ARRAY_TYPE then return(getStringArray(data, ei['offset'], ei['count']).join("\n")) when RPM_I18NSTRING_TYPE then return(getStringArray(data, ei['offset'], ei['count']).join("\n").AsciiToUtf8) else $log.warn "MiqRpmPackages.getVal: unsupported data type: #{ei['ttype']}" return("") end end def getInt32Val(data, offset) (data[offset, 4].unpack("N").first) end def getStringVal(data, offset) eos = data.index(0.chr, offset) - offset (data[offset, eos]) end def getStringArray(data, offset, count) ra = [] cpos = offset count.times do s = getStringVal(data, cpos) ra << s cpos += (s.length + 1) end ra.uniq! (ra) end end # class MiqRPM if __FILE__ == $0 rpmPkgs = MiqRpmPackages.new(nil, "/var/lib/rpm/Packages") rpmPkgs.each do |pkg| puts "Package: #{pkg.name}" puts "\tInstall Time: #{pkg.installtime}" puts "\tBuild Time: #{pkg.buildtime}" puts "\tVersion: #{pkg.version}" puts "\tRelease: #{pkg.release}" puts "\tSummary: #{pkg.summary}" puts "\tVendor: #{pkg.vendor}" puts "\tArchitecture: #{pkg.arch}" puts "\tCategory: #{pkg.category}" puts "\tDescription: #{pkg.description}" puts "\tURL: #{pkg.url}" puts "\tDepends: #{pkg.depends}" end end