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

Inherits:
Object
  • Object
show all
Includes:
Entry, 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



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/omnizip/formats/cpio/entry.rb', line 70

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)



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

def checksum
  @checksum
end

#dataString

Returns File data.

Returns:

  • (String)

    File data



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

def data
  @data
end

#dev_majorInteger

Returns Device major number.

Returns:

  • (Integer)

    Device major number



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

def dev_major
  @dev_major
end

#dev_minorInteger

Returns Device minor number.

Returns:

  • (Integer)

    Device minor number



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

def dev_minor
  @dev_minor
end

#filesizeInteger

Returns File size in bytes.

Returns:

  • (Integer)

    File size in bytes



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

def filesize
  @filesize
end

#gidInteger

Returns Group ID.

Returns:

  • (Integer)

    Group ID



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

def gid
  @gid
end

#inoInteger

Returns Inode number.

Returns:

  • (Integer)

    Inode number



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

def ino
  @ino
end

#magicString

Returns Magic number identifying format.

Returns:

  • (String)

    Magic number identifying format



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

def magic
  @magic
end

#modeInteger

Returns File mode and type.

Returns:

  • (Integer)

    File mode and type



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

def mode
  @mode
end

#mtimeInteger

Returns Modification time (Unix timestamp).

Returns:

  • (Integer)

    Modification time (Unix timestamp)



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

def mtime
  @mtime
end

#nameString

Returns Entry name/path.

Returns:

  • (String)

    Entry name/path



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

def name
  @name
end

#namesizeInteger

Returns Filename length (including null terminator).

Returns:

  • (Integer)

    Filename length (including null terminator)



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

def namesize
  @namesize
end

Returns Number of hard links.

Returns:

  • (Integer)

    Number of hard links



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

def nlink
  @nlink
end

#rdev_majorInteger

Returns Special device major number (for device files).

Returns:

  • (Integer)

    Special device major number (for device files)



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

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)



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

def rdev_minor
  @rdev_minor
end

#uidInteger

Returns User ID.

Returns:

  • (Integer)

    User ID



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

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



243
244
245
246
247
248
249
250
251
252
253
# File 'lib/omnizip/formats/cpio/entry.rb', line 243

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



382
383
384
385
# File 'lib/omnizip/formats/cpio/entry.rb', line 382

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



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/omnizip/formats/cpio/entry.rb', line 222

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



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
308
309
310
311
312
313
# File 'lib/omnizip/formats/cpio/entry.rb', line 259

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



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
362
363
364
365
366
367
# File 'lib/omnizip/formats/cpio/entry.rb', line 319

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)



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

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



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

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

#entry_directory?Boolean

Returns:

  • (Boolean)


63
# File 'lib/omnizip/formats/cpio/entry.rb', line 63

def entry_directory? = directory?

#entry_mtimeObject



65
# File 'lib/omnizip/formats/cpio/entry.rb', line 65

def entry_mtime = mtime

#entry_nameObject



62
# File 'lib/omnizip/formats/cpio/entry.rb', line 62

def entry_name = name

#entry_sizeObject



64
# File 'lib/omnizip/formats/cpio/entry.rb', line 64

def entry_size = filesize

#file?Boolean

Check if entry is a regular file

Returns:

  • (Boolean)

    true if regular file



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

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

#symlink?Boolean

Check if entry is a symbolic link

Returns:

  • (Boolean)

    true if symlink



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

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



133
134
135
136
137
138
139
140
141
142
# File 'lib/omnizip/formats/cpio/entry.rb', line 133

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



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
181
182
183
184
185
186
# File 'lib/omnizip/formats/cpio/entry.rb', line 147

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



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/omnizip/formats/cpio/entry.rb', line 191

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



125
126
127
# File 'lib/omnizip/formats/cpio/entry.rb', line 125

def trailer?
  @name == TRAILER_NAME
end