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



21
22
23
24
25
26
# 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

Instance Attribute Details

#fileObject (readonly)

Returns the value of attribute file.



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

def file
  @file
end

Instance Method Details

#config_filesObject

Get an array of config files



177
178
179
180
181
182
183
184
185
186
187
188
# 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

#conflictsObject

Get an array of conflicts defined in this package.



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

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)


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# 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

#filesObject

List the files in this RPM.

This should have roughly the same effect as:

% rpm2cpio blah.rpm | cpio -it


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
230
231
232
233
234
235
236
237
238
239
# 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

#headerObject

Return the header for this rpm.



67
68
69
70
71
72
73
74
75
# 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

#leadObject

Return the lead for this rpm

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



31
32
33
34
35
36
37
38
39
40
41
# 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

#mask?(value, mask) ⇒ Boolean

def files



241
242
243
# File 'lib/arr-pm/file.rb', line 241

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

#operator(flag) ⇒ Object

def mask?



245
246
247
248
249
250
251
# 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

#payloadObject

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



79
80
81
82
83
84
85
86
87
88
89
# 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

#providesObject

Get an array of provides defined in this package.



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

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



142
143
144
145
146
147
148
149
150
151
152
153
# 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

#requiresObject

Get an array of requires defined in this package.



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

def requires
  return relation(:require)
end

#signatureObject

Return the signature header for this rpm



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 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

#tagsObject

def extract



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

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