Much of the code here is derived from knowledge gained by reading the rpm source code, but mostly it started making more sense after reading this site: www.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html
from rpm/rpmfi.h
# File lib/arr-pm/file.rb, line 21 def initialize(file) if file.is_a?(String) file = File.new(file, "r") end @file = file end
Get an array of config files
# File lib/arr-pm/file.rb, line 177 def config_files # this stuff seems to be in the 'enum rpmfileAttrs_e' from rpm/rpmfi.h results = [] # short-circuit if there's no :fileflags tag return results unless tags.include?(:fileflags) tags[:fileflags].each_with_index do |flag, i| # The :fileflags (and other :file... tags) are an array, in order of # files in the rpm payload, we want a list of paths of config files. results << files[i] if mask?(flag, FLAG_CONFIG_FILE) end return results end
Get an array of conflicts defined in this package.
@return Array of [ [name, operator, version], … ]
# File lib/arr-pm/file.rb, line 165 def conflicts return relation(:conflict) end
Extract this RPM to a target directory.
This should have roughly the same effect as:
% rpm2cpio blah.rpm | (cd {target}; cpio -i --make-directories)
# File lib/arr-pm/file.rb, line 96 def extract(target) if !File.directory?(target) raise Errno::ENOENT.new(target) end extractor = IO.popen("#{tags[:payloadcompressor]} -d | (cd #{target}; cpio -i --quiet --make-directories)", "w") buffer = "" begin buffer.force_encoding("BINARY") rescue NoMethodError # Do Nothing end payload_fd = payload.clone loop do data = payload_fd.read(16384, buffer) break if data.nil? # eof extractor.write(data) end payload_fd.close extractor.close end
List the files in this RPM.
This should have roughly the same effect as:
% rpm2cpio blah.rpm | cpio -it
# File lib/arr-pm/file.rb, line 195 def files return @files unless @files.nil? lister = IO.popen("#{tags[:payloadcompressor]} -d | cpio -it --quiet", "r+") buffer = "" begin buffer.force_encoding("BINARY") rescue NoMethodError # Do Nothing end payload_fd = payload.clone output = "" loop do data = payload_fd.read(16384, buffer) break if data.nil? # listerextractor.write(data) lister.write(data) # Read output from the pipe. begin output << lister.read_nonblock(16384) rescue Errno::EAGAIN # Nothing to read, move on! end end lister.close_write # Read remaining output begin output << lister.read rescue Errno::EAGAIN # Because read_nonblock enables NONBLOCK the 'lister' fd, # and we may have invoked a read *before* cpio has started # writing, let's keep retrying this read until we get an EOF retry rescue EOFError # At EOF, hurray! We're done reading. end # Split output by newline and strip leading "." @files = output.split("\n").collect { |s| s.gsub(/^\./, "") } return @files ensure lister.close unless lister.nil? payload_fd.close unless payload_fd.nil? end
Return the header for this rpm.
# File lib/arr-pm/file.rb, line 67 def header signature if @header.nil? @header = ::RPM::File::Header.new(@file) @header.read end return @header end
Return the lead for this rpm
This 'lead' structure is almost entirely deprecated in the RPM file format.
# File lib/arr-pm/file.rb, line 31 def lead if @lead.nil? # Make sure we're at the beginning of the file. @file.seek(0, IO::SEEK_SET) @lead = ::RPM::File::Lead.new(@file) # TODO(sissel): have 'read' return number of bytes read? @lead.read end return @lead end
# File lib/arr-pm/file.rb, line 241 def mask?(value, mask) return (value & mask) == mask end
# File lib/arr-pm/file.rb, line 245 def operator(flag) return "<=" if mask?(flag, FLAG_LESS | FLAG_EQUAL) return ">=" if mask?(flag, FLAG_GREATER | FLAG_EQUAL) return "=" if mask?(flag, FLAG_EQUAL) return "<" if mask?(flag, FLAG_LESS) return ">" if mask?(flag, FLAG_GREATER) end
Returns a file descriptor for the payload. On first invocation, it seeks to the start of the payload
# File lib/arr-pm/file.rb, line 79 def payload header if @payload.nil? @payload = @file.clone # The payload starts after the lead, signature, and header. Remember the signature has an # 8-byte boundary-rounding. end @payload.seek(@lead.length + @signature.length + @signature.length % 8 + @header.length, IO::SEEK_SET) return @payload end
Get an array of provides defined in this package.
@return Array of [ [name, operator, version], … ]
# File lib/arr-pm/file.rb, line 172 def provides return relation(:provide) end
Get all relations of a given type to this package.
Examples:
rpm.relation(:require) rpm.relation(:conflict) rpm.relation(:provide)
In the return array-of-arrays, the elements are:
operator will be “>=”, “>”, “=”, “<”, or “<=”
@return Array of [name, operator, version]
# File lib/arr-pm/file.rb, line 142 def relation(type) name = "#{type}name".to_sym flags = "#{type}flags".to_sym version = "#{type}version".to_sym # There is no data if we are missing all 3 tag types (name/flags/version) # FYI: 'tags.keys' is an array, Array#& does set intersection. return [] if (tags.keys & [name, flags, version]).size != 3 # Find tags <type>name, <type>flags, and <type>version, and return # an array of "name operator version" return tags[name].zip(tags[flags], tags[version]) .reduce([]) { |memo, (n,o,v)| memo << [n, operator(o), v] } end
Get an array of requires defined in this package.
@return Array of [ [name, operator, version], … ]
# File lib/arr-pm/file.rb, line 158 def requires return relation(:require) end
Return the signature header for this rpm
# File lib/arr-pm/file.rb, line 44 def signature lead # Make sure we've parsed the lead... # If signature_type is not 5 (HEADER_SIGNED_TYPE), no signature. if @lead.signature_type != Header::HEADER_SIGNED_TYPE @signature = false return end if @signature.nil? @signature = ::RPM::File::Header.new(@file) @signature.read # signature headers are padded up to an 8-byte boundar, details here: # http://rpm.org/gitweb?p=rpm.git;a=blob;f=lib/signature.c;h=63e59c00f255a538e48cbc8b0cf3b9bd4a4dbd56;hb=HEAD#l204 # Throw away the pad. @file.read(@signature.length % 8) end return @signature end