Class: Omnizip::Formats::Cpio::Entry

Inherits:
Object
  • Object
show all
Includes:
Constants
Defined in:
lib/omnizip/formats/cpio/entry.rb

Overview

CPIO archive entry

Represents a file, directory, or special file in a CPIO archive. Supports newc, CRC, and ODC formats.

Constant Summary

Constants included from Constants

Constants::MAGIC_BINARY, Constants::MAGIC_CRC, Constants::MAGIC_NEWC, Constants::MAGIC_ODC, Constants::NEWC_ALIGNMENT, Constants::NEWC_HEADER_SIZE, Constants::S_IFBLK, Constants::S_IFCHR, Constants::S_IFDIR, Constants::S_IFIFO, Constants::S_IFLNK, Constants::S_IFMT, Constants::S_IFREG, Constants::S_IFSOCK, Constants::S_IRGRP, Constants::S_IROTH, Constants::S_IRUSR, Constants::S_IRWXG, Constants::S_IRWXO, Constants::S_IRWXU, Constants::S_ISGID, Constants::S_ISUID, Constants::S_ISVTX, Constants::S_IWGRP, Constants::S_IWOTH, Constants::S_IWUSR, Constants::S_IXGRP, Constants::S_IXOTH, Constants::S_IXUSR, Constants::TRAILER_NAME

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}) ⇒ Entry

Initialize CPIO entry

Parameters:

  • attributes (Hash) (defaults to: {})

    Entry attributes



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/omnizip/formats/cpio/entry.rb', line 64

def initialize(attributes = {})
  @magic = attributes.fetch(:magic, MAGIC_NEWC)
  @ino = attributes.fetch(:ino, 0)
  @mode = attributes.fetch(:mode, S_IFREG | 0o644)
  @uid = attributes.fetch(:uid, 0)
  @gid = attributes.fetch(:gid, 0)
  @nlink = attributes.fetch(:nlink, 1)
  @mtime = attributes.fetch(:mtime, Time.now.to_i)
  @filesize = attributes.fetch(:filesize, 0)
  @dev_major = attributes.fetch(:dev_major, 0)
  @dev_minor = attributes.fetch(:dev_minor, 0)
  @rdev_major = attributes.fetch(:rdev_major, 0)
  @rdev_minor = attributes.fetch(:rdev_minor, 0)
  @namesize = attributes.fetch(:namesize, 0)
  @checksum = attributes.fetch(:checksum, 0)
  @name = attributes.fetch(:name, "")
  @data = attributes.fetch(:data, "")

  # Auto-calculate namesize if not provided
  @namesize = @name.bytesize + 1 if @namesize.zero? && !@name.empty?
  @filesize = @data.bytesize if @filesize.zero? && !@data.empty?
end

Instance Attribute Details

#checksumInteger

Returns Checksum (CRC format only).

Returns:

  • (Integer)

    Checksum (CRC format only)



53
54
55
# File 'lib/omnizip/formats/cpio/entry.rb', line 53

def checksum
  @checksum
end

#dataString

Returns File data.

Returns:

  • (String)

    File data



59
60
61
# File 'lib/omnizip/formats/cpio/entry.rb', line 59

def data
  @data
end

#dev_majorInteger

Returns Device major number.

Returns:

  • (Integer)

    Device major number



38
39
40
# File 'lib/omnizip/formats/cpio/entry.rb', line 38

def dev_major
  @dev_major
end

#dev_minorInteger

Returns Device minor number.

Returns:

  • (Integer)

    Device minor number



41
42
43
# File 'lib/omnizip/formats/cpio/entry.rb', line 41

def dev_minor
  @dev_minor
end

#filesizeInteger

Returns File size in bytes.

Returns:

  • (Integer)

    File size in bytes



35
36
37
# File 'lib/omnizip/formats/cpio/entry.rb', line 35

def filesize
  @filesize
end

#gidInteger

Returns Group ID.

Returns:

  • (Integer)

    Group ID



26
27
28
# File 'lib/omnizip/formats/cpio/entry.rb', line 26

def gid
  @gid
end

#inoInteger

Returns Inode number.

Returns:

  • (Integer)

    Inode number



17
18
19
# File 'lib/omnizip/formats/cpio/entry.rb', line 17

def ino
  @ino
end

#magicString

Returns Magic number identifying format.

Returns:

  • (String)

    Magic number identifying format



14
15
16
# File 'lib/omnizip/formats/cpio/entry.rb', line 14

def magic
  @magic
end

#modeInteger

Returns File mode and type.

Returns:

  • (Integer)

    File mode and type



20
21
22
# File 'lib/omnizip/formats/cpio/entry.rb', line 20

def mode
  @mode
end

#mtimeInteger

Returns Modification time (Unix timestamp).

Returns:

  • (Integer)

    Modification time (Unix timestamp)



32
33
34
# File 'lib/omnizip/formats/cpio/entry.rb', line 32

def mtime
  @mtime
end

#nameString

Returns Entry name/path.

Returns:

  • (String)

    Entry name/path



56
57
58
# File 'lib/omnizip/formats/cpio/entry.rb', line 56

def name
  @name
end

#namesizeInteger

Returns Filename length (including null terminator).

Returns:

  • (Integer)

    Filename length (including null terminator)



50
51
52
# File 'lib/omnizip/formats/cpio/entry.rb', line 50

def namesize
  @namesize
end

Returns Number of hard links.

Returns:

  • (Integer)

    Number of hard links



29
30
31
# File 'lib/omnizip/formats/cpio/entry.rb', line 29

def nlink
  @nlink
end

#rdev_majorInteger

Returns Special device major number (for device files).

Returns:

  • (Integer)

    Special device major number (for device files)



44
45
46
# File 'lib/omnizip/formats/cpio/entry.rb', line 44

def rdev_major
  @rdev_major
end

#rdev_minorInteger

Returns Special device minor number (for device files).

Returns:

  • (Integer)

    Special device minor number (for device files)



47
48
49
# File 'lib/omnizip/formats/cpio/entry.rb', line 47

def rdev_minor
  @rdev_minor
end

#uidInteger

Returns User ID.

Returns:

  • (Integer)

    User ID



23
24
25
# File 'lib/omnizip/formats/cpio/entry.rb', line 23

def uid
  @uid
end

Class Method Details

.detect_format(magic) ⇒ Symbol

Detect CPIO format from magic

Parameters:

  • magic (String)

    Magic bytes

Returns:

  • (Symbol)

    Format type



237
238
239
240
241
242
243
244
245
246
247
# File 'lib/omnizip/formats/cpio/entry.rb', line 237

def self.detect_format(magic)
  case magic
  when MAGIC_NEWC then :newc
  when MAGIC_CRC then :crc
  when MAGIC_ODC then :odc
  else
    # Try binary format
    magic_int = magic.unpack1("n")
    magic_int == MAGIC_BINARY ? :binary : nil
  end
end

.padding_to_align(size, alignment) ⇒ Object

Class method for padding calculation



376
377
378
379
# File 'lib/omnizip/formats/cpio/entry.rb', line 376

def self.padding_to_align(size, alignment)
  remainder = size % alignment
  remainder.zero? ? 0 : alignment - remainder
end

.parse(io, format: nil) ⇒ Entry

Parse entry from binary data

Parameters:

  • io (IO)

    Input stream

  • format (Symbol, nil) (defaults to: nil)

    Format hint (:newc, :crc, :odc, nil=auto-detect)

Returns:

  • (Entry)

    Parsed entry



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/omnizip/formats/cpio/entry.rb', line 216

def self.parse(io, format: nil)
  # Read magic to detect format
  magic = io.read(6)
  io.seek(-6, ::IO::SEEK_CUR) # Rewind

  format ||= detect_format(magic)

  case format
  when :newc, :crc
    parse_newc(io)
  when :odc
    parse_odc(io)
  else
    raise "Unknown CPIO format"
  end
end

.parse_newc(io) ⇒ Entry

Parse newc format entry

Parameters:

  • io (IO)

    Input stream

Returns:

  • (Entry)

    Parsed entry



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/omnizip/formats/cpio/entry.rb', line 253

def self.parse_newc(io)
  header = io.read(NEWC_HEADER_SIZE)
  raise "Truncated CPIO header" unless header && header.bytesize == NEWC_HEADER_SIZE

  # Parse ASCII hex fields
  magic = header[0, 6]
  ino = header[6, 8].to_i(16)
  mode = header[14, 8].to_i(16)
  uid = header[22, 8].to_i(16)
  gid = header[30, 8].to_i(16)
  nlink = header[38, 8].to_i(16)
  mtime = header[46, 8].to_i(16)
  filesize = header[54, 8].to_i(16)
  dev_major = header[62, 8].to_i(16)
  dev_minor = header[70, 8].to_i(16)
  rdev_major = header[78, 8].to_i(16)
  rdev_minor = header[86, 8].to_i(16)
  namesize = header[94, 8].to_i(16)
  checksum = header[102, 8].to_i(16)

  # Read filename
  name_data = io.read(namesize)
  name = name_data.chomp("\x00")

  # Skip padding to 4-byte boundary
  header_name_size = NEWC_HEADER_SIZE + namesize
  padding = padding_to_align(header_name_size, NEWC_ALIGNMENT)
  io.read(padding) if padding.positive?

  # Read file data
  data = io.read(filesize)

  # Skip padding to 4-byte boundary
  data_padding = padding_to_align(filesize, NEWC_ALIGNMENT)
  io.read(data_padding) if data_padding.positive?

  new(
    magic: magic,
    ino: ino,
    mode: mode,
    uid: uid,
    gid: gid,
    nlink: nlink,
    mtime: mtime,
    filesize: filesize,
    dev_major: dev_major,
    dev_minor: dev_minor,
    rdev_major: rdev_major,
    rdev_minor: rdev_minor,
    namesize: namesize,
    checksum: checksum,
    name: name,
    data: data || "",
  )
end

.parse_odc(io) ⇒ Entry

Parse ODC format entry

Parameters:

  • io (IO)

    Input stream

Returns:

  • (Entry)

    Parsed entry



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/omnizip/formats/cpio/entry.rb', line 313

def self.parse_odc(io)
  header = io.read(76)
  raise "Truncated CPIO header" unless header && header.bytesize == 76

  # Parse octal fields
  magic = header[0, 6]
  dev = header[6, 6].to_i(8)
  ino = header[12, 6].to_i(8)
  mode = header[18, 6].to_i(8)
  uid = header[24, 6].to_i(8)
  gid = header[30, 6].to_i(8)
  nlink = header[36, 6].to_i(8)
  rdev = header[42, 6].to_i(8)
  mtime = header[48, 11].to_i(8)
  namesize = header[59, 6].to_i(8)
  filesize = header[65, 11].to_i(8)

  # Extract device numbers
  dev_major = dev >> 8
  dev_minor = dev & 0xFF
  rdev_major = rdev >> 8
  rdev_minor = rdev & 0xFF

  # Read filename
  name_data = io.read(namesize)
  name = name_data.chomp("\x00")

  # Read file data
  data = io.read(filesize)

  new(
    magic: magic,
    ino: ino,
    mode: mode,
    uid: uid,
    gid: gid,
    nlink: nlink,
    mtime: mtime,
    filesize: filesize,
    dev_major: dev_major,
    dev_minor: dev_minor,
    rdev_major: rdev_major,
    rdev_minor: rdev_minor,
    namesize: namesize,
    checksum: 0,
    name: name,
    data: data || "",
  )
end

Instance Method Details

#device?Boolean

Check if entry is a device

Returns:

  • (Boolean)

    true if device (block or character)



111
112
113
114
# File 'lib/omnizip/formats/cpio/entry.rb', line 111

def device?
  type = @mode & S_IFMT
  [S_IFBLK, S_IFCHR].include?(type)
end

#directory?Boolean

Check if entry is a directory

Returns:

  • (Boolean)

    true if directory



90
91
92
# File 'lib/omnizip/formats/cpio/entry.rb', line 90

def directory?
  (@mode & S_IFMT) == S_IFDIR
end

#file?Boolean

Check if entry is a regular file

Returns:

  • (Boolean)

    true if regular file



97
98
99
# File 'lib/omnizip/formats/cpio/entry.rb', line 97

def file?
  (@mode & S_IFMT) == S_IFREG
end

#symlink?Boolean

Check if entry is a symbolic link

Returns:

  • (Boolean)

    true if symlink



104
105
106
# File 'lib/omnizip/formats/cpio/entry.rb', line 104

def symlink?
  (@mode & S_IFMT) == S_IFLNK
end

#to_binary(format: :newc) ⇒ String

Convert entry to binary format

Parameters:

  • format (Symbol) (defaults to: :newc)

    CPIO format (:newc, :crc, :odc)

Returns:

  • (String)

    Binary representation



127
128
129
130
131
132
133
134
135
136
# File 'lib/omnizip/formats/cpio/entry.rb', line 127

def to_binary(format: :newc)
  case format
  when :newc, :crc
    to_newc_binary
  when :odc
    to_odc_binary
  else
    raise ArgumentError, "Unsupported CPIO format: #{format}"
  end
end

#to_newc_binaryString

Convert to newc format binary

Returns:

  • (String)

    Binary data in newc format



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/omnizip/formats/cpio/entry.rb', line 141

def to_newc_binary
  # Build header (110 bytes, ASCII hex)
  header = format(
    "%06s%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x",
    @magic,
    @ino & 0xFFFFFFFF,
    @mode & 0xFFFFFFFF,
    @uid & 0xFFFFFFFF,
    @gid & 0xFFFFFFFF,
    @nlink & 0xFFFFFFFF,
    @mtime & 0xFFFFFFFF,
    @filesize & 0xFFFFFFFF,
    @dev_major & 0xFFFFFFFF,
    @dev_minor & 0xFFFFFFFF,
    @rdev_major & 0xFFFFFFFF,
    @rdev_minor & 0xFFFFFFFF,
    @namesize & 0xFFFFFFFF,
    @checksum & 0xFFFFFFFF,
  )

  # Assemble complete entry
  result = +""
  result << header
  result << @name
  result << "\x00"

  # Pad header+name to 4-byte boundary
  header_name_size = header.bytesize + @name.bytesize + 1
  padding = padding_to_align(header_name_size, NEWC_ALIGNMENT)
  result << ("\x00" * padding) if padding.positive?

  # Add file data
  result << @data

  # Pad data to 4-byte boundary
  data_padding = padding_to_align(@data.bytesize, NEWC_ALIGNMENT)
  result << ("\x00" * data_padding) if data_padding.positive?

  result
end

#to_odc_binaryString

Convert to ODC format binary

Returns:

  • (String)

    Binary data in ODC format



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/omnizip/formats/cpio/entry.rb', line 185

def to_odc_binary
  # ODC format uses 6-character octal fields
  header = format(
    "%06o%06o%06o%06o%06o%06o%06o%06o%011o%06o%011o",
    MAGIC_BINARY,
    (@dev_major << 8) | @dev_minor,
    @ino & 0o777777,
    @mode & 0o777777,
    @uid & 0o777777,
    @gid & 0o777777,
    @nlink & 0o777777,
    (@rdev_major << 8) | @rdev_minor,
    @mtime & 0o77777777777,
    @namesize & 0o777777,
    @filesize & 0o77777777777,
  )

  result = +""
  result << header
  result << @name
  result << "\x00"
  result << @data

  result
end

#trailer?Boolean

Check if entry is the trailer

Returns:

  • (Boolean)

    true if trailer entry



119
120
121
# File 'lib/omnizip/formats/cpio/entry.rb', line 119

def trailer?
  @name == TRAILER_NAME
end