Class: RPM::File

Inherits:
Object
  • Object
show all
Defined in:
lib/arr-pm/file.rb,
lib/arr-pm/namespace.rb

Overview

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

Defined Under Namespace

Classes: Header, Lead, Tag

Constant Summary collapse

FLAG_LESS =

RPMSENSE_LESS = (1 << 1),

(1 << 1)
FLAG_GREATER =

RPMSENSE_GREATER = (1 << 2),

(1 << 2)
FLAG_EQUAL =

RPMSENSE_EQUAL = (1 << 3),

(1 << 3)
FLAG_CONFIG_FILE =

from rpm/rpmfi.h

(1 << 0)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file) ⇒ File

Returns a new instance of File.



22
23
24
25
26
27
# File 'lib/arr-pm/file.rb', line 22

def initialize(file)
  if file.is_a?(String)
    file = File.new(file, "r")
  end
  @file = file
end

Instance Attribute Details

#fileObject (readonly)

Returns the value of attribute file.



13
14
15
# File 'lib/arr-pm/file.rb', line 13

def file
  @file
end

Instance Method Details

#config_filesObject

Get an array of config files



191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/arr-pm/file.rb', line 191

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

#conflictsObject

Get an array of conflicts defined in this package.

Returns:

  • Array of [ [name, operator, version], … ]



179
180
181
# File 'lib/arr-pm/file.rb', line 179

def conflicts
  return relation(:conflict)
end

#extract(target) ⇒ Object

Extract this RPM to a target directory.

This should have roughly the same effect as:

% rpm2cpio blah.rpm | (cd {target}; cpio -i --make-directories)


105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/arr-pm/file.rb', line 105

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

#filesObject

List the files in this RPM.

This should have roughly the same effect as:

% rpm2cpio blah.rpm | cpio -it


211
212
213
214
215
216
217
218
219
# File 'lib/arr-pm/file.rb', line 211

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

#headerObject

Return the header for this rpm.



68
69
70
71
72
73
74
75
76
# File 'lib/arr-pm/file.rb', line 68

def header
  signature

  if @header.nil?
    @header = ::RPM::File::Header.new(@file)
    @header.read
  end
  return @header
end

#leadObject

Return the lead for this rpm

This ‘lead’ structure is almost entirely deprecated in the RPM file format.



32
33
34
35
36
37
38
39
40
41
42
# File 'lib/arr-pm/file.rb', line 32

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

#mask?(value, mask) ⇒ Boolean

def files

Returns:

  • (Boolean)


221
222
223
# File 'lib/arr-pm/file.rb', line 221

def mask?(value, mask)
  return (value & mask) == mask
end

#operator(flag) ⇒ Object

def mask?



225
226
227
228
229
230
231
# File 'lib/arr-pm/file.rb', line 225

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

#payloadObject

Returns a file descriptor for the payload. On first invocation, it seeks to the start of the payload



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/arr-pm/file.rb', line 80

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

#providesObject

Get an array of provides defined in this package.

Returns:

  • Array of [ [name, operator, version], … ]



186
187
188
# File 'lib/arr-pm/file.rb', line 186

def provides
  return relation(:provide)
end

#relation(type) ⇒ Object

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 “<=”

Returns:

  • Array of [name, operator, version]



156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/arr-pm/file.rb', line 156

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

#requiresObject

Get an array of requires defined in this package.

Returns:

  • Array of [ [name, operator, version], … ]



172
173
174
# File 'lib/arr-pm/file.rb', line 172

def requires
  return relation(:require)
end

#signatureObject

Return the signature header for this rpm



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/arr-pm/file.rb', line 45

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

#tagsObject

def extract



132
133
134
135
136
137
138
139
140
# File 'lib/arr-pm/file.rb', line 132

def tags
  if @tags.nil?
    @tags = {}
    header.tags.each do |tag|
      tags[tag.tag] = tag.value
    end
  end
  @tags
end

#valid_compressor?(name) ⇒ Boolean

def payload

Returns:

  • (Boolean)


92
93
94
95
96
97
98
# File 'lib/arr-pm/file.rb', line 92

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