Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 8 additions & 63 deletions Sources/ContainerizationArchive/ArchiveReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down
47 changes: 47 additions & 0 deletions Sources/ContainerizationArchive/CArchive/archive_swift_bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,54 @@
*/

#include "archive_bridge.h"
#include <zstd.h>
#include <stdlib.h>
#include <unistd.h>

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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@
#pragma once

#include "archive.h"
#include <stdint.h>

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);