omnizip 0.3.37 → 0.3.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +76 -9
- data/docs/guides/archive-formats/gzip-format.adoc +2 -4
- data/docs/guides/archive-formats/ole-format.adoc +54 -219
- data/docs/guides/archive-formats/rar5.adoc +15 -15
- data/docs/guides/archive-formats/rpm-format.adoc +48 -189
- data/docs/reference/api/overview.adoc +16 -17
- data/docs/troubleshooting/index.adoc +1 -4
- data/lib/omnizip/archive_handler.rb +2 -0
- data/lib/omnizip/archive_handlers/cpio_handler.rb +47 -0
- data/lib/omnizip/archive_handlers/iso_handler.rb +54 -0
- data/lib/omnizip/archive_handlers.rb +2 -0
- data/lib/omnizip/cli.rb +25 -0
- data/lib/omnizip/convenience.rb +6 -4
- data/lib/omnizip/formats/iso/directory_builder.rb +24 -0
- data/lib/omnizip/formats/iso/reader.rb +2 -2
- data/lib/omnizip/formats/iso/writer.rb +20 -4
- data/lib/omnizip/formats/iso.rb +3 -1
- data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +5 -3
- data/lib/omnizip/formats/rar/rar5/header.rb +50 -33
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +17 -34
- data/lib/omnizip/formats/rar/rar5/vint.rb +25 -37
- data/lib/omnizip/formats/rar/rar5/writer.rb +50 -14
- data/lib/omnizip/formats/rar.rb +5 -3
- data/lib/omnizip/formats/seven_zip/parser.rb +22 -7
- data/lib/omnizip/formats/seven_zip/writer.rb +78 -19
- data/lib/omnizip/version.rb +1 -1
- metadata +3 -1
|
@@ -8,242 +8,101 @@ grand_parent: Guides
|
|
|
8
8
|
[[rpm-format]]
|
|
9
9
|
== Purpose
|
|
10
10
|
|
|
11
|
-
RPM
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
[cols="1,3"]
|
|
16
|
-
|===
|
|
17
|
-
|Property |Value
|
|
18
|
-
|
|
19
|
-
|Compression
|
|
20
|
-
|gzip, bzip2, xz, or zstd
|
|
21
|
-
|
|
22
|
-
|Encryption
|
|
23
|
-
|GPG signature verification
|
|
24
|
-
|
|
25
|
-
|Archive Support
|
|
26
|
-
|Single package with CPIO payload
|
|
27
|
-
|
|
28
|
-
|Large Files
|
|
29
|
-
|Limited by CPIO and compression
|
|
30
|
-
|
|
31
|
-
|Best For
|
|
32
|
-
|Linux software distribution, RPM-based systems
|
|
33
|
-
|===
|
|
11
|
+
RPM is the package format of Red Hat-based Linux distributions. An
|
|
12
|
+
RPM file is a lead, signature, metadata header and a payload — a
|
|
13
|
+
CPIO archive compressed with gzip/bzip2/xz/zstd.
|
|
34
14
|
|
|
35
15
|
== Basic Usage
|
|
36
16
|
|
|
37
|
-
===
|
|
17
|
+
=== Open a Package
|
|
38
18
|
|
|
39
19
|
[source,ruby]
|
|
40
20
|
----
|
|
41
|
-
|
|
42
|
-
rpm
|
|
43
|
-
|
|
44
|
-
puts "Name: #{rpm.name}"
|
|
45
|
-
puts "Version: #{rpm.version}"
|
|
46
|
-
puts "Release: #{rpm.release}"
|
|
47
|
-
puts "Architecture: #{rpm.arch}"
|
|
48
|
-
puts "Summary: #{rpm.summary}"
|
|
49
|
-
puts "Description: #{rpm.description}"
|
|
50
|
-
|
|
51
|
-
# List package contents
|
|
52
|
-
rpm.each_entry do |entry|
|
|
53
|
-
puts "#{entry.path} (#{entry.size} bytes)"
|
|
21
|
+
Omnizip::Formats::Rpm.open('package.rpm') do |rpm|
|
|
22
|
+
puts rpm.files.size
|
|
54
23
|
end
|
|
55
24
|
|
|
25
|
+
rpm = Omnizip::Formats::Rpm.open('package.rpm')
|
|
56
26
|
rpm.close
|
|
57
27
|
----
|
|
58
28
|
|
|
59
|
-
===
|
|
29
|
+
=== Package Metadata
|
|
60
30
|
|
|
61
31
|
[source,ruby]
|
|
62
32
|
----
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
33
|
+
info = Omnizip::Formats::Rpm.info('package.rpm')
|
|
34
|
+
puts info[:name] # => "app"
|
|
35
|
+
puts info[:version]
|
|
36
|
+
puts info[:release]
|
|
37
|
+
puts info[:arch]
|
|
38
|
+
puts info[:summary]
|
|
39
|
+
puts info[:license]
|
|
40
|
+
puts info[:vendor]
|
|
41
|
+
puts info[:build_time]
|
|
42
|
+
puts info[:file_count]
|
|
43
|
+
|
|
44
|
+
# Same details on the reader
|
|
45
|
+
Omnizip::Formats::Rpm.open('package.rpm') do |rpm|
|
|
46
|
+
puts rpm.name
|
|
47
|
+
puts rpm.version
|
|
48
|
+
puts rpm.license
|
|
69
49
|
end
|
|
70
50
|
----
|
|
71
51
|
|
|
72
|
-
===
|
|
52
|
+
=== List and Extract Files
|
|
73
53
|
|
|
74
54
|
[source,ruby]
|
|
75
55
|
----
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
56
|
+
# File paths inside the payload
|
|
57
|
+
Omnizip::Formats::Rpm.list('package.rpm')
|
|
58
|
+
|
|
59
|
+
# Extract the payload (CPIO contents)
|
|
60
|
+
Omnizip::Formats::Rpm.extract('package.rpm', '/output/directory/')
|
|
61
|
+
|
|
62
|
+
# Or per-reader
|
|
63
|
+
Omnizip::Formats::Rpm.open('package.rpm') do |rpm|
|
|
64
|
+
rpm.extract('/output/directory/')
|
|
81
65
|
end
|
|
82
66
|
----
|
|
83
67
|
|
|
84
68
|
== RPM Structure
|
|
85
69
|
|
|
86
|
-
RPM packages consist of:
|
|
87
|
-
|
|
88
70
|
. **Lead** - 96 bytes identifying the package
|
|
89
|
-
. **Signature** -
|
|
90
|
-
. **Header** -
|
|
91
|
-
. **Payload** - CPIO archive
|
|
71
|
+
. **Signature** - cryptographic signatures
|
|
72
|
+
. **Header** - metadata (name, version, dependencies)
|
|
73
|
+
. **Payload** - CPIO archive
|
|
92
74
|
|
|
93
75
|
[source,ruby]
|
|
94
76
|
----
|
|
95
|
-
|
|
96
|
-
rpm = Omnizip::Rpm.open('package.rpm')
|
|
97
|
-
|
|
98
|
-
# Access lead
|
|
77
|
+
rpm = Omnizip::Formats::Rpm.open('package.rpm')
|
|
99
78
|
puts rpm.lead.inspect
|
|
100
|
-
|
|
101
|
-
# Access signature
|
|
102
79
|
puts rpm.signature.inspect
|
|
103
|
-
|
|
104
|
-
# Access header (metadata)
|
|
105
80
|
puts rpm.header.inspect
|
|
106
|
-
|
|
107
|
-
# Access payload directly (compressed CPIO)
|
|
108
|
-
puts rpm.payload.inspect
|
|
109
81
|
----
|
|
110
82
|
|
|
111
|
-
== Payload
|
|
112
|
-
|
|
113
|
-
The RPM payload is a CPIO archive that can be extracted:
|
|
83
|
+
== Payload
|
|
114
84
|
|
|
115
85
|
[source,ruby]
|
|
116
86
|
----
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
rpm.extract_payload('/output/directory/')
|
|
120
|
-
end
|
|
87
|
+
Omnizip::Formats::Rpm.open('package.rpm') do |rpm|
|
|
88
|
+
puts "Compression: #{rpm.payload_compressor}"
|
|
121
89
|
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
rpm.each_payload_entry do |entry|
|
|
125
|
-
puts "Entry: #{entry.path}"
|
|
126
|
-
puts "Content: #{entry.read[0, 100]}..."
|
|
127
|
-
end
|
|
90
|
+
# Compressed CPIO bytes; decompress with the matching format
|
|
91
|
+
payload = rpm.payload
|
|
128
92
|
end
|
|
129
93
|
----
|
|
130
94
|
|
|
131
|
-
== Compression Types
|
|
132
|
-
|
|
133
|
-
RPM supports four payload compression formats:
|
|
134
|
-
|
|
135
|
-
[cols="2,1,1"]
|
|
136
|
-
|===
|
|
137
|
-
|Format |Extension |Compression Ratio
|
|
138
|
-
|
|
139
|
-
|gzip
|
|
140
|
-
|.gz
|
|
141
|
-
|Good
|
|
142
|
-
|
|
143
|
-
|bzip2
|
|
144
|
-
|.bz2
|
|
145
|
-
|Better
|
|
146
|
-
|
|
147
|
-
|xz
|
|
148
|
-
|.xz
|
|
149
|
-
|Best
|
|
150
|
-
|
|
151
|
-
|zstd
|
|
152
|
-
|.zst
|
|
153
|
-
|Best (modern)
|
|
154
|
-
|===
|
|
155
|
-
|
|
156
|
-
[source,ruby]
|
|
157
|
-
----
|
|
158
|
-
# Check compression type
|
|
159
|
-
rpm = Omnizip::Rpm.open('package.rpm')
|
|
160
|
-
puts "Compression: #{rpm.compression_type}"
|
|
161
|
-
|
|
162
|
-
# Handle different compression types
|
|
163
|
-
case rpm.compression_type
|
|
164
|
-
when :gzip
|
|
165
|
-
puts "Using gzip decompression"
|
|
166
|
-
when :bzip2
|
|
167
|
-
puts "Using bzip2 decompression"
|
|
168
|
-
when :xz
|
|
169
|
-
puts "Using xz decompression"
|
|
170
|
-
when :zstd
|
|
171
|
-
puts "Using zstd decompression"
|
|
172
|
-
end
|
|
173
|
-
----
|
|
174
|
-
|
|
175
|
-
== Scriptlets
|
|
176
|
-
|
|
177
|
-
RPM packages can contain pre/post install scripts:
|
|
178
|
-
|
|
179
|
-
[source,ruby]
|
|
180
|
-
----
|
|
181
|
-
# Access scriptlets
|
|
182
|
-
rpm = Omnizip::Rpm.open('package.rpm')
|
|
183
|
-
|
|
184
|
-
puts "Pre-install script: #{rpm.pre_install_script}"
|
|
185
|
-
puts "Post-install script: #{rpm.post_install_script}"
|
|
186
|
-
puts "Pre-uninstall script: #{rpm.pre_uninstall_script}"
|
|
187
|
-
puts "Post-uninstall script: #{rpm.post_uninstall_script}"
|
|
188
|
-
----
|
|
189
|
-
|
|
190
95
|
== Dependencies
|
|
191
96
|
|
|
192
|
-
RPM packages declare dependencies:
|
|
193
|
-
|
|
194
|
-
[source,ruby]
|
|
195
|
-
----
|
|
196
|
-
# Access dependencies
|
|
197
|
-
rpm = Omnizip::Rpm.open('package.rpm')
|
|
198
|
-
|
|
199
|
-
# Required packages
|
|
200
|
-
puts "Requires:"
|
|
201
|
-
rpm.requires.each do |dep|
|
|
202
|
-
puts " #{dep.name} #{dep.version}"
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# Provided capabilities
|
|
206
|
-
puts "Provides:"
|
|
207
|
-
rpm.provides.each do |cap|
|
|
208
|
-
puts " #{cap.name}"
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# Required capabilities
|
|
212
|
-
puts "Requires:"
|
|
213
|
-
rpm.requires.each do |req|
|
|
214
|
-
puts " #{req.name} #{req.version}"
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
# Conflicts
|
|
218
|
-
puts "Conflicts:"
|
|
219
|
-
rpm.conflicts.each do |conf|
|
|
220
|
-
puts " #{conf.name} #{conf.version}"
|
|
221
|
-
end
|
|
222
|
-
----
|
|
223
|
-
|
|
224
|
-
== File Information
|
|
225
|
-
|
|
226
|
-
RPM tracks detailed file information:
|
|
227
|
-
|
|
228
97
|
[source,ruby]
|
|
229
98
|
----
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
rpm.
|
|
233
|
-
|
|
234
|
-
puts "Size: #{file.size}"
|
|
235
|
-
puts "Mode: #{file.mode.to_s(8)}"
|
|
236
|
-
puts "Owner: #{file.owner}"
|
|
237
|
-
puts "Group: #{file.group}"
|
|
238
|
-
puts "MD5: #{file.md5}" if file.md5
|
|
239
|
-
puts "Is config: #{file.config?}"
|
|
240
|
-
puts "Is doc: #{file.doc?}"
|
|
241
|
-
end
|
|
99
|
+
Omnizip::Formats::Rpm.open('package.rpm') do |rpm|
|
|
100
|
+
rpm.requires.each { |name, op, version| puts "#{name} #{op} #{version}" }
|
|
101
|
+
rpm.provides.each { |name, op, version| puts "#{name} #{op} #{version}" }
|
|
102
|
+
rpm.conflicts.each { |name, op, version| puts "#{name} #{op} #{version}" }
|
|
242
103
|
end
|
|
243
104
|
----
|
|
244
105
|
|
|
245
106
|
== See Also
|
|
246
107
|
|
|
247
|
-
* link:
|
|
248
|
-
* link:xz-format.html[XZ Format] - XZ compression used in some RPMs
|
|
249
|
-
* link:cpio-format.html[CPIO Format] - Underlying payload format
|
|
108
|
+
* link:gzip-format.html[GZIP Format] - Common payload compression
|
|
@@ -99,15 +99,16 @@ writer.close
|
|
|
99
99
|
[source,ruby]
|
|
100
100
|
----
|
|
101
101
|
# 7z archive
|
|
102
|
-
|
|
103
|
-
writer.add_file('document.txt')
|
|
104
|
-
writer.add_directory('photos/')
|
|
105
|
-
|
|
102
|
+
Omnizip::Formats::SevenZip.create('archive.7z') do |writer|
|
|
103
|
+
writer.add_file('document.txt')
|
|
104
|
+
writer.add_directory('photos/')
|
|
105
|
+
end
|
|
106
106
|
|
|
107
107
|
# ZIP archive
|
|
108
108
|
writer = Omnizip::Formats::Zip::Writer.new('archive.zip')
|
|
109
109
|
writer.add_file('document.txt')
|
|
110
|
-
writer.
|
|
110
|
+
writer.add_directory('photos/')
|
|
111
|
+
writer.write
|
|
111
112
|
----
|
|
112
113
|
|
|
113
114
|
==== Extracting Archives
|
|
@@ -115,26 +116,24 @@ writer.close
|
|
|
115
116
|
[source,ruby]
|
|
116
117
|
----
|
|
117
118
|
# 7z archive
|
|
118
|
-
|
|
119
|
-
reader.extract_all('output/')
|
|
120
|
-
|
|
119
|
+
Omnizip::Formats::SevenZip.open('archive.7z') do |reader|
|
|
120
|
+
reader.extract_all('output/')
|
|
121
|
+
end
|
|
121
122
|
|
|
122
|
-
# ZIP archive
|
|
123
|
-
reader = Omnizip::Formats::Zip::Reader.new('archive.zip')
|
|
123
|
+
# ZIP archive (call .read to parse before extracting)
|
|
124
|
+
reader = Omnizip::Formats::Zip::Reader.new('archive.zip').read
|
|
124
125
|
reader.extract_all('output/')
|
|
125
|
-
reader.close
|
|
126
126
|
----
|
|
127
127
|
|
|
128
128
|
==== Listing Contents
|
|
129
129
|
|
|
130
130
|
[source,ruby]
|
|
131
131
|
----
|
|
132
|
-
|
|
133
|
-
reader.
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
Omnizip::Formats::SevenZip.open('archive.7z') do |reader|
|
|
133
|
+
reader.list_files.each do |entry|
|
|
134
|
+
puts "#{entry.name}: #{entry.size} bytes"
|
|
135
|
+
end
|
|
136
136
|
end
|
|
137
|
-
reader.close
|
|
138
137
|
----
|
|
139
138
|
|
|
140
139
|
=== Error Handling
|
|
@@ -145,7 +144,7 @@ begin
|
|
|
145
144
|
Omnizip.compress_file('input.txt', 'output.lzma')
|
|
146
145
|
rescue Omnizip::CompressionError => e
|
|
147
146
|
puts "Compression failed: #{e.message}"
|
|
148
|
-
rescue Omnizip::
|
|
147
|
+
rescue Omnizip::AlgorithmNotFoundError => e
|
|
149
148
|
puts "Algorithm not found: #{e.message}"
|
|
150
149
|
rescue IOError => e
|
|
151
150
|
puts "I/O error: #{e.message}"
|
|
@@ -188,11 +188,8 @@ omnizip archive create archive.zip files/ \
|
|
|
188
188
|
|
|
189
189
|
[source,ruby]
|
|
190
190
|
----
|
|
191
|
-
#
|
|
191
|
+
# One reader covers both RAR4 and RAR5 (version auto-detected)
|
|
192
192
|
reader = Omnizip::Formats::Rar::Reader.new('archive.rar')
|
|
193
|
-
|
|
194
|
-
# Or RAR5 reader
|
|
195
|
-
reader = Omnizip::Formats::Rar::Rar5::Reader.new('archive.rar')
|
|
196
193
|
----
|
|
197
194
|
|
|
198
195
|
### Getting Help
|
|
@@ -71,6 +71,8 @@ module Omnizip
|
|
|
71
71
|
tar: -> { Omnizip::ArchiveHandlers::TarHandler },
|
|
72
72
|
seven_zip: -> { Omnizip::ArchiveHandlers::SevenZipHandler },
|
|
73
73
|
rar: -> { Omnizip::ArchiveHandlers::RarHandler },
|
|
74
|
+
cpio: -> { Omnizip::ArchiveHandlers::CpioHandler },
|
|
75
|
+
iso: -> { Omnizip::ArchiveHandlers::IsoHandler },
|
|
74
76
|
}.freeze
|
|
75
77
|
private_constant :LAZY_LOAD_TRIGGERS
|
|
76
78
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tmpdir"
|
|
4
|
+
|
|
5
|
+
module Omnizip
|
|
6
|
+
module ArchiveHandlers
|
|
7
|
+
# Read-only adapter for the CPIO format (initramfs images, RPM
|
|
8
|
+
# payloads). Extraction and listing are supported; CPIO writing
|
|
9
|
+
# exists at the format level but is not exposed through the
|
|
10
|
+
# convenience archive API.
|
|
11
|
+
class CpioHandler
|
|
12
|
+
def create(_path, **_options)
|
|
13
|
+
raise Omnizip::UnsupportedFormatError,
|
|
14
|
+
"cpio archives cannot be created through the convenience " \
|
|
15
|
+
"API; use Omnizip::Formats::Cpio.create directly"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def extract_to(path, output_dir, **_)
|
|
19
|
+
Omnizip::Formats::Cpio.extract(path, output_dir)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def list(path, details: false, **_)
|
|
23
|
+
entries = Omnizip::Formats::Cpio.list(path)
|
|
24
|
+
if details
|
|
25
|
+
entries.map do |e|
|
|
26
|
+
{ name: e.name, size: e.data.bytesize,
|
|
27
|
+
directory: e.name.end_with?("/"), mtime: nil }
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
entries.map(&:name)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def read_entry(path, entry_name, **_)
|
|
35
|
+
entry = Omnizip::Formats::Cpio.list(path)
|
|
36
|
+
.find { |e| e.name == entry_name }
|
|
37
|
+
unless entry
|
|
38
|
+
raise Errno::ENOENT, "Entry not found: #{entry_name}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
entry.data
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
Omnizip::ArchiveHandler.register(:cpio, Omnizip::ArchiveHandlers::CpioHandler.new)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tmpdir"
|
|
4
|
+
|
|
5
|
+
module Omnizip
|
|
6
|
+
module ArchiveHandlers
|
|
7
|
+
# Read-only adapter for the ISO 9660 format (disk images).
|
|
8
|
+
# Extraction and listing are supported; ISO writing exists at the
|
|
9
|
+
# format level but is not exposed through the convenience archive
|
|
10
|
+
# API.
|
|
11
|
+
class IsoHandler
|
|
12
|
+
def create(_path, **_options)
|
|
13
|
+
raise Omnizip::UnsupportedFormatError,
|
|
14
|
+
"iso images cannot be created through the convenience " \
|
|
15
|
+
"API; use Omnizip::Formats::Iso.create directly"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def extract_to(path, output_dir, **_)
|
|
19
|
+
Omnizip::Formats::Iso.extract(path, output_dir)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def list(path, details: false, **_)
|
|
23
|
+
entries = Omnizip::Formats::Iso.list(path)
|
|
24
|
+
if details
|
|
25
|
+
entries.map do |e|
|
|
26
|
+
{ name: e.full_path, size: e.size, directory: e.directory?,
|
|
27
|
+
mtime: e.recording_date }
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
entries.map(&:full_path)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def read_entry(path, entry_name, **_)
|
|
35
|
+
entry = Omnizip::Formats::Iso.list(path)
|
|
36
|
+
.find { |e| e.full_path == entry_name }
|
|
37
|
+
unless entry
|
|
38
|
+
raise Errno::ENOENT, "Entry not found: #{entry_name}"
|
|
39
|
+
end
|
|
40
|
+
raise Errno::EISDIR, "Entry is a directory: #{entry_name}" if entry.directory?
|
|
41
|
+
|
|
42
|
+
Dir.mktmpdir("omnizip-iso-entry") do |dir|
|
|
43
|
+
dest = File.join(dir, "entry")
|
|
44
|
+
Omnizip::Formats::Iso.open(path) do |iso|
|
|
45
|
+
iso.extract_entry(entry_name, dest)
|
|
46
|
+
end
|
|
47
|
+
File.binread(dest)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Omnizip::ArchiveHandler.register(:iso, Omnizip::ArchiveHandlers::IsoHandler.new)
|
|
@@ -9,5 +9,7 @@ module Omnizip
|
|
|
9
9
|
autoload :TarHandler, "omnizip/archive_handlers/tar_handler"
|
|
10
10
|
autoload :SevenZipHandler, "omnizip/archive_handlers/seven_zip_handler"
|
|
11
11
|
autoload :RarHandler, "omnizip/archive_handlers/rar_handler"
|
|
12
|
+
autoload :CpioHandler, "omnizip/archive_handlers/cpio_handler"
|
|
13
|
+
autoload :IsoHandler, "omnizip/archive_handlers/iso_handler"
|
|
12
14
|
end
|
|
13
15
|
end
|
data/lib/omnizip/cli.rb
CHANGED
|
@@ -25,6 +25,28 @@ module Omnizip
|
|
|
25
25
|
# under Omnizip::Cli would re-trigger this file's autoload
|
|
26
26
|
# mid-load.
|
|
27
27
|
module Shared
|
|
28
|
+
# Thor 1.x does not treat --help/-h on subcommands as a request
|
|
29
|
+
# for help: the flag reaches the command as a positional argument
|
|
30
|
+
# and arity checking fails. Classes extending this module route
|
|
31
|
+
# those flags to the standard help output instead.
|
|
32
|
+
module HelpDispatch
|
|
33
|
+
def dispatch(meth, given_args, given_opts, config)
|
|
34
|
+
wants_help = [Array(given_args), Array(given_opts)].any? do |list|
|
|
35
|
+
list.include?("--help") || list.include?("-h")
|
|
36
|
+
end
|
|
37
|
+
if wants_help
|
|
38
|
+
name = meth || Array(given_args).grep_v(/\A-/).first
|
|
39
|
+
# Subcommand groups intercept their own --help (e.g.
|
|
40
|
+
# "archive create --help" must show the create usage).
|
|
41
|
+
return super if subcommands.include?(name)
|
|
42
|
+
|
|
43
|
+
return start(["help", name].compact, config)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
super
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
28
50
|
# Print a formatted error message and exit nonzero.
|
|
29
51
|
#
|
|
30
52
|
# @param error [StandardError] the error to display
|
|
@@ -51,6 +73,7 @@ module Omnizip
|
|
|
51
73
|
# Profile commands subcommand group
|
|
52
74
|
class ProfileCommands < Thor
|
|
53
75
|
include Shared
|
|
76
|
+
extend Shared::HelpDispatch
|
|
54
77
|
|
|
55
78
|
class << self
|
|
56
79
|
def exit_on_failure?
|
|
@@ -99,6 +122,7 @@ module Omnizip
|
|
|
99
122
|
# Archive commands subcommand group
|
|
100
123
|
class ArchiveCommands < Thor
|
|
101
124
|
include Shared
|
|
125
|
+
extend Shared::HelpDispatch
|
|
102
126
|
|
|
103
127
|
class << self
|
|
104
128
|
def exit_on_failure?
|
|
@@ -328,6 +352,7 @@ module Omnizip
|
|
|
328
352
|
# files using various compression algorithms.
|
|
329
353
|
class Cli < Thor
|
|
330
354
|
include Shared
|
|
355
|
+
extend Shared::HelpDispatch
|
|
331
356
|
|
|
332
357
|
class << self
|
|
333
358
|
def exit_on_failure?
|
data/lib/omnizip/convenience.rb
CHANGED
|
@@ -230,16 +230,18 @@ module Omnizip
|
|
|
230
230
|
".7z" => :seven_zip,
|
|
231
231
|
}.freeze
|
|
232
232
|
|
|
233
|
-
# Extensions naming
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
233
|
+
# Extensions naming formats with read-only convenience routing.
|
|
234
|
+
# Writing them a ZIP under a foreign name was silent corruption;
|
|
235
|
+
# creation raises truthfully instead (the format-level writers
|
|
236
|
+
# exist for RAR/CPIO/ISO but are not part of the archive API).
|
|
237
237
|
READ_ONLY_FORMAT_EXTENSIONS = [".rar", ".iso", ".cpio"].freeze
|
|
238
238
|
|
|
239
239
|
# Extensions whose format has a READ-ONLY handler: extraction and
|
|
240
240
|
# listing route to it, while creation keeps raising.
|
|
241
241
|
READ_ARCHIVE_FORMAT_EXTENSIONS = {
|
|
242
242
|
".rar" => :rar,
|
|
243
|
+
".cpio" => :cpio,
|
|
244
|
+
".iso" => :iso,
|
|
243
245
|
}.freeze
|
|
244
246
|
|
|
245
247
|
# Extension -> single-file decompressor (stream interface).
|
|
@@ -43,6 +43,12 @@ module Omnizip
|
|
|
43
43
|
# Allocate sectors for directories and files
|
|
44
44
|
allocate_sectors(tree)
|
|
45
45
|
|
|
46
|
+
# Tree nodes are separate hashes from @files entries; copy
|
|
47
|
+
# the allocated file locations onto the tree so directory
|
|
48
|
+
# records reference the real extents (otherwise every
|
|
49
|
+
# record points at sector 0).
|
|
50
|
+
assign_file_locations(tree)
|
|
51
|
+
|
|
46
52
|
# Build path table
|
|
47
53
|
path_table = build_path_table(tree)
|
|
48
54
|
|
|
@@ -143,6 +149,24 @@ module Omnizip
|
|
|
143
149
|
allocate_file_sectors
|
|
144
150
|
end
|
|
145
151
|
|
|
152
|
+
# Copy allocated file locations onto the matching tree nodes
|
|
153
|
+
#
|
|
154
|
+
# @param tree [Hash] Directory tree root
|
|
155
|
+
def assign_file_locations(tree)
|
|
156
|
+
walk = lambda do |node|
|
|
157
|
+
(node[:children] || []).each do |child|
|
|
158
|
+
if child[:directory]
|
|
159
|
+
walk.call(child)
|
|
160
|
+
else
|
|
161
|
+
match = @files.find { |f| f[:iso_path] == child[:iso_path] }
|
|
162
|
+
child[:location] = match[:location] if match
|
|
163
|
+
child[:size] = match[:size] if match
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
walk.call(tree)
|
|
168
|
+
end
|
|
169
|
+
|
|
146
170
|
# Allocate sectors for a directory
|
|
147
171
|
#
|
|
148
172
|
# @param dir_node [Hash] Directory node
|
|
@@ -90,8 +90,8 @@ module Omnizip
|
|
|
90
90
|
@entries.each do |entry|
|
|
91
91
|
next if entry.current_directory? || entry.parent_directory?
|
|
92
92
|
|
|
93
|
-
output_path = File.join(output_dir, entry.
|
|
94
|
-
extract_entry(entry.
|
|
93
|
+
output_path = File.join(output_dir, entry.full_path)
|
|
94
|
+
extract_entry(entry.full_path, output_path)
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
|
|
@@ -178,8 +178,13 @@ module Omnizip
|
|
|
178
178
|
preparer: "OMNIZIP #{Omnizip::VERSION}",
|
|
179
179
|
application: "OMNIZIP",
|
|
180
180
|
level: 2,
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
# Rock Ridge System Use fields and Joliet UCS-2 directory
|
|
182
|
+
# trees are not implemented; advertising either extension
|
|
183
|
+
# makes readers misparse the image (e.g. 7-Zip reads the
|
|
184
|
+
# ASCII names as UCS-2 via the cloned SVD), so both stay
|
|
185
|
+
# off until real implementations land.
|
|
186
|
+
rock_ridge: false,
|
|
187
|
+
joliet: false,
|
|
183
188
|
}
|
|
184
189
|
end
|
|
185
190
|
|
|
@@ -193,6 +198,7 @@ module Omnizip
|
|
|
193
198
|
source: File.expand_path(dir_path),
|
|
194
199
|
iso_path: iso_path,
|
|
195
200
|
stat: File.stat(dir_path),
|
|
201
|
+
directory: true,
|
|
196
202
|
}
|
|
197
203
|
|
|
198
204
|
# Add all contents
|
|
@@ -219,6 +225,7 @@ module Omnizip
|
|
|
219
225
|
source: File.expand_path(dir_path),
|
|
220
226
|
iso_path: iso_path,
|
|
221
227
|
stat: File.stat(dir_path),
|
|
228
|
+
directory: true,
|
|
222
229
|
}
|
|
223
230
|
|
|
224
231
|
Dir.foreach(dir_path) do |entry|
|
|
@@ -283,13 +290,22 @@ module Omnizip
|
|
|
283
290
|
# @param builder [VolumeBuilder] Volume builder
|
|
284
291
|
# @param dir_structure [Hash] Directory structure
|
|
285
292
|
def write_volume_descriptors(io, builder, dir_structure)
|
|
293
|
+
# Fixed layout: 16 PVD, 17 terminator, 18 path table (L),
|
|
294
|
+
# 19 path table (BE), directory data from sector 22.
|
|
295
|
+
root = dir_structure[:root].merge(
|
|
296
|
+
total_sectors: dir_structure[:total_sectors],
|
|
297
|
+
path_table_size: dir_structure[:path_table_size],
|
|
298
|
+
path_table_location: 18,
|
|
299
|
+
path_table_location_be: 19,
|
|
300
|
+
)
|
|
301
|
+
|
|
286
302
|
# Write primary volume descriptor
|
|
287
|
-
pvd = builder.build_primary(
|
|
303
|
+
pvd = builder.build_primary(root)
|
|
288
304
|
io.write(pvd)
|
|
289
305
|
|
|
290
306
|
# Write Joliet supplementary descriptor if enabled
|
|
291
307
|
if @joliet
|
|
292
|
-
svd = builder.build_joliet(
|
|
308
|
+
svd = builder.build_joliet(root)
|
|
293
309
|
io.write(svd)
|
|
294
310
|
end
|
|
295
311
|
|
data/lib/omnizip/formats/iso.rb
CHANGED
|
@@ -69,7 +69,9 @@ module Omnizip
|
|
|
69
69
|
# @param path [String] Path to ISO file
|
|
70
70
|
# @return [Array<DirectoryRecord>] Directory entries
|
|
71
71
|
def self.list(path)
|
|
72
|
-
|
|
72
|
+
entries = nil
|
|
73
|
+
open(path) { |iso| entries = iso.entries }
|
|
74
|
+
entries
|
|
73
75
|
end
|
|
74
76
|
|
|
75
77
|
# Extract ISO contents
|