Skip to content

Commit af2b7cd

Browse files
authored
Merge pull request #91 from Augani/codex/fix-release-lzfse
Restore release LZFSE packaging command
2 parents 47a367e + 545f64e commit af2b7cd

3 files changed

Lines changed: 187 additions & 1 deletion

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import Compression
2+
import Foundation
3+
4+
public enum LZFSEError: Error, CustomStringConvertible {
5+
case openInput(String)
6+
case openOutput(String)
7+
case streamInit
8+
case read
9+
case write
10+
case process
11+
12+
public var description: String {
13+
switch self {
14+
case .openInput(let path): "cannot open input \(path)"
15+
case .openOutput(let path): "cannot open output \(path)"
16+
case .streamInit: "compression_stream_init failed"
17+
case .read: "read failed"
18+
case .write: "write failed"
19+
case .process: "compression_stream_process failed"
20+
}
21+
}
22+
}
23+
24+
/// Streaming LZFSE codec over Apple's Compression framework. Release assembly uses this exact
25+
/// implementation to compress guest payloads, while the installed app uses the same format when it
26+
/// expands those payloads on first launch. Keeping the codec in the signed runner avoids an ambient
27+
/// Homebrew or system-tool dependency in the public release path.
28+
public enum LZFSE {
29+
private static let chunk = 1 << 20
30+
31+
public static func compress(source: String, destination: String) throws {
32+
try transform(source: source, destination: destination, operation: COMPRESSION_STREAM_ENCODE)
33+
}
34+
35+
public static func decompress(source: String, destination: String) throws {
36+
try transform(source: source, destination: destination, operation: COMPRESSION_STREAM_DECODE)
37+
}
38+
39+
private static func transform(
40+
source: String,
41+
destination: String,
42+
operation: compression_stream_operation
43+
) throws {
44+
guard FileManager.default.isReadableFile(atPath: source),
45+
let input = InputStream(fileAtPath: source) else {
46+
throw LZFSEError.openInput(source)
47+
}
48+
guard let output = OutputStream(toFileAtPath: destination, append: false) else {
49+
throw LZFSEError.openOutput(destination)
50+
}
51+
input.open()
52+
output.open()
53+
defer {
54+
input.close()
55+
output.close()
56+
}
57+
58+
let sourceBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: chunk)
59+
let destinationBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: chunk)
60+
defer {
61+
sourceBuffer.deallocate()
62+
destinationBuffer.deallocate()
63+
}
64+
65+
var stream = compression_stream(
66+
dst_ptr: destinationBuffer,
67+
dst_size: chunk,
68+
src_ptr: UnsafePointer(sourceBuffer),
69+
src_size: 0,
70+
state: nil
71+
)
72+
guard compression_stream_init(&stream, operation, COMPRESSION_LZFSE)
73+
== COMPRESSION_STATUS_OK else {
74+
throw LZFSEError.streamInit
75+
}
76+
defer { compression_stream_destroy(&stream) }
77+
78+
// compression_stream_init resets the caller-owned source/destination fields.
79+
stream.src_ptr = UnsafePointer(sourceBuffer)
80+
stream.src_size = 0
81+
stream.dst_ptr = destinationBuffer
82+
stream.dst_size = chunk
83+
84+
var inputExhausted = false
85+
while true {
86+
if stream.src_size == 0, !inputExhausted {
87+
let read = input.read(sourceBuffer, maxLength: chunk)
88+
if read < 0 { throw LZFSEError.read }
89+
if read == 0 { inputExhausted = true }
90+
stream.src_ptr = UnsafePointer(sourceBuffer)
91+
stream.src_size = read
92+
}
93+
94+
let flags = inputExhausted ? Int32(COMPRESSION_STREAM_FINALIZE.rawValue) : 0
95+
let status = compression_stream_process(&stream, flags)
96+
guard status == COMPRESSION_STATUS_OK || status == COMPRESSION_STATUS_END else {
97+
throw LZFSEError.process
98+
}
99+
100+
// Compression.framework owns the partially filled destination buffer while it returns
101+
// OK. Publish and reset that buffer only once it is full, or publish the final partial
102+
// buffer when the stream ends. Resetting a partially filled buffer between OK calls
103+
// produces a malformed stream once a payload crosses the input chunk boundary.
104+
let produced: Int
105+
switch status {
106+
case COMPRESSION_STATUS_OK where stream.dst_size == 0:
107+
produced = chunk
108+
case COMPRESSION_STATUS_END:
109+
produced = chunk - stream.dst_size
110+
default:
111+
produced = 0
112+
}
113+
var offset = 0
114+
while offset < produced {
115+
let written = output.write(
116+
destinationBuffer + offset,
117+
maxLength: produced - offset
118+
)
119+
if written <= 0 { throw LZFSEError.write }
120+
offset += written
121+
}
122+
if status == COMPRESSION_STATUS_OK, stream.dst_size == 0 {
123+
stream.dst_ptr = destinationBuffer
124+
stream.dst_size = chunk
125+
}
126+
127+
if status == COMPRESSION_STATUS_END { return }
128+
}
129+
}
130+
}

Packages/ContainerizationEngine/Sources/dory-hv/main.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,28 @@ do {
223223
fail("application launch authority handoff failed: \(error)")
224224
}
225225
guard let command = arguments.first else {
226-
fail("usage: dory-hv <smoke|madvtest|desktop|agent-ping|data-drive|engine|usb|renderer-qualify> [options]")
226+
fail("usage: dory-hv <desktop|agent-ping|data-drive|engine|usb|renderer-qualify|lzfse> [options]")
227227
}
228228

229229
switch command {
230+
case "lzfse":
231+
let operation = arguments.dropFirst().first
232+
let paths = Array(arguments.dropFirst(2))
233+
guard let operation, paths.count == 2 else {
234+
fail("usage: dory-hv lzfse <compress|decompress> <input> <output>")
235+
}
236+
do {
237+
switch operation {
238+
case "compress":
239+
try LZFSE.compress(source: paths[0], destination: paths[1])
240+
case "decompress":
241+
try LZFSE.decompress(source: paths[0], destination: paths[1])
242+
default:
243+
fail("usage: dory-hv lzfse <compress|decompress> <input> <output>")
244+
}
245+
} catch {
246+
fail("lzfse \(operation) failed: \(error)")
247+
}
230248
case "data-drive":
231249
guard arguments.count >= 2 else {
232250
fail("usage: dory-hv data-drive <resolve|prepare|id|selected-path|select|bind-existing|recover-existing|capacity|grow|backup|verify-backup|restore> [paths]")
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Foundation
2+
import XCTest
3+
@testable import DoryHV
4+
5+
final class LZFSETests: XCTestCase {
6+
func testRoundTripAcrossMultipleStreamChunks() throws {
7+
let directory = FileManager.default.temporaryDirectory
8+
.appendingPathComponent(UUID().uuidString, isDirectory: true)
9+
try FileManager.default.createDirectory(
10+
at: directory,
11+
withIntermediateDirectories: true
12+
)
13+
defer { try? FileManager.default.removeItem(at: directory) }
14+
15+
let input = directory.appendingPathComponent("input.bin")
16+
let compressed = directory.appendingPathComponent("input.bin.lzfse")
17+
let restored = directory.appendingPathComponent("restored.bin")
18+
let bytes = Data((0..<(3 * 1_048_576 + 257)).map { UInt8(truncatingIfNeeded: $0) })
19+
try bytes.write(to: input)
20+
21+
try LZFSE.compress(source: input.path, destination: compressed.path)
22+
try LZFSE.decompress(source: compressed.path, destination: restored.path)
23+
24+
XCTAssertEqual(try Data(contentsOf: restored), bytes)
25+
}
26+
27+
func testMissingInputReportsItsPath() {
28+
let missing = "/tmp/dory-lzfse-missing-\(UUID().uuidString)"
29+
XCTAssertThrowsError(
30+
try LZFSE.compress(source: missing, destination: "\(missing).lzfse")
31+
) { error in
32+
guard case .openInput(let path) = error as? LZFSEError else {
33+
return XCTFail("unexpected error: \(error)")
34+
}
35+
XCTAssertEqual(path, missing)
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)