arr-pm-0.0.12/0000755000175000017500000000000014541330153014065 5ustar thegodtunethegodtunearr-pm-0.0.12/.gitignore0000644000175000017500000000013414541330153016053 0ustar thegodtunethegodtune# vim .*.sw[a-z] # build byproducts build-*/* fpm.wiki *.gem # python *.pyc # RVM .rvmrc arr-pm-0.0.12/lib/0000755000175000017500000000000014541330153014633 5ustar thegodtunethegodtunearr-pm-0.0.12/lib/arr-pm.rb0000644000175000017500000000006114541330153016353 0ustar thegodtunethegodtunerequire "arr-pm/namespace" require "arr-pm/file" arr-pm-0.0.12/lib/arr-pm/0000755000175000017500000000000014541330153016031 5ustar thegodtunethegodtunearr-pm-0.0.12/lib/arr-pm/file.rb0000644000175000017500000001631614541330153017304 0ustar thegodtunethegodtunerequire File.join(File.dirname(__FILE__), "namespace") require File.join(File.dirname(__FILE__), "file", "header") require File.join(File.dirname(__FILE__), "file", "lead") require File.join(File.dirname(__FILE__), "file", "tag") require "fcntl" require "shellwords" # 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: # http://www.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html class RPM::File attr_reader :file FLAG_LESS = (1 << 1) # RPMSENSE_LESS = (1 << 1), FLAG_GREATER = (1 << 2) # RPMSENSE_GREATER = (1 << 2), FLAG_EQUAL = (1 << 3) # RPMSENSE_EQUAL = (1 << 3), # from rpm/rpmfi.h FLAG_CONFIG_FILE = (1 << 0) # RPMFILE_CONFIG = (1 << 0) def initialize(file) if file.is_a?(String) file = File.new(file, "r") end @file = file end # def initialize # Return the lead for this rpm # # This 'lead' structure is almost entirely deprecated in the RPM file format. 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 # def lead # Return the signature header for this rpm 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 # def signature # Return the header for this rpm. def header signature if @header.nil? @header = ::RPM::File::Header.new(@file) @header.read end return @header end # def header # Returns a file descriptor for the payload. On first invocation, it seeks to # the start of the payload 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 # def payload def valid_compressor?(name) # I scanned rpm's rpmio.c for payload implementation names and found the following. # sed -rne '/struct FDIO_s \w+ *= *\{/{ n; s/^.*"(\w+)",$/\1/p }' rpmio/rpmio.c # It's possible this misses some supported rpm payload compressors. [ "gzip", "bzip2", "xz", "lzma", "zstd" ].include?(name) 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) def extract(target) if !File.directory?(target) raise Errno::ENOENT.new(target) end compressor = tags[:payloadcompressor] if !valid_compressor?(compressor) raise "Cannot decompress. This RPM uses an invalid compressor '#{compressor}'" end extractor = IO.popen("#{compressor} -d | (cd #{Shellwords.escape(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 # def extract def tags if @tags.nil? @tags = {} header.tags.each do |tag| tags[tag.tag] = tag.value end end @tags end # def taghash # 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: # [ name (string), operator (string), version (string) ] # # operator will be ">=", ">", "=", "<", or "<=" # # @return Array of [name, operator, version] 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 name, flags, and 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 # def relation # Get an array of requires defined in this package. # # @return Array of [ [name, operator, version], ... ] def requires return relation(:require) end # def requires # Get an array of conflicts defined in this package. # # @return Array of [ [name, operator, version], ... ] def conflicts return relation(:conflict) end # def conflicts # Get an array of provides defined in this package. # # @return Array of [ [name, operator, version], ... ] def provides return relation(:provide) end # def provides # Get an array of config files 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) if !tags[:fileflags].nil? 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 end return results end # def config_files # List the files in this RPM. # # This should have roughly the same effect as: # # % rpm2cpio blah.rpm | cpio -it def files # RPM stores the file metadata split across multiple tags. # A single path's filename (with no directories) is stored in the "basename" tag. # The directory a file lives in is stored in the "dirnames" tag # We can find out what directory a file is in using the "dirindexes" tag. # # We can join each entry of dirnames and basenames to make the full filename. return tags[:basenames].zip(tags[:dirindexes]).map { |name, i| File.join(tags[:dirnames][i], name) } end # def files def mask?(value, mask) return (value & mask) == mask end # def mask? 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 # def operator public(:extract, :payload, :header, :lead, :signature, :initialize, :requires, :conflicts, :provides) end # class RPM::File arr-pm-0.0.12/lib/arr-pm/file/0000755000175000017500000000000014541330153016750 5ustar thegodtunethegodtunearr-pm-0.0.12/lib/arr-pm/file/header.rb0000644000175000017500000000560114541330153020527 0ustar thegodtunethegodtunerequire File.expand_path(File.join(File.dirname(__FILE__), "..", "namespace")) require File.join(File.dirname(__FILE__), "tag") class RPM::File::Header attr_reader :tags attr_reader :length attr_accessor :magic # 8-byte string magic attr_accessor :index_count # rpmlib calls this field 'il' unhelpfully attr_accessor :data_length # rpmlib calls this field 'dl' unhelpfully HEADER_SIGNED_TYPE = 5 HEADER_MAGIC = "\x8e\xad\xe8\x01\x00\x00\x00\x00".force_encoding("BINARY") # magic + index_count + data_length HEADER_HEADER_LENGTH = HEADER_MAGIC.length + 4 + 4 TAG_ENTRY_SIZE = 16 # tag id, type, offset, count == 16 bytes def initialize(file) @file = file @inspectables = [:@length, :@index_count, :@data_length] @tags = [] end def read # TODO(sissel): update the comments here to reflect learnings about rpm # internals # At this point assume we've read and consumed the lead and signature. #len = @rpm.signature.index_length + @rpm.signature # # header size is # ( @rpm.signature.index_length * size of a header entry ) # + @rpm.signature.data_length # # header 'entries' are an # int32 (tag id), int32 (tag type), int32 (offset), uint32 (count) # # len = sizeof(il) + sizeof(dl) + (il * sizeof(struct entryInfo_s)) + dl; # See rpm's header.c, the headerLoad method function for reference. # Header always starts with HEADER_MAGIC + index_count(2bytes) + # data_length(2bytes) data = @file.read(HEADER_HEADER_LENGTH).unpack("a8NN") # TODO(sissel): @index_count is really a count, rename? @magic, @index_count, @data_length = data validate @index_size = @index_count * TAG_ENTRY_SIZE tag_data = @file.read(@index_size) data = @file.read(@data_length) (0 ... @index_count).each do |i| offset = i * TAG_ENTRY_SIZE entry_data = tag_data[i * TAG_ENTRY_SIZE, TAG_ENTRY_SIZE] entry = entry_data.unpack("NNNN") entry << data tag = ::RPM::File::Tag.new(*entry) @tags << tag end # each index @length = HEADER_HEADER_LENGTH + @index_size + @data_length end # def read def write raise "not implemented yet" # Sort tags by type (integer value) # emit all tags in order # then emit all data segments in same order end # def write def validate # TODO(sissel): raise real exceptions if @magic != ::RPM::File::Header::HEADER_MAGIC raise "Header magic did not match; got #{@magic.inspect}, " \ "expected #{::RPM::File::Header::HEADER_MAGIC.inspect}" end #if !(0..32).include?(@index_count) #raise "Invalid 'index_count' value #{@index_count}, expected to be in range [0..32]" #end #if !(0..8192).include?(@data_length) #raise "Invalid 'data_length' value #{@data_length}, expected to be in range [0..8192]" #end end # def validate end # class RPM::File::Header arr-pm-0.0.12/lib/arr-pm/file/tag.rb0000644000175000017500000001623114541330153020053 0ustar thegodtunethegodtunerequire File.expand_path(File.join(File.dirname(__FILE__), "..", "namespace")) class RPM::File::Tag attr_accessor :tag attr_accessor :type attr_accessor :offset attr_accessor :count attr_accessor :value # This data can be found mostly in rpmtag.h TAG = { 61 => :headerimage, 62 => :headersignatures, 63 => :headerimmutable, 64 => :headerregions, 100 => :headeri18ntable, 256 => :sig_base, 257 => :sigsize, 258 => :siglemd5_1, 259 => :sigpgp, 260 => :siglemd5_2, 261 => :sigmd5, 262 => :siggpg, 263 => :sigpgp5, 264 => :badsha1_1, 265 => :badsha1_2, 266 => :pubkeys, 267 => :dsaheader, 268 => :rsaheader, 269 => :sha1header, 270 => :longsigsize, 271 => :longarchivesize, 1000 => :name, 1001 => :version, 1002 => :release, 1003 => :epoch, 1004 => :summary, 1005 => :description, 1006 => :buildtime, 1007 => :buildhost, 1008 => :installtime, 1009 => :size, 1010 => :distribution, 1011 => :vendor, 1012 => :gif, 1013 => :xpm, 1014 => :license, 1015 => :packager, 1016 => :group, 1017 => :changelog, 1018 => :source, 1019 => :patch, 1020 => :url, 1021 => :os, 1022 => :arch, 1023 => :prein, 1024 => :postin, 1025 => :preun, 1026 => :postun, 1027 => :oldfilenames, 1028 => :filesizes, 1029 => :filestates, 1030 => :filemodes, 1031 => :fileuids, 1032 => :filegids, 1033 => :filerdevs, 1034 => :filemtimes, 1035 => :filedigests, 1036 => :filelinktos, 1037 => :fileflags, 1038 => :root, 1039 => :fileusername, 1040 => :filegroupname, 1041 => :exclude, 1042 => :exclusive, 1043 => :icon, 1044 => :sourcerpm, 1045 => :fileverifyflags, 1046 => :archivesize, 1047 => :providename, 1048 => :requireflags, 1049 => :requirename, 1050 => :requireversion, 1051 => :nosource, 1052 => :nopatch, 1053 => :conflictflags, 1054 => :conflictname, 1055 => :conflictversion, 1056 => :defaultprefix, 1057 => :buildroot, 1058 => :installprefix, 1059 => :excludearch, 1060 => :excludeos, 1061 => :exclusivearch, 1062 => :exclusiveos, 1063 => :autoreqprov, 1064 => :rpmversion, 1065 => :triggerscripts, 1066 => :triggername, 1067 => :triggerversion, 1068 => :triggerflags, 1069 => :triggerindex, 1079 => :verifyscript, 1080 => :changelogtime, 1081 => :changelogname, 1082 => :changelogtext, 1083 => :brokenmd5, 1084 => :prereq, 1085 => :preinprog, 1086 => :postinprog, 1087 => :preunprog, 1088 => :postunprog, 1089 => :buildarchs, 1090 => :obsoletename, 1091 => :verifyscriptprog, 1092 => :triggerscriptprog, 1093 => :docdir, 1094 => :cookie, 1095 => :filedevices, 1096 => :fileinodes, 1097 => :filelangs, 1098 => :prefixes, 1099 => :instprefixes, 1100 => :triggerin, 1101 => :triggerun, 1102 => :triggerpostun, 1103 => :autoreq, 1104 => :autoprov, 1105 => :capability, 1106 => :sourcepackage, 1107 => :oldorigfilenames, 1108 => :buildprereq, 1109 => :buildrequires, 1110 => :buildconflicts, 1111 => :buildmacros, 1112 => :provideflags, 1113 => :provideversion, 1114 => :obsoleteflags, 1115 => :obsoleteversion, 1116 => :dirindexes, 1117 => :basenames, 1118 => :dirnames, 1119 => :origdirindexes, 1120 => :origbasenames, 1121 => :origdirnames, 1122 => :optflags, 1123 => :disturl, 1124 => :payloadformat, 1125 => :payloadcompressor, 1126 => :payloadflags, 1127 => :installcolor, 1128 => :installtid, 1129 => :removetid, 1130 => :sha1rhn, 1131 => :rhnplatform, 1132 => :platform, 1133 => :patchesname, 1134 => :patchesflags, 1135 => :patchesversion, 1136 => :cachectime, 1137 => :cachepkgpath, 1138 => :cachepkgsize, 1139 => :cachepkgmtime, 1140 => :filecolors, 1141 => :fileclass, 1142 => :classdict, 1143 => :filedependsx, 1144 => :filedependsn, 1145 => :dependsdict, 1146 => :sourcepkgid, 1147 => :filecontexts, 1148 => :fscontexts, 1149 => :recontexts, 1150 => :policies, 1151 => :pretrans, 1152 => :posttrans, 1153 => :pretransprog, 1154 => :posttransprog, 1155 => :disttag, 1156 => :suggestsname, 1157 => :suggestsversion, 1158 => :suggestsflags, 1159 => :enhancesname, 1160 => :enhancesversion, 1161 => :enhancesflags, 1162 => :priority, 1163 => :cvsid, 1164 => :blinkpkgid, 1165 => :blinkhdrid, 1166 => :blinknevra, 1167 => :flinkpkgid, 1168 => :flinkhdrid, 1169 => :flinknevra, 1170 => :packageorigin, 1171 => :triggerprein, 1172 => :buildsuggests, 1173 => :buildenhances, 1174 => :scriptstates, 1175 => :scriptmetrics, 1176 => :buildcpuclock, 1177 => :filedigestalgos, 1178 => :variants, 1179 => :xmajor, 1180 => :xminor, 1181 => :repotag, 1182 => :keywords, 1183 => :buildplatforms, 1184 => :packagecolor, 1185 => :packageprefcolor, 1186 => :xattrsdict, 1187 => :filexattrsx, 1188 => :depattrsdict, 1189 => :conflictattrsx, 1190 => :obsoleteattrsx, 1191 => :provideattrsx, 1192 => :requireattrsx, 1193 => :buildprovides, 1194 => :buildobsoletes, 1195 => :dbinstance, 1196 => :nvra, 5000 => :filenames, 5001 => :fileprovide, 5002 => :filerequire, 5003 => :fsnames, 5004 => :fssizes, 5005 => :triggerconds, 5006 => :triggertype, 5007 => :origfilenames, 5008 => :longfilesizes, 5009 => :longsize, 5010 => :filecaps, 5011 => :filedigestalgo, 5012 => :bugurl, 5013 => :evr, 5014 => :nvr, 5015 => :nevr, 5016 => :nevra, 5017 => :headercolor, 5018 => :verbose, 5019 => :epochnum, } # See 'rpmTagType' enum in rpmtag.h TYPE = { 0 => :null, 1 => :char, 2 => :int8, 3 => :int16, 4 => :int32, 5 => :int64, 6 => :string, 7 => :binary, 8 => :string_array, 9 => :i18nstring, } def initialize(tag_id, type, offset, count, data) @tag = tag_id @type = type @offset = offset @count = count @data = data @inspectables = [:@tag, :@type, :@offset, :@count, :@value] end # def initialize def tag TAG.fetch(@tag, @tag) end # def tag def tag_as_int @tag end def type TYPE.fetch(@type, @type) end # def type def value if !@value case type when :string # string at offset up to first null @value = @data[@offset .. -1][/^[^\0]+/] when :i18nstring # string at offset up to first null @value = @data[@offset .. -1][/^[^\0]+/] when :string_array @value = @data[@offset .. -1].split("\0")[0 ... @count] when :binary @value = @data[@offset, @count] when :int32 @value = @data[@offset, 4 * count].unpack("N" * count) when :int16 @value = @data[@offset, 2 * count].unpack("n" * count) end # case type end # if !@value return @value end # def value end # class RPM::File::Tag arr-pm-0.0.12/lib/arr-pm/file/lead.rb0000644000175000017500000000254414541330153020207 0ustar thegodtunethegodtunerequire File.expand_path(File.join(File.dirname(__FILE__), "..", "namespace")) class RPM::File::Lead #struct rpmlead { attr_accessor :magic #unsigned char magic[4]; attr_accessor :major #unsigned char major; attr_accessor :minor #unsigned char minor; attr_accessor :type #short type; attr_accessor :archnum #short archnum; attr_accessor :name #char name[66]; attr_accessor :osnum #short osnum; attr_accessor :signature_type #short signature_type; attr_accessor :reserved #char reserved[16]; #} attr_accessor :length def initialize(file) @file = file @inspectables = [:@major, :@minor, :@length, :@type, :@archnum, :@signature_type, :@reserved, :@osnum] end def type case @type when 0 return :binary when 1 return :source else raise "Unknown package 'type' value #{@type}" end end # def type def read # Use 'A' here instead of 'a' to trim nulls. @length = 96 data = @file.read(@length).unpack("A4CCnnA66nnA16") @magic, @major, @minor, @type, @archnum, @name, \ @osnum, @signature_type, @reserved = data return nil end # def read def write(file) data = [ @magic, @major, @minor, @type, @archnum, @name, \ @osnum, @signature_type, @reserved ].pack("a4CCnna66nna16") file.write(data) end # def write end # class RPM::File::Lead arr-pm-0.0.12/lib/arr-pm/requires.rb0000644000175000017500000000110114541330153020206 0ustar thegodtunethegodtunerequire File.join(File.dirname(__FILE__), "namespace") class RPM::Requires private def initialize(name) @name = name @version = "0" @operator = ">=" end # def initialize def condition(operator, version) @operator = operator @version = version end # def condition def <=(version); condition(:<=, version) end def >=(version); condition(:>=, version) end def <(version); condition(:<, version) end def >(version); condition(:>, version) end def ==(version); condition(:==, version) end public(:initialize, :<=, :>=, :<, :>, :==) end arr-pm-0.0.12/lib/arr-pm/conflicts.rb0000644000175000017500000000022714541330153020343 0ustar thegodtunethegodtunerequire File.join(File.dirname(__FILE__), "namespace") require File.join(File.dirname(__FILE__), "requires") class RPM::Conflicts < RPM::Requires end arr-pm-0.0.12/lib/arr-pm/v2/0000755000175000017500000000000014541330153016360 5ustar thegodtunethegodtunearr-pm-0.0.12/lib/arr-pm/v2/error.rb0000644000175000017500000000136414541330153020042 0ustar thegodtunethegodtune# encoding: utf-8 require "arr-pm/namespace" require "arr-pm/v2/format" module ArrPM::V2::Error class Base < StandardError; end class InvalidMagicValue < Base def initialize(value) super("Got invalid magic value '#{value}'. Expected #{ArrPM::V2::Format::MAGIC}.") end end class InvalidHeaderMagicValue < Base def initialize(value) super("Got invalid magic value '#{value}'. Expected #{ArrPM::V2::HeaderHeader::MAGIC}.") end end class EmptyFile < Base; end class ShortFile < Base; end class InvalidVersion < Base; end class InvalidType < Base def initialize(value) super("Invalid type: #{value.inspect}") end end class InvalidName < Base; end class InvalidArchitecture < Base; end end arr-pm-0.0.12/lib/arr-pm/v2/architecture.rb0000644000175000017500000000066614541330153021377 0ustar thegodtunethegodtunerequire "arr-pm/namespace" module ArrPM::V2::Architecture NOARCH = 0 I386 = 1 ALPHA = 2 SPARC = 3 MIPS = 4 PPC = 5 M68K = 6 IP = 7 RS6000 = 8 IA64 = 9 SPARC64 = 10 MIPSEL = 11 ARM = 12 MK68KMINT = 13 S390 = 14 S390X = 15 PPC64 = 16 SH = 17 XTENSA = 18 X86_64 = 19 module_function # Is a given rpm architecture value valid? def valid?(value) return value >= 0 && value <= 19 end end arr-pm-0.0.12/lib/arr-pm/v2/header.rb0000644000175000017500000000174514541330153020144 0ustar thegodtunethegodtune# encoding: utf-8 require "arr-pm/namespace" require "arr-pm/v2/format" require "arr-pm/v2/header_header" require "arr-pm/v2/tag" require "arr-pm/v2/error" class ArrPM::V2::Header attr_reader :tags def load(io) headerheader = ArrPM::V2::HeaderHeader.new headerheader.load(io) headerdata = io.read(headerheader.entries * 16) tagdata = io.read(headerheader.bytesize) parse(headerdata, headerheader.entries, tagdata) # 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. io.read(tagdata.length % 8) end def parse(data, entry_count, tagdata) @tags = entry_count.times.collect do |i| tag_number, type_number, offset, count = data[i * 16, 16].unpack("NNNN") tag = ArrPM::V2::Tag.new(tag_number, type_number) tag.parse(tagdata, offset, count) tag end nil end end arr-pm-0.0.12/lib/arr-pm/v2/package.rb0000644000175000017500000000037314541330153020303 0ustar thegodtunethegodtune# encoding: utf-8 require "arr-pm/namespace" class ArrPM::V2::RPM attr_accessor :name attr_accessor :epoch attr_accessor :version attr_accessor :release def initialize defaults end def defaults @type = Type::BINARY end end arr-pm-0.0.12/lib/arr-pm/v2/tag.rb0000644000175000017500000001373714541330153017473 0ustar thegodtunethegodtunerequire "arr-pm/namespace" class ArrPM::V2::Tag module Type NULL = 0 CHAR = 1 INT8 = 2 INT16 = 3 INT32 = 4 INT64 = 5 STRING = 6 BINARY = 7 STRING_ARRAY = 8 I18NSTRING = 9 TYPE_MAP = Hash[constants.collect { |c| [const_get(c), c] }] def self.parse(data, type, offset, count) case type when NULL nil when CHAR data[offset, count].unpack("A#{count}") when INT8 data[offset, count].unpack("C" * count) when INT16 data[offset, 2 * count].unpack("n" * count) when INT32 data[offset, 4 * count].unpack("N" * count) when INT64 a, b = data[offset, 8].unpack("NN") a << 32 + b when STRING, I18NSTRING data[offset..-1][/^[^\0]*/] when BINARY data[offset, count] when STRING_ARRAY data[offset..-1].split("\0")[0...count] else raise ArrPM::V2::Error::InvalidType, type end end end # module Type HEADERIMAGE = 61 HEADERSIGNATURES = 62 HEADERIMMUTABLE = 63 HEADERREGIONS = 64 HEADERI18NTABLE = 100 SIG_BASE = 256 SIGSIZE = 257 SIGLEMD5_1 = 258 SIGPGP = 259 SIGLEMD5_2 = 260 SIGMD5 = 261 SIGGPG = 262 SIGPGP5 = 263 BADSHA1_1 = 264 BADSHA1_2 = 265 PUBKEYS = 266 DSAHEADER = 267 RSAHEADER = 268 SHA1HEADER = 269 LONGSIGSIZE = 270 LONGARCHIVESIZE = 271 NAME = 1000 VERSION = 1001 RELEASE = 1002 EPOCH = 1003 SUMMARY = 1004 DESCRIPTION = 1005 BUILDTIME = 1006 BUILDHOST = 1007 INSTALLTIME = 1008 SIZE = 1009 DISTRIBUTION = 1010 VENDOR = 1011 GIF = 1012 XPM = 1013 LICENSE = 1014 PACKAGER = 1015 GROUP = 1016 CHANGELOG = 1017 SOURCE = 1018 PATCH = 1019 URL = 1020 OS = 1021 ARCH = 1022 PREIN = 1023 POSTIN = 1024 PREUN = 1025 POSTUN = 1026 OLDFILENAMES = 1027 FILESIZES = 1028 FILESTATES = 1029 FILEMODES = 1030 FILEUIDS = 1031 FILEGIDS = 1032 FILERDEVS = 1033 FILEMTIMES = 1034 FILEDIGESTS = 1035 FILELINKTOS = 1036 FILEFLAGS = 1037 ROOT = 1038 FILEUSERNAME = 1039 FILEGROUPNAME = 1040 EXCLUDE = 1041 EXCLUSIVE = 1042 ICON = 1043 SOURCERPM = 1044 FILEVERIFYFLAGS = 1045 ARCHIVESIZE = 1046 PROVIDENAME = 1047 REQUIREFLAGS = 1048 REQUIRENAME = 1049 REQUIREVERSION = 1050 NOSOURCE = 1051 NOPATCH = 1052 CONFLICTFLAGS = 1053 CONFLICTNAME = 1054 CONFLICTVERSION = 1055 DEFAULTPREFIX = 1056 BUILDROOT = 1057 INSTALLPREFIX = 1058 EXCLUDEARCH = 1059 EXCLUDEOS = 1060 EXCLUSIVEARCH = 1061 EXCLUSIVEOS = 1062 AUTOREQPROV = 1063 RPMVERSION = 1064 TRIGGERSCRIPTS = 1065 TRIGGERNAME = 1066 TRIGGERVERSION = 1067 TRIGGERFLAGS = 1068 TRIGGERINDEX = 1069 VERIFYSCRIPT = 1079 CHANGELOGTIME = 1080 CHANGELOGNAME = 1081 CHANGELOGTEXT = 1082 BROKENMD5 = 1083 PREREQ = 1084 PREINPROG = 1085 POSTINPROG = 1086 PREUNPROG = 1087 POSTUNPROG = 1088 BUILDARCHS = 1089 OBSOLETENAME = 1090 VERIFYSCRIPTPROG = 1091 TRIGGERSCRIPTPROG = 1092 DOCDIR = 1093 COOKIE = 1094 FILEDEVICES = 1095 FILEINODES = 1096 FILELANGS = 1097 PREFIXES = 1098 INSTPREFIXES = 1099 TRIGGERIN = 1100 TRIGGERUN = 1101 TRIGGERPOSTUN = 1102 AUTOREQ = 1103 AUTOPROV = 1104 CAPABILITY = 1105 SOURCEPACKAGE = 1106 OLDORIGFILENAMES = 1107 BUILDPREREQ = 1108 BUILDREQUIRES = 1109 BUILDCONFLICTS = 1110 BUILDMACROS = 1111 PROVIDEFLAGS = 1112 PROVIDEVERSION = 1113 OBSOLETEFLAGS = 1114 OBSOLETEVERSION = 1115 DIRINDEXES = 1116 BASENAMES = 1117 DIRNAMES = 1118 ORIGDIRINDEXES = 1119 ORIGBASENAMES = 1120 ORIGDIRNAMES = 1121 OPTFLAGS = 1122 DISTURL = 1123 PAYLOADFORMAT = 1124 PAYLOADCOMPRESSOR = 1125 PAYLOADFLAGS = 1126 INSTALLCOLOR = 1127 INSTALLTID = 1128 REMOVETID = 1129 SHA1RHN = 1130 RHNPLATFORM = 1131 PLATFORM = 1132 PATCHESNAME = 1133 PATCHESFLAGS = 1134 PATCHESVERSION = 1135 CACHECTIME = 1136 CACHEPKGPATH = 1137 CACHEPKGSIZE = 1138 CACHEPKGMTIME = 1139 FILECOLORS = 1140 FILECLASS = 1141 CLASSDICT = 1142 FILEDEPENDSX = 1143 FILEDEPENDSN = 1144 DEPENDSDICT = 1145 SOURCEPKGID = 1146 FILECONTEXTS = 1147 FSCONTEXTS = 1148 RECONTEXTS = 1149 POLICIES = 1150 PRETRANS = 1151 POSTTRANS = 1152 PRETRANSPROG = 1153 POSTTRANSPROG = 1154 DISTTAG = 1155 SUGGESTSNAME = 1156 SUGGESTSVERSION = 1157 SUGGESTSFLAGS = 1158 ENHANCESNAME = 1159 ENHANCESVERSION = 1160 ENHANCESFLAGS = 1161 PRIORITY = 1162 CVSID = 1163 BLINKPKGID = 1164 BLINKHDRID = 1165 BLINKNEVRA = 1166 FLINKPKGID = 1167 FLINKHDRID = 1168 FLINKNEVRA = 1169 PACKAGEORIGIN = 1170 TRIGGERPREIN = 1171 BUILDSUGGESTS = 1172 BUILDENHANCES = 1173 SCRIPTSTATES = 1174 SCRIPTMETRICS = 1175 BUILDCPUCLOCK = 1176 FILEDIGESTALGOS = 1177 VARIANTS = 1178 XMAJOR = 1179 XMINOR = 1180 REPOTAG = 1181 KEYWORDS = 1182 BUILDPLATFORMS = 1183 PACKAGECOLOR = 1184 PACKAGEPREFCOLOR = 1185 XATTRSDICT = 1186 FILEXATTRSX = 1187 DEPATTRSDICT = 1188 CONFLICTATTRSX = 1189 OBSOLETEATTRSX = 1190 PROVIDEATTRSX = 1191 REQUIREATTRSX = 1192 BUILDPROVIDES = 1193 BUILDOBSOLETES = 1194 DBINSTANCE = 1195 NVRA = 1196 FILENAMES = 5000 FILEPROVIDE = 5001 FILEREQUIRE = 5002 FSNAMES = 5003 FSSIZES = 5004 TRIGGERCONDS = 5005 TRIGGERTYPE = 5006 ORIGFILENAMES = 5007 LONGFILESIZES = 5008 LONGSIZE = 5009 FILECAPS = 5010 FILEDIGESTALGO = 5011 BUGURL = 5012 EVR = 5013 NVR = 5014 NEVR = 5015 NEVRA = 5016 HEADERCOLOR = 5017 VERBOSE = 5018 EPOCHNUM = 5019 ENCODING = 5062 TAG_MAP = Hash[constants.collect { |c| [const_get(c), c] }] attr_accessor :tag, :type, :value def initialize(tag_number, type_number) @tag = self.class::TAG_MAP[tag_number] || tag_number @type = type_number end def parse(data, offset, count) @value = Type.parse(data, @type, offset, count) nil end def inspect format("<%s#%s> %s/%d value=%s>", self.class.name, self.object_id, @tag, @type, @value.inspect) end end # module ArrPM::V2::Tag arr-pm-0.0.12/lib/arr-pm/v2/format.rb0000644000175000017500000000045414541330153020200 0ustar thegodtunethegodtune# encoding: utf-8 require "arr-pm/namespace" module ArrPM::V2::Format MAGIC = [0x8e, 0xad, 0xe8] MAGIC_LENGTH = MAGIC.count MAGIC_STRING = MAGIC.pack("C#{MAGIC_LENGTH}") module_function def valid_magic?(magic) magic = magic.bytes if magic.is_a?(String) magic == MAGIC end end arr-pm-0.0.12/lib/arr-pm/v2/header_header.rb0000644000175000017500000000140214541330153021442 0ustar thegodtunethegodtune# encoding: utf-8 require "arr-pm/namespace" require "arr-pm/v2/format" require "arr-pm/v2/error" # The header of an rpm has ... a header. Funky naming :) class ArrPM::V2::HeaderHeader MAGIC = [ 0x8e, 0xad, 0xe8 ] MAGIC_LENGTH = MAGIC.count attr_accessor :version, :entries, :bytesize def load(io) data = io.read(16) parse(data) end def parse(data) magic, version, reserved, entries, bytesize = data.unpack("a3Ca4NN") self.class.validate_magic(magic.bytes) @version = version @entries = entries @bytesize = bytesize nil end def dump [magic, 1, 0, @entries, @bytesize].pack("a3Ca4NN") end def self.validate_magic(value) raise ArrPM::V2::Error::InvalidHeaderMagicValue, value if value != MAGIC end end arr-pm-0.0.12/lib/arr-pm/v2/lead.rb0000644000175000017500000000563414541330153017622 0ustar thegodtunethegodtune# encoding: utf-8 require "arr-pm/namespace" require "arr-pm/v2/format" require "arr-pm/v2/error" class ArrPM::V2::Lead LENGTH = 96 MAGIC = [ 0xed, 0xab, 0xee, 0xdb ] MAGIC_LENGTH = MAGIC.count SIGNED_TYPE = 5 attr_accessor :major, :minor, :type, :architecture, :name, :os, :signature_type, :reserved def validate self.class.validate_type(type) self.class.validate_architecture(architecture) if name.length > 65 raise ArrPM::V2::Error::InvalidName, "Name is longer than 65 chracters. This is invalid." end end def dump(io) io.write(serialize) end def serialize validate [ *MAGIC, major, minor, type, architecture, name, os, signature_type, *reserved ].pack("C4CCnnZ66nnC16") end def load(io) data = io.read(LENGTH) parse(data) end def parse(bytestring) raise ArrPM::V2::Error::EmptyFile if bytestring.nil? data = bytestring.bytes @magic = self.class.parse_magic(data) @major, @minor = self.class.parse_version(data) @type = self.class.parse_type(data) @architecture = self.class.parse_architecture(data) @name = self.class.parse_name(data) @os = self.class.parse_os(data) @signature_type = self.class.parse_signature_type(data) @reserved = self.class.parse_reserved(data) self end def signature? @signature_type == SIGNED_TYPE end def self.valid_version?(version) version == 1 end def self.parse_magic(data) magic = data[0, MAGIC_LENGTH] validate_magic(magic) magic end def self.validate_magic(magic) raise ArrPM::V2::Error::InvalidMagicValue, magic unless magic == MAGIC end def self.parse_version(data) offset = MAGIC_LENGTH major, minor = data[offset, 2] return major, minor end def self.parse_type(data) offset = MAGIC_LENGTH + 2 type = data[offset, 2].pack("CC").unpack("n").first validate_type(type) type end def self.validate_type(type) raise ArrPM::V2::Error::InvalidType, type unless ArrPM::V2::Type.valid?(type) end def self.parse_architecture(data) offset = MAGIC_LENGTH + 4 architecture = data[offset, 2].pack("C*").unpack("n").first validate_architecture(architecture) architecture end def self.validate_architecture(architecture) raise ArrPM::V2::Error::InvalidArchitecture unless ArrPM::V2::Architecture.valid?(architecture) end def self.parse_name(data) offset = MAGIC_LENGTH + 6 name = data[offset, 66] length = name.find_index(0) # find the first null byte raise ArrPM::V2::Error::InvalidName unless length return name[0, length].pack("C*") end def self.parse_os(data) offset = MAGIC_LENGTH + 72 data[offset, 2].pack("C*").unpack("n").first end def self.parse_signature_type(data) offset = MAGIC_LENGTH + 74 data[offset, 2].pack("C*").unpack("n").first end def self.parse_reserved(data) offset = MAGIC_LENGTH + 76 data[offset, 16] end end arr-pm-0.0.12/lib/arr-pm/v2/type.rb0000644000175000017500000000041614541330153017667 0ustar thegodtunethegodtunerequire "arr-pm/namespace" module ArrPM::V2::Type BINARY = 0 SOURCE = 1 module_function # Is a given rpm type value valid? # # The only valid types are BINARY (0) or SOURCE (1) def valid?(value) return (value == BINARY || value == SOURCE) end end arr-pm-0.0.12/lib/arr-pm/namespace.rb0000644000175000017500000000013314541330153020307 0ustar thegodtunethegodtuneclass RPM class File; end end module ArrPM module V2 class Package; end end end arr-pm-0.0.12/cpio.rb0000644000175000017500000001210214541330153015340 0ustar thegodtunethegodtuneclass BoundedIO attr_reader :length attr_reader :remaining def initialize(io, length, &eof_callback) @io = io @length = length @remaining = length @eof_callback = eof_callback @eof = false end def read(size=nil) return nil if eof? size = @remaining if size.nil? data = @io.read(size) @remaining -= data.bytesize eof? data end def sysread(size) raise EOFError, "end of file reached" if eof? read(size) end def eof? return false if @remaining > 0 return @eof if @eof @eof_callback.call @eof = true end end module CPIO FIELDS = [ :magic, :ino, :mode, :uid, :gid, :nlink, :mtime, :filesize, :devmajor, :devminor, :rdevmajor, :rdevminor, :namesize, :check ] end class CPIO::ASCIIReader FIELD_SIZES = { :magic => 6, :ino => 8, :mode => 8, :uid => 8, :gid => 8, :nlink => 8, :mtime => 8, :filesize => 8, :devmajor => 8, :devminor => 8, :rdevmajor => 8, :rdevminor => 8, :namesize => 8, :check => 8 } HEADER_LENGTH = FIELD_SIZES.reduce(0) { |m, (_, v)| m + v } HEADER_PACK = FIELD_SIZES.collect { |_, v| "A#{v}" }.join FIELD_ORDER = [ :magic, :ino, :mode, :uid, :gid, :nlink, :mtime, :filesize, :devmajor, :devminor, :rdevmajor, :rdevminor, :namesize, :check ] def initialize(io) @io = io end private def io @io end def each(&block) while true entry = read break if entry.nil? # The CPIO format has the end-of-stream marker as a file called "TRAILER!!!" break if entry.name == "TRAILER!!!" block.call(entry, entry.file) verify_correct_read(entry) unless entry.directory? end end def verify_correct_read(entry) # Read and throw away the whole file if not read at all. entry.file.tap do |file| if file.nil? || file.remaining == 0 # All OK! :) elsif file.remaining == file.length file.read(16384) while !file.eof? else # The file was only partially read? This should be an error by the # user. consumed = file.length - file.remaining raise BadState, "Only #{consumed} bytes were read of the #{file.length} byte file: #{entry.name}" end end end def read entry = CPIOEntry.new header = io.read(HEADER_LENGTH) return nil if header.nil? FIELD_ORDER.zip(header.unpack(HEADER_PACK)).each do |field, value| entry.send("#{field}=", value.to_i(16)) end entry.validate entry.mtime = Time.at(entry.mtime) read_name(entry, @io) read_file(entry, @io) entry end def read_name(entry, io) entry.name = io.read(entry.namesize - 1) # - 1 for null terminator nul = io.read(1) raise ArgumentError, "Corrupt CPIO or bug? Name null terminator was not null: #{nul.inspect}" if nul != "\0" padding_data = io.read(padding_name(entry)) # Padding should be all null bytes if padding_data != ("\0" * padding_data.bytesize) raise ArgumentError, "Corrupt CPIO or bug? Name null padding was #{padding_name(entry)} bytes: #{padding_data.inspect}" end end def read_file(entry, io) if entry.directory? entry.file = nil #read_file_padding(entry, io) nil else entry.file = BoundedIO.new(io, entry.filesize) do read_file_padding(entry, io) end end end def read_file_padding(entry, io) padding_data = io.read(padding_file(entry)) if padding_data != ("\0" * padding_data.bytesize) raise ArgumentError, "Corrupt CPIO or bug? File null padding was #{padding_file(entry)} bytes: #{padding_data.inspect}" end end def padding_name(entry) # name padding is padding up to a multiple of 4 after header+namesize -(HEADER_LENGTH + entry.namesize) % 4 end def padding_file(entry) (-(HEADER_LENGTH + entry.filesize + 2) % 4) end public(:each) end class CPIOEntry CPIO::FIELDS.each do |field| attr_accessor field end attr_accessor :name attr_accessor :file DIRECTORY_FLAG = 0040000 def validate raise "Invalid magic #{magic.inspect}" if magic != 0x070701 raise "Invalid ino #{ino.inspect}" if ino < 0 raise "Invalid mode #{mode.inspect}" if mode < 0 raise "Invalid uid #{uid.inspect}" if uid < 0 raise "Invalid gid #{gid.inspect}" if gid < 0 raise "Invalid nlink #{nlink.inspect}" if nlink < 0 raise "Invalid mtime #{mtime.inspect}" if mtime < 0 raise "Invalid filesize #{filesize.inspect}" if filesize < 0 raise "Invalid devmajor #{devmajor.inspect}" if devmajor < 0 raise "Invalid devminor #{devminor.inspect}" if devminor < 0 raise "Invalid rdevmajor #{rdevmajor.inspect}" if rdevmajor < 0 raise "Invalid rdevminor #{rdevminor.inspect}" if rdevminor < 0 raise "Invalid namesize #{namesize.inspect}" if namesize < 0 raise "Invalid check #{check.inspect}" if check < 0 end # def validate def read(*args) return nil if directory? file.read(*args) end def directory? mode & DIRECTORY_FLAG > 0 end end CPIO::ASCIIReader.new(STDIN).each do |entry, file| puts entry.name file.read unless entry.directory? end arr-pm-0.0.12/Makefile0000644000175000017500000000106414541330153015526 0ustar thegodtunethegodtuneGEMSPEC=$(shell ls *.gemspec) VERSION=$(shell awk -F\" '/spec.version/ { print $$2 }' $(GEMSPEC)) NAME=$(shell awk -F\" '/spec.name/ { print $$2 }' $(GEMSPEC)) GEM=$(NAME)-$(VERSION).gem .PHONY: package package: | $(GEM) .PHONY: gem gem: $(GEM) $(GEM): gem build $(GEMSPEC) .PHONY: test-package test-package: $(GEM) # Sometimes 'gem build' makes a faulty gem. gem unpack $(GEM) rm -rf ftw-$(VERSION)/ .PHONY: publish publish: test-package gem push $(GEM) .PHONY: install install: $(GEM) gem install $(GEM) .PHONY: clean clean: -rm -rf .yardoc $(GEM) arr-pm-0.0.12/arr-pm.gemspec0000644000175000017500000000136414541330153016634 0ustar thegodtunethegodtuneGem::Specification.new do |spec| files = %x{git ls-files}.split("\n") spec.name = "arr-pm" spec.version = "0.0.12" spec.summary = "RPM reader and writer library" spec.description = "This library allows to you to read and write rpm " \ "packages. Written in pure ruby because librpm is not available " \ "on all systems" spec.license = "Apache 2" spec.files = files spec.require_paths << "lib" spec.bindir = "bin" spec.authors = ["Jordan Sissel"] spec.email = ["jls@semicomplete.com"] spec.add_development_dependency "flores", ">0" spec.add_development_dependency "rspec", ">3.0.0" spec.add_development_dependency "stud", ">=0.0.23" spec.add_development_dependency "insist", ">=1.0.0" #spec.homepage = "..." end arr-pm-0.0.12/spec/0000755000175000017500000000000014541330153015017 5ustar thegodtunethegodtunearr-pm-0.0.12/spec/arr-pm/0000755000175000017500000000000014541330153016215 5ustar thegodtunethegodtunearr-pm-0.0.12/spec/arr-pm/v2/0000755000175000017500000000000014541330153016544 5ustar thegodtunethegodtunearr-pm-0.0.12/spec/arr-pm/v2/header_spec.rb0000644000175000017500000000161214541330153021333 0ustar thegodtunethegodtune# encoding: utf-8 require "flores/random" require "arr-pm/v2/header" require "arr-pm/v2/type" require "arr-pm/v2/architecture" require "json" describe ArrPM::V2::Header do context "with a known good rpm" do let(:path) { File.join(File.dirname(__FILE__), "../../fixtures/example-1.0-1.x86_64.rpm") } let(:file) { File.new(path) } before do lead = ArrPM::V2::Lead.new lead.load(file) # Throw away the signature if we have one described_class.new.load(file) if lead.signature? subject.load(file) end expectations = JSON.parse(File.read(File.join(File.dirname(__FILE__), "../../fixtures/example.json"))) expectations.each do |name, expected_value| it "should have expected value for the #{name} tag" do tag = subject.tags.find { |t| t.tag.to_s == name } expect(tag.value).to be == expected_value end end end end arr-pm-0.0.12/spec/arr-pm/v2/lead_spec.rb0000644000175000017500000000717214541330153021017 0ustar thegodtunethegodtune# encoding: utf-8 require "flores/random" require "arr-pm/v2/lead" require "arr-pm/v2/type" require "arr-pm/v2/architecture" describe ArrPM::V2::Lead do let(:major) { Flores::Random.integer(0..255) } let(:minor) { Flores::Random.integer(0..255) } let(:magic) { described_class::MAGIC } let(:type) { ArrPM::V2::Type::BINARY } let(:architecture) { ArrPM::V2::Architecture::I386 } let(:os) { 0 } let(:os_bytes) { [os].pack("n").unpack("C2") } let(:signature_type) { 0 } let(:signature_type_bytes) { [signature_type].pack("n").unpack("C2") } let(:longname) { "test-1.0-1" } let(:longnamebytes) { longname.bytes + (66-longname.bytesize).times.collect { 0 } } let(:leadbytes) { magic + [major, minor] + [0, type, 0, architecture] + longnamebytes + os_bytes + signature_type_bytes + 16.times.collect { 0 } } let(:lead) { leadbytes.pack("C*") } describe ".parse_magic" do context "when given an invalid magic value" do # Generate random bytes for the magic value, should be bad. let(:magic) { Flores::Random.iterations(0..10).collect { Flores::Random.integer(0..255) } } it "should fail" do expect { described_class.parse_magic(leadbytes) }.to raise_error(ArrPM::V2::Error::InvalidMagicValue) end end context "when given a valid magic value" do it "should succeed" do expect { described_class.parse_magic(leadbytes) }.not_to raise_error end end end describe ".parse_version" do context "when given an invalid version value" do let(:data) { magic + [major, minor] } it "should return an array of two values " do expect(described_class.parse_version(leadbytes)).to be == [major, minor] end end end describe ".parse_type" do context "when given an invalid type" do let(:type) { Flores::Random.integer(2..1000) } it "should fail" do expect { described_class.parse_type(leadbytes) }.to raise_error(ArrPM::V2::Error::InvalidType) end end context "with a valid type" do it "should return the type" do expect(described_class.parse_type(leadbytes)).to be == type end end end describe ".parse_name" do context "with a valid name" do it "should return the name" do expect(described_class.parse_name(leadbytes)).to be == longname end end end describe ".parse_signature_type" do it "should return the signature type" do expect(described_class.parse_signature_type(leadbytes)).to be == signature_type end end describe ".parse_reserved" do it "should return exactly 16 bytes" do expect(described_class.parse_reserved(leadbytes).count).to be == 16 end end describe "#parse" do before do subject.parse(lead) end it "should have a correct parsed values" do expect(subject.name).to be == longname expect(subject.major).to be == major expect(subject.minor).to be == minor expect(subject.type).to be == type expect(subject.architecture).to be == architecture end end describe "#dump" do before do subject.parse(lead) end let(:blob) { subject.serialize } it "should parse successfully" do subject.parse(blob) end end context "with a known good rpm" do let(:path) { File.join(File.dirname(__FILE__), "../../fixtures/example-1.0-1.x86_64.rpm") } before do subject.load(File.new(path)) end it "should have expected values" do expect(subject.name).to be == "example-1.0-1" expect(subject.major).to be == 3 expect(subject.minor).to be == 0 expect(subject.architecture).to be == architecture end end end arr-pm-0.0.12/spec/rpm/0000755000175000017500000000000014541330153015615 5ustar thegodtunethegodtunearr-pm-0.0.12/spec/rpm/file_spec.rb0000644000175000017500000000277414541330153020105 0ustar thegodtunethegodtune# encoding: utf-8 require "arr-pm/file" require "stud/temporary" require "insist" describe ::RPM::File do subject { described_class.new(path) } context "with a known good rpm" do let(:path) { File.join(File.dirname(__FILE__), "../fixtures/pagure-mirror-5.13.2-5.fc35.noarch.rpm") } context "#files" do let(:files) { [ "/usr/lib/systemd/system/pagure_mirror.service", "/usr/share/licenses/pagure-mirror", "/usr/share/licenses/pagure-mirror/LICENSE" ]} it "should have the correct list of files" do expect(subject.files).to eq(files) end end end context "#extract" do # This RPM should be correctly built, but we will modify the tags at runtime to force an error. let(:path) { File.join(File.dirname(__FILE__), "../fixtures/example-1.0-1.x86_64.rpm") } let(:dir) { dir = Stud::Temporary.directory } after do FileUtils.rm_rf(dir) end context "with an invalid payloadcompressor" do before do subject.tags[:payloadcompressor] = "some invalid | string" end it "should raise an error" do insist { subject.extract(dir) }.raises(RuntimeError) end end [ "gzip", "bzip2", "xz", "lzma", "zstd" ].each do |name| context "with a '#{name}' payloadcompressor" do before do subject.tags[:payloadcompressor] = name end it "should succeed" do reject { subject.extract(dir) }.raises(RuntimeError) end end end end end arr-pm-0.0.12/spec/fixtures/0000755000175000017500000000000014541330153016670 5ustar thegodtunethegodtunearr-pm-0.0.12/spec/fixtures/pagure-mirror-5.13.2-5.fc35.noarch.rpm0000644000175000017500000004437014541330153025111 0ustar thegodtunethegodtunepagure-mirror-5.13.2-5.fc35 > 6 6_6 3!x~Gl@ F9qgŏ`~ F9qgŏ{j88z2,P vB5^ޅfJz׵yʠ䕱Gm۳)Ȫ][+ِ\v+D@vJt˛Pg*͕7@Wy܆3O$#OD^w?(UXD=(\on5gn.N -$ ?n8 e1$Z'6>4(# +ʖPh'>lMgD_n:C*-g눢H?>Դi4c(i ]˔MVbv(9M{}>V`U4S`N=*ZHUX7lpn!} L?XSs֣xF,@Ƶp mTjpUﱉzEIɁr>OHX?4 P>l ݼ|vJMuTj1@yT#?E﷑;v:7 8Ƈ`$]jwh{x(.Tk>/}/ yT{,|{M-1ξa/å.YP7DŦ ` SZ|Vngs->׳:дn^d Wކ߄۵m6AKNoT}&|R]Vlps O-n"Sc1&2^&( k n z7̵OOת#Uv/:xr#8Q=R*:t{;ئx…0C8] K>p@I?9d  ?  17>      $(8"9p": ">?@GHI(X,Y0\@]L^xbd3e8f=l@tXudvpCpagure-mirror5.13.25.fc35The mirroring service for pagurepagure-mirror is the service mirroring projects that asked for it outside of this pagure instance.`buildvm-ppc64le-34.iad2.fedoraproject.orgIFedora ProjectFedora ProjectGPLv2+Fedora ProjectUnspecifiedhttps://pagure.io/pagurelinuxnoarch if [ $1 -eq 1 ] && [ -x /usr/bin/systemctl ]; then # Initial installation /usr/bin/systemctl --no-reload preset pagure_mirror.service || : fi if [ $1 -eq 0 ] && [ -x /usr/bin/systemctl ]; then # Package removal, not upgrade if [ -d /run/systemd/system ]; then /usr/bin/systemctl --no-reload disable --now pagure_mirror.service || : else /usr/bin/systemctl --no-reload disable pagure_mirror.service || : fi fi if [ $1 -ge 1 ] && [ -x /usr/bin/systemctl ]; then # Package upgrade, not uninstall for unit in pagure_mirror.service; do /usr/bin/systemctl set-property $unit Markers=+needs-restart || : done fiKFRA큤``W9e0fb4a622b9308d559b265866c58b9323ce8749b7010ab65b727c3aad967c45231f7edcc7352d7734a96eef0b8030f77982678c516876fcb81e25b32d68564crootrootrootrootrootrootpagure-5.13.2-5.fc35.src.rpmpagure-mirror     /bin/sh/bin/sh/bin/shpagurerpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)rpmlib(PayloadIsZstd)5.13.2-5.fc353.0.4-14.6.0-14.0-15.4.18-14.17.0-beta1`]``lM@`>(`%@``@___l@_2@_)M_ @^K^?@^J^^^^2^2^2^2^2^@^{G^1s]]]]Z@]P@]P@]9Fedora Release Engineering - 5.13.2-5Python Maint - 5.13.2-4Pierre-Yves Chibon - 5.13.2-3Zbigniew Jędrzejewski-Szmek - 5.13.2-2Neal Gompa - 5.13.2-1Neal Gompa - 5.13.1-1Fedora Release Engineering - 5.12-3Neal Gompa - 5.12-2Neal Gompa - 5.12-1Neal Gompa - 5.11.3-2Neal Gompa - 5.11.3-1Neal Gompa - 5.11.2-1Fedora Release Engineering - 5.10.0-12Neal Gompa - 5.10.0-11Neal Gompa - 5.10.0-10Neal Gompa - 5.10.0-9Miro Hrončok - 5.10.0-8Neal Gompa - 5.10.0-7Neal Gompa - 5.10.0-6Neal Gompa - 5.10.0-5Neal Gompa - 5.10.0-4Neal Gompa - 5.10.0-3Neal Gompa - 5.10.0-2Neal Gompa - 5.10.0-1Neal Gompa - 5.9.1-1Neal Gompa - 5.9.0-1Fedora Release Engineering - 5.8.1-2Neal Gompa - 5.8.1-1Neal Gompa - 5.8-1Miro Hrončok - 5.7.4-4Miro Hrončok - 5.7.4-3Neal Gompa - 5.7.4-2Neal Gompa - 5.7.4-1Fedora Release Engineering - 5.5-2- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild- Rebuilt for Python 3.10- Backport patch: noggin_support.patch from upstream commit: 6a3f43dd1fc33367f9ab2a2dca8f941591374374- Rebuilt for updated systemd-rpm-macros See https://pagure.io/fesco/issue/2583.- Update to 5.13.2 (RH#1927326)- Update to 5.13.1 (RH#1914378)- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild- Add optional dependencies for fedora-messaging support- Update to 5.12 (RH#1913480)- Backport various fixes from upstream- Update to 5.11.3 (RH#1868029)- Update to 5.11.2 (RH#1862974)- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild- Backport various fixes from upstream - Add patch to use whitenoise for serving static assets- Backport various fixes from upstream- Fix installability of web-apache-httpd subpackage on EL7- Rebuilt for Python 3.9- Backport support for STARTTLS support for SMTP servers- Backport fix for stats - Add missing step to start pagure web services for nginx setup in quickstart- Install missing pagure_authorized_keys_worker service- Fix thinko in quick start instructions- Add Obsoletes for package split of webserver configuration- Bump to build in EPEL8- Update to 5.10.0 (RH#1836004) - Clean up spec for better suitability for container deployments - Refresh quick start instructions for new configuration options - Drop unneeded patch- Update to 5.9.1 (RH#1818753) - Downgrade gitolite3 dependency to Recommends per CPE team request- Update to 5.9.0 (RH#1816636)- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild- Update to 5.8.1 (RH#1778787)- Update to 5.8 (RH#1744065)- Rebuilt for Python 3.8.0rc1 (#1748018)- Rebuilt for Python 3.8- Fix httpd conf path in README.Fedora- Update to 5.7.4- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild/bin/sh/bin/sh/bin/sh5.13.2-5.fc35pagure_mirror.servicepagure-mirrorLICENSE/usr/lib/systemd/system//usr/share/licenses//usr/share/licenses/pagure-mirror/-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mcpu=power8 -mtune=power8 -fasynchronous-unwind-tables -fstack-clash-protectioncpiozstd19noarch-redhat-linux-gnuASCII textdirectoryhttps://bugz.fedoraproject.org/pagure9ca7265cfb4c4827278fe1e6352aa8f486b1a6d2c5fe6ec1b70810f7da31c799c3cfaddcadd7c94cf1735da14f3dab1d17243ab8c218847e59254c3d3e8e404a?(/h!/tGqW$+VE/&E5ZV:nb?YWںuIsʹsM/:8*IAȗ/Ϲ3_ T2!@4<@@,x|.D6(-:cP/ʦzq]3I:E6 h8^,֎l AfBh)x$UdR 9ô `ou I [A0iv<E+R=:_h= r_ؒ;; *֜$g /{S8R+A;v[ -ɼ!c Ou5}i1HP3/ ٲq_WLrpB%jaµ `*l35Ce5P4 CCB:7:$tQhkyz2vgcy{m:2!5B,w-9TabkDA P xilKI居ؑb\2u ύޜ۝cuXS(OzZsbg ^U )(d{Q>^_htqVo,AFT ?ݫ餐i$ NlUW(EE"(h" H!"9D"J4''-PiQu^Uj3Vz{ō2} uDž9GŪ|{݋(noZKf-Qـ`ȅyۜ/+ӗӚi%fJKۋ慊脵i c S4ǻۧ}]eeD<*µ8KgӸ|Szu.4 dQo{EטF{j/Zjޮw¶F=#7P2jY޾g/l0~/_P]׿Ssݤ~T'i}Ȝ1d16oͥ^a]WվUt~ rnju={rVP~TwwI$|5sJg (P HDiwcۧm).&cǞFcZgȅ~eUgKk }k^ %~C_ 9Ϡc_m[,[": `F`<{540W`!b}ZcqFě]Di0<^2H@[qZ>]xRAyM-1]hb]ekY屈DgXX8(lH* )j2 tQ4(ģHx* ɓ L")hHQq< D="!A-BLTf$$b 1(% Ga"&PDI:H#bHDRDeE4d!)ȑNCHĐP"IR (I T!=$I91*"$! JB$ҁ@Iz"dqP;mK7m{硳 t+܊+ثITYiU=}I=)C$`c@4:< \Lw\" 8#b5XGG>o??zrz(RW1;v;qxzfMaBH1Cl9OĠ@n>/ ^Րzc輒hvS?Dj,rktf+y&H&796ń8Xa) x1)s:7tMTF#&N,I s:&y̘Fݡ mP pB+.%#2kxŦ .TV<"=J]'d%7%w 3q(wB]Fy`Rromz wggU(Tڛ9FﬠÁ^<(šT7No*["ɻt "3~ьB9-dzRM|-HDP΀WXDȔDI2i,ȭo;@?GBC:'-4}+Q {M|g/W.6}fs*0uF?`>6{| Q̀%-\ݟr.iE`1t@Y48@*v@\p:/bQ+|V%$'˖2x%{|3)PYvgϜ `ƫW)Wlo$T#STш =Ze=Jָ}թ5sإòԋWYIf| 峡IDսGdHfOdunWKE j+WhՈ&cu$ޗhYh(6rRSeqJ T~Y+PC9ZZB]7'#^T$apc3Fwɩ/Ww8v%G jc !J#dyJ;1& N8w3Z-D4KTeQمkK `=~ٹ&JrPwHgahg7t_VI[/ 7F&!dZ{Ӹr0PecOz`.q&I?_ǁA'$DU?ٺG䁂]hL.3ReaS8C6%640~^%D bzINB/d 5wSt:iWBG!F0|- MKn=2.c(Tz;#L%"23xKE:b9w rBȅCC)A<n: SB$W Z}PkV\*̳\rm X3O/)aBuoÝGNcr~k~)3C :%ǥtn0'$Hnk&Q# bs[zc߉FBɵPrn1[!j<\ }@LQ ˶8(}l !>*ԥ=LW{6Dƭ00L4ha+IвTxC9F];VLyt^w2+ zXBHΏ< Ocܠ!R)# J\&9%R1 S?Ǯ b(53EJFEŇl` p_ vA D|NwM-c U௿; j7C٪EV I,!7 u-{ .\HSKhaH(i+I<- -k:8JABnЧue)Xb,<or'Rn5(=>T ƷfBUq{"_SN *39l\ ?six]}<8΅H_펫V,4 FoFp$))vNG^tMT4Z!Ϭ`ĉ[pbM.[#PHH."WUĮp8 zjN3tAw0N%%oBR LH"eD|ǵj:%*Cg,J5˵L/$$HZejNǓsOduV`5I .,m4ml(졠 x?}>}] ]&Cr-}ވܾs}0A6ǢY- ZQzC#JAy&7`cZ[G+_f;x-njqK cxzHԾX2b|W8^ ̸Y>_rޣC9K^{1^DBuQ}jͤϺEwp4en2ESVCOBc3bHL  h%!)p믄=gEd!L_XQbAզiW)'Gտq/ig hy DiKQs SGY~v)\L&(Ԅkvݠcz3ʉ t##Cz&&l W?3e+好N3slw j[Miy2]5}} qP=큢%Vfx#& ٔ& Һ^[v!uЃ]bݩmzs_~BزM%iM\MYEv"{)5-xʢX2[/1ck !RIxZԙYU>!oخ%NE$=$2*|Hz$̋&$^ 2b C8?=tPfPڲQv*Ji]]b3IUXjV/ǥ "?9 %9&΂7jo%PRfᯚ6C>.;+VL -Zz@W7{̚1:+[`Wb_?@t"%22nbt ,Ɖ>R H`$8gJF g8 ΍Gc1 /Xη?>R ]t!h<LW2T{*NNr/VZ|YoA%u. Du42FyK8M^7vJ,uT}ExBɿ%7ǔшˇ|}:0H{|G[DaW]{{X*QvYx? /]arr-pm-0.0.12/spec/fixtures/example-1.0-1.x86_64.rpm0000644000175000017500000001271614541330153022441 0ustar thegodtunethegodtuneexample-1.0-1T>D ,0@D73670cb0c637fe76adad6ba81265e7503138febfbpmZZЅ|> f?Vd   %<@LPU] y(-J8X<YDbPd,e1f6l8PCexample1.01no description givenno description givenWf^localhostnoneunknowndefaulthttp://example.com/no-uri-givenlinuxx86_64example-1.0-1.src.rpmexampleexample(x86-64)  rpmlib(CompressedFileNames)rpmlib(PayloadFilesHavePrefix)3.0.4-14.0-14.13.0-rc1/1.0-11.0-1-O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -m64 -mtune=genericcpiogzip9x86_64-redhat-linux-gnuutf-8?3070704 $!A>A @5|arr-pm-0.0.12/spec/fixtures/example.json0000644000175000017500000000177114541330153021224 0ustar thegodtunethegodtune{ "NAME": "example", "VERSION": "1.0", "RELEASE": "1", "SUMMARY": "no description given", "DESCRIPTION": "no description given", "BUILDTIME": [ 1466326707 ], "BUILDHOST": "localhost", "SIZE": [ 0 ], "VENDOR": "none", "LICENSE": "unknown", "PACKAGER": "", "GROUP": "default", "URL": "http://example.com/no-uri-given", "OS": "linux", "ARCH": "x86_64", "SOURCERPM": "example-1.0-1.src.rpm", "PROVIDENAME": [ "example", "example(x86-64)" ], "REQUIREFLAGS": [ 16777226, 16777226 ], "REQUIRENAME": [ "rpmlib(CompressedFileNames)", "rpmlib(PayloadFilesHavePrefix)" ], "REQUIREVERSION": [ "3.0.4-1", "4.0-1" ], "RPMVERSION": "4.13.0-rc1", "PREFIXES": [ "/" ], "PROVIDEFLAGS": [ 8, 8 ], "PROVIDEVERSION": [ "1.0-1", "1.0-1" ], "PAYLOADFORMAT": "cpio", "PAYLOADCOMPRESSOR": "gzip", "PAYLOADFLAGS": "9", "PLATFORM": "x86_64-redhat-linux-gnu", "ENCODING": "utf-8" } arr-pm-0.0.12/Gemfile0000644000175000017500000000004714541330153015361 0ustar thegodtunethegodtunesource "https://rubygems.org" gemspec arr-pm-0.0.12/.rubocop.yml0000644000175000017500000000361614541330153016345 0ustar thegodtunethegodtune# Let's not argue over this... StringLiterals: Enabled: false # I can't find a reason for raise vs fail. SignalException: Enabled: false # I can't find a reason to prefer 'map' when 'collect' is what I mean. # I'm collecting things from a list. Maybe someone can help me understand the # semantics here. CollectionMethods: Enabled: false # Why do you even *SEE* trailing whitespace? Because your editor was # misconfigured to highlight trailing whitespace, right? Maybe turn that off? # ;) TrailingWhitespace: Enabled: false # Line length is another weird problem that somehow in the past 40 years of # computing we don't seem to have solved. It's a display problem :( LineLength: Max: 9000 # %w() vs [ "x", "y", ... ] # The complaint is on lib/pleaserun/detector.rb's map of OS=>Runner, # i'll ignore it. WordArray: MinSize: 5 # A 20-line method isn't too bad. MethodLength: Max: 20 # Hash rockets (=>) forever. Why? Not all of my hash keys are static symbols. HashSyntax: EnforcedStyle: hash_rockets # I prefer explicit return. It makes it clear in the code that the # code author intended to return a value from a method. RedundantReturn: Enabled: false # My view on a readable case statement seems to disagree with # what rubocop wants and it doesn't let me configure it other than # enable/disable. CaseIndentation: Enabled: false # module This::Module::Definition is good. Style/ClassAndModuleChildren: Enabled: true EnforcedStyle: compact # "in interpolation #{use.some("double quotes is ok")}" Style/StringLiteralsInInterpolation: Enabled: true EnforcedStyle: double_quotes # Long-block `if !something ... end` are more readable to me than `unless something ... end` Style/NegatedIf: Enabled: false Style/NumericLiterals: MinDigits: 6 # This kind of style "useless use of %x" assumes code is write-once. Style/UnneededPercentX: Enabled: false Style/FileName: Enabled: false arr-pm-0.0.12/README.md0000644000175000017500000000430714541330153015350 0ustar thegodtunethegodtune# ARRRRRRRRRR PM RPM reader/writer library written in Ruby. It aims to provide [fpm](https://github.com/jordansissel/fpm) with a way to read and write rpms. ## Why not use librpm? The API is quite confusing in many places, poorly documented in most. I have reached out to some CentOS/rpm folks to see what we can do about improving that API. Even still, librpm has dependencies of its own. I want fpm to be able to read and write RPMs without requiring and endless chain of dependencies and most especially without requiring root access to get things going. Mainly, if I try to build librpm myself, I get this: "configure: error: missing required NSPR / NSS header" and I'm not the burden of dependency resolution on fpm users. ## API ideas It should be possible to do a read+modify+write on an RPM. ### Creating an RPM (proposed API) rpm = RPM.new # requires and conflicts rpm.requires("name") <= "version" # Something fun-dsl-likef rpm.requires("name", :<=, "version") # Or maybe something like this # provides rpm.provides("name") # Add some files rpm.files << path rpm.files << path2 rpm.files << path3 # Scripts? rpm.scripts[:preinstall](path_or_script_string) rpm.scripts[:postinstall](path_or_script_string) rpm.scripts[:preuninstall](path_or_script_string) rpm.scripts[:postuninstall](path_or_script_string) rpm.write(output_file_name) ### Reading an RPM (proposed API) rpm = RPM.read(file) rpm.requires # Array of RPM::Requires ? rpm.provides # Array of 'Provides' strings? rpm.conflicts # Array of RPM::Conflicts ? rpm.files # Array of RPM::File ? Maybe something like: rpm.files.each do |file| # Tags that are defined in rpm header tags # fileclass filecolors filecontexts filedependsn filedependsx filedevices # filedigests fileflags filegroupname fileinodes filelangs filelinktos # filemodes filemtimes filerdevs filesizes fileusername fileverifyflags # # frankly, we don't care about most of these. Meaningful ones: # username, groupname, size, mtime, mode, linkto # file.io could give a nice IO-like thing that let you read the file out # of the rpm end arr-pm-0.0.12/LICENSE0000644000175000017500000000104714541330153015074 0ustar thegodtunethegodtuneCopyright 2012 Jordan Sissel Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. arr-pm-0.0.12/CHANGELOG.md0000644000175000017500000000130314541330153015673 0ustar thegodtunethegodtune # v0.0.12 * Security: Fixed a safety problem which would allow for arbitrary shell execution when invoking `RPM::File#extract` or `RPM::File#files`. (Jordan Sissel, @joernchen; #14, #15) * This library now has no external dependencies. (Jordan Sissel, #18) * `RPM::File#extract` now correctly works when the target directory contains spaces or other special characters. (@joernchen, #19) * Listing files (`RPM::File#files`) no longer requires external tools like `cpio` (Jordan Sissel, #17) # v0.0.11 * Support Ruby 3.0 (Alexey Morozov, #12) * Fix bug caused when listing config_files on an rpm with no config files. (Daniel Jay Haskin, #7) # Older versions Changelog not tracked for older versions.