From fcd11512f7bdee26a0f1f937b6d768baedb6a90a Mon Sep 17 00:00:00 2001 From: John Logan Date: Mon, 23 Feb 2026 19:30:03 -0800 Subject: [PATCH] Remove libzstd imports from ContainerizationArchive. - Closes #532. - Relocate all zstd calls down into CArchive, and export a function zstd_decompress_fd(src_fd, dst_fd) that ContainerizationArchive can use. This eliminates problems that can occur when treating Swift warnings as errors in Xcode projects that use ContainerizationArchive. --- .../ArchiveReader.swift | 71 +++---------------- .../CArchive/archive_swift_bridge.c | 47 ++++++++++++ .../CArchive/include/archive_bridge.h | 5 ++ 3 files changed, 60 insertions(+), 63 deletions(-) diff --git a/Sources/ContainerizationArchive/ArchiveReader.swift b/Sources/ContainerizationArchive/ArchiveReader.swift index f5351ebd..b353b209 100644 --- a/Sources/ContainerizationArchive/ArchiveReader.swift +++ b/Sources/ContainerizationArchive/ArchiveReader.swift @@ -19,7 +19,6 @@ import ContainerizationError import ContainerizationOS import Foundation import SystemPackage -import libzstd /// A protocol for reading data in chunks, compatible with both `InputStream` and zero-allocation archive readers. public protocol ReadableStream { @@ -127,79 +126,25 @@ public final class ArchiveReader { /// Decompress a zstd file to a temporary location private static func decompressZstd(_ source: URL) throws -> URL { - let inputStream: InputStream? - if source.scheme == nil || source.scheme == "" { - // can't use InputStream(url:) with nil scheme - inputStream = .init(fileAtPath: source.path) - } else { - inputStream = InputStream(url: source) - } - - guard let inputStream else { - throw ArchiveError.noUnderlyingArchive - } - inputStream.open() - defer { inputStream.close() } - - // Create temp file into which the source zstd archived is decompressed guard let tempDir = createTemporaryDirectory(baseName: "zstd-decompress") else { throw ArchiveError.failedToDetectFormat } - let tempFile = tempDir.appendingPathComponent( source.deletingPathExtension().lastPathComponent ) - guard let outputStream = OutputStream(url: tempFile, append: false) else { - throw ArchiveError.noUnderlyingArchive - } - outputStream.open() - defer { outputStream.close() } + let srcPath = source.scheme == nil || source.scheme == "" ? source.path : source.path + let srcFd = open(srcPath, O_RDONLY) + guard srcFd >= 0 else { throw ArchiveError.failedToDetectFormat } + defer { close(srcFd) } - // Use streaming decompression since content size may be unknown - guard let dstream = ZSTD_createDStream() else { - throw ArchiveError.failedToDetectFormat - } - defer { ZSTD_freeDStream(dstream) } + let dstFd = open(tempFile.path, O_WRONLY | O_CREAT | O_TRUNC, 0o644) + guard dstFd >= 0 else { throw ArchiveError.failedToDetectFormat } + defer { close(dstFd) } - let initResult = ZSTD_initDStream(dstream) - guard ZSTD_isError(initResult) == 0 else { + guard zstd_decompress_fd(srcFd, dstFd) == 0 else { throw ArchiveError.failedToDetectFormat } - - let inputBufferSize = ZSTD_DStreamInSize() - let outputBufferSize = ZSTD_DStreamOutSize() - - var inputBuffer = [UInt8](repeating: 0, count: inputBufferSize) - - while case let amount = inputStream.read(&inputBuffer, maxLength: inputBufferSize), amount > 0 { - try inputBuffer.withUnsafeBufferPointer { ptr in - var input = ZSTD_inBuffer( - src: ptr.baseAddress, - size: amount, - pos: 0 - ) - while input.pos < input.size { - var outputBuffer = [UInt8](repeating: 0, count: outputBufferSize) - var decompressedBytes = 0 - try outputBuffer.withUnsafeMutableBytes { outputBytes in - var output = ZSTD_outBuffer( - dst: outputBytes.baseAddress, - size: outputBufferSize, - pos: 0 - ) - let result = ZSTD_decompressStream(dstream, &output, &input) - guard ZSTD_isError(result) == 0 else { - throw ArchiveError.failedToDetectFormat - } - decompressedBytes = output.pos - } - if decompressedBytes > 0 { - outputStream.write(outputBuffer, maxLength: decompressedBytes) - } - } - } - } return tempFile } diff --git a/Sources/ContainerizationArchive/CArchive/archive_swift_bridge.c b/Sources/ContainerizationArchive/CArchive/archive_swift_bridge.c index 9cec4d88..a16d32b0 100644 --- a/Sources/ContainerizationArchive/CArchive/archive_swift_bridge.c +++ b/Sources/ContainerizationArchive/CArchive/archive_swift_bridge.c @@ -15,7 +15,54 @@ */ #include "archive_bridge.h" +#include +#include +#include void archive_set_error_wrapper(struct archive *a, int error_number, const char *error_string) { archive_set_error(a, error_number, "%s", error_string); } + +int zstd_decompress_fd(int src_fd, int dst_fd) { + ZSTD_DStream *dstream = ZSTD_createDStream(); + if (!dstream) return 1; + + size_t init_result = ZSTD_initDStream(dstream); + if (ZSTD_isError(init_result)) { + ZSTD_freeDStream(dstream); + return 1; + } + + size_t in_size = ZSTD_DStreamInSize(); + size_t out_size = ZSTD_DStreamOutSize(); + void *in_buf = malloc(in_size); + void *out_buf = malloc(out_size); + if (!in_buf || !out_buf) { + free(in_buf); + free(out_buf); + ZSTD_freeDStream(dstream); + return 1; + } + + int rc = 0; + ssize_t bytes_read; + while ((bytes_read = read(src_fd, in_buf, in_size)) > 0) { + ZSTD_inBuffer input = { in_buf, (size_t)bytes_read, 0 }; + while (input.pos < input.size) { + ZSTD_outBuffer output = { out_buf, out_size, 0 }; + size_t result = ZSTD_decompressStream(dstream, &output, &input); + if (ZSTD_isError(result)) { rc = 1; goto done; } + if (output.pos > 0) { + ssize_t written = write(dst_fd, out_buf, output.pos); + if (written != (ssize_t)output.pos) { rc = 1; goto done; } + } + } + } + if (bytes_read < 0) rc = 1; + +done: + free(in_buf); + free(out_buf); + ZSTD_freeDStream(dstream); + return rc; +} diff --git a/Sources/ContainerizationArchive/CArchive/include/archive_bridge.h b/Sources/ContainerizationArchive/CArchive/include/archive_bridge.h index 6b30015f..91c3ac26 100644 --- a/Sources/ContainerizationArchive/CArchive/include/archive_bridge.h +++ b/Sources/ContainerizationArchive/CArchive/include/archive_bridge.h @@ -3,5 +3,10 @@ #pragma once #include "archive.h" +#include void archive_set_error_wrapper(struct archive *a, int error_number, const char *error_string); + +/// Decompress a zstd-compressed file at \p src_fd into \p dst_fd. +/// Returns 0 on success, or a non-zero error code on failure. +int zstd_decompress_fd(int src_fd, int dst_fd);