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.



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

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.



11
12
13
# File 'lib/arr-pm/file.rb', line 11

def file
  @file
end

Instance Method Details

#config_filesObject

Get an array of config files



176
177
178
179
180
181
182
183
184
185
# File 'lib/arr-pm/file.rb', line 176

def config_files
  # this stuff seems to be in the 'enum rpmfileAttrs_e' from rpm/rpmfi.h
  results = []
  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

#conflictsObject

Get an array of conflicts defined in this package.

Returns:

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



164
165
166
# File 'lib/arr-pm/file.rb', line 164

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)


95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/arr-pm/file.rb', line 95

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

#filesObject

List the files in this RPM.

This should have roughly the same effect as:

% rpm2cpio blah.rpm | cpio -it


192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/arr-pm/file.rb', line 192

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
      # do nothing
    end
  end
  lister.close_write
  # Read remaining output
  begin
      output << lister.read
  rescue Errno::EAGAIN
      # Do Nothing
  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

#headerObject

Return the header for this rpm.



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

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.



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

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)


231
232
233
# File 'lib/arr-pm/file.rb', line 231

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

#operator(flag) ⇒ Object

def mask?



235
236
237
238
239
240
241
# File 'lib/arr-pm/file.rb', line 235

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



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

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], … ]



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

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]



141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/arr-pm/file.rb', line 141

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], … ]



157
158
159
# File 'lib/arr-pm/file.rb', line 157

def requires
  return relation(:require)
end

#signatureObject

Return the signature header for this rpm



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

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



117
118
119
120
121
122
123
124
125
# File 'lib/arr-pm/file.rb', line 117

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