mirror of
https://github.com/apple/container.git
synced 2026-09-26 01:25:52 +00:00
Add docs for ContainerizationArchive (#28)
Also removes some dead code. Signed-off-by: Aditya Ramani <a_ramani@apple.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
import CArchive
|
||||
import Foundation
|
||||
|
||||
/// An enumeration of the errors that can be thrown while interacting with an archive.
|
||||
public enum ArchiveError: Error, CustomStringConvertible {
|
||||
case unableToCreateArchive
|
||||
case noUnderlyingArchive
|
||||
@@ -37,6 +38,7 @@ public enum ArchiveError: Error, CustomStringConvertible {
|
||||
case failedToDetectFormat
|
||||
case failedToExtractArchive(String)
|
||||
|
||||
/// Description of the error
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .unableToCreateArchive:
|
||||
|
||||
@@ -18,10 +18,13 @@
|
||||
import CArchive
|
||||
import Foundation
|
||||
|
||||
/// A class responsible for writing archives in various formats.
|
||||
public final class ArchiveWriter {
|
||||
var underlying: OpaquePointer!
|
||||
var delegate: ArchiveWriterDelegate?
|
||||
var delegate: FileArchiveWriterDelegate?
|
||||
|
||||
/// Initialize a new `ArchiveWriter` with the given configuration.
|
||||
/// This method attempts to initialize an empty archive in memory, failing which it throws a `unableToCreateArchive` error.
|
||||
public init(configuration: ArchiveWriterConfiguration) throws {
|
||||
// because for some bizarre reason, UTF8 paths won't work unless this process explicitly sets a locale like en_US.UTF-8
|
||||
try Self.attemptSetLocales(locales: configuration.locales)
|
||||
@@ -34,24 +37,38 @@ public final class ArchiveWriter {
|
||||
try setOptions(configuration.options)
|
||||
}
|
||||
|
||||
public convenience init(configuration: ArchiveWriterConfiguration, delegate: ArchiveWriterDelegate) throws {
|
||||
/// Initialize a new `ArchiveWriter` with the given configuration and specifed delegate.
|
||||
private convenience init(configuration: ArchiveWriterConfiguration, delegate: FileArchiveWriterDelegate) throws {
|
||||
try self.init(configuration: configuration)
|
||||
self.delegate = delegate
|
||||
try self.open()
|
||||
}
|
||||
|
||||
private convenience init(configuration: ArchiveWriterConfiguration, file: URL) throws {
|
||||
try self.init(configuration: configuration, delegate: FileArchiveWriterDelegate(url: file))
|
||||
}
|
||||
|
||||
/// Initialize a new `ArchiveWriter` for writing into the specified file with the given configuration options.
|
||||
public convenience init(format: Format, filter: Filter, options: [Options] = [], file: URL) throws {
|
||||
try self.init(
|
||||
configuration: .init(format: format, filter: filter), delegate: FileArchiveWriterDelegate(url: file))
|
||||
}
|
||||
|
||||
/// Opens the given file for writing data into
|
||||
public func open(file: URL) throws {
|
||||
guard let underlying = underlying else { throw ArchiveError.noUnderlyingArchive }
|
||||
let res = archive_write_open_filename(underlying, file.path)
|
||||
try wrap(res, ArchiveError.unableToOpenArchive, underlying: underlying)
|
||||
}
|
||||
|
||||
/// Opens the given fd for writing data into
|
||||
public func open(fileDescriptor: Int32) throws {
|
||||
guard let underlying = underlying else { throw ArchiveError.noUnderlyingArchive }
|
||||
let res = archive_write_open_fd(underlying, fileDescriptor)
|
||||
try wrap(res, ArchiveError.unableToOpenArchive, underlying: underlying)
|
||||
}
|
||||
|
||||
/// Performs any necessary finalizations on the archive and releases resources.
|
||||
public func finishEncoding() throws {
|
||||
if let u = underlying {
|
||||
let r = archive_free(u)
|
||||
@@ -72,7 +89,7 @@ public final class ArchiveWriter {
|
||||
}
|
||||
}
|
||||
|
||||
public static func attemptSetLocales(locales: [String]) throws {
|
||||
private static func attemptSetLocales(locales: [String]) throws {
|
||||
for locale in locales {
|
||||
if setlocale(LC_ALL, locale) != nil {
|
||||
return
|
||||
@@ -195,12 +212,29 @@ extension ArchiveWriter {
|
||||
ArchiveWriterTransaction(writer: self)
|
||||
}
|
||||
|
||||
/// Create a new entry in the archive with the given properties.
|
||||
/// - Parameters:
|
||||
/// - entry: A `WriteEntry` object describing the metadata of the entry to be created
|
||||
/// (e.g., name, modification date, permissions).
|
||||
/// - data: The `Data` object containing the content for the new entry.
|
||||
public func writeEntry(entry: WriteEntry, data: Data) throws {
|
||||
try data.withUnsafeBytes { bytes in
|
||||
try writeEntry(entry: entry, data: bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new entry in the archive with the given properties.
|
||||
///
|
||||
/// This method performs the following:
|
||||
/// 1. Writes the archive header using the provided `WriteEntry` metadata.
|
||||
/// 2. Writes the content from the `UnsafeRawBufferPointer` into the archive.
|
||||
/// 3. Finalizes the entry in the archive.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - entry: A `WriteEntry` object describing the metadata of the entry to be created
|
||||
/// (e.g., name, modification date, permissions, type).
|
||||
/// - data: An optional `UnsafeRawBufferPointer` containing the raw bytes for the new entry's
|
||||
/// content. Pass `nil` for entries that do not have content data (e.g., directories, symlinks).
|
||||
public func writeEntry(entry: WriteEntry, data: UnsafeRawBufferPointer?) throws {
|
||||
try writeHeader(entry: entry)
|
||||
if let data = data {
|
||||
@@ -234,7 +268,7 @@ extension ArchiveWriter {
|
||||
}
|
||||
|
||||
extension ArchiveWriter {
|
||||
/// Recursively archives the content of a directory. Regular files, symlinks and directories are added to the archive.
|
||||
/// Recursively archives the content of a directory. Regular files, symlinks and directories are added into the archive.
|
||||
/// Note: Symlinks are added to the archive if both the source and target for the symlink are both contained in the top level directory.
|
||||
public func archiveDirectory(_ dir: URL) throws {
|
||||
let fm = FileManager.default
|
||||
|
||||
@@ -17,12 +17,24 @@
|
||||
|
||||
import CArchive
|
||||
|
||||
/// Represents the configuration settings for an `ArchiveWriter`.
|
||||
///
|
||||
/// This struct allows specifying the archive format, compression filter,
|
||||
/// various format-specific options, and preferred locales for string encoding.
|
||||
public struct ArchiveWriterConfiguration {
|
||||
/// The desired archive format
|
||||
public var format: Format
|
||||
/// The compression filter to apply to the archive
|
||||
public var filter: Filter
|
||||
/// An array of format-specific options to apply to the archive.
|
||||
/// This includes options like compression level and extended attribute format.
|
||||
public var options: [Options]
|
||||
/// An array of preferred locale identifiers for string encoding
|
||||
public var locales: [String]
|
||||
|
||||
/// Initializes a new `ArchiveWriterConfiguration`.
|
||||
///
|
||||
/// Sets up the configuration with the specified format, filter, options, and locales.
|
||||
public init(
|
||||
format: Format, filter: Filter, options: [Options] = [], locales: [String] = ["en_US.UTF-8", "C.UTF-8"]
|
||||
) {
|
||||
@@ -34,19 +46,19 @@ public struct ArchiveWriterConfiguration {
|
||||
}
|
||||
|
||||
extension ArchiveWriter {
|
||||
func setFormat(_ format: Format) throws {
|
||||
internal func setFormat(_ format: Format) throws {
|
||||
guard let underlying = self.underlying else { throw ArchiveError.noUnderlyingArchive }
|
||||
let r = archive_write_set_format(underlying, format.code)
|
||||
guard r == ARCHIVE_OK else { throw ArchiveError.unableToSetFormat(r, format) }
|
||||
}
|
||||
|
||||
func addFilter(_ filter: Filter) throws {
|
||||
internal func addFilter(_ filter: Filter) throws {
|
||||
guard let underlying = self.underlying else { throw ArchiveError.noUnderlyingArchive }
|
||||
let r = archive_write_add_filter(underlying, filter.code)
|
||||
guard r == ARCHIVE_OK else { throw ArchiveError.unableToAddFilter(r, filter) }
|
||||
}
|
||||
|
||||
func setOptions(_ options: [Options]) throws {
|
||||
internal func setOptions(_ options: [Options]) throws {
|
||||
try options.forEach {
|
||||
switch $0 {
|
||||
case .compressionLevel(let level):
|
||||
@@ -99,6 +111,7 @@ public enum Options {
|
||||
}
|
||||
}
|
||||
|
||||
/// An enumeration of the supported archive formats.
|
||||
public enum Format: String, Sendable {
|
||||
/// POSIX-standard `ustar` archives
|
||||
case ustar
|
||||
@@ -126,7 +139,7 @@ public enum Format: String, Sendable {
|
||||
/// XAR archives
|
||||
case xar
|
||||
|
||||
var code: CInt {
|
||||
internal var code: CInt {
|
||||
switch self {
|
||||
case .ustar: return ARCHIVE_FORMAT_TAR_USTAR
|
||||
case .pax: return ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE
|
||||
@@ -147,7 +160,7 @@ public enum Format: String, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
/// A filter (compression / encoding) to use when writing.
|
||||
/// An enumreration of the supported filters (compression / encoding standards) for an archive.
|
||||
public enum Filter: String, Sendable {
|
||||
case none
|
||||
case gzip
|
||||
@@ -163,7 +176,7 @@ public enum Filter: String, Sendable {
|
||||
case grzip
|
||||
case lz4
|
||||
|
||||
var code: CInt {
|
||||
internal var code: CInt {
|
||||
switch self {
|
||||
case .none: return ARCHIVE_FILTER_NONE
|
||||
case .gzip: return ARCHIVE_FILTER_GZIP
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025 Apple Inc. and the Containerization project authors.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import CArchive
|
||||
|
||||
public protocol ArchiveWriterDelegate: AnyObject {
|
||||
/// The open callback is invoked by archive_write_open(). It should return ARCHIVE_OK if the underlying file or data source is successfully opened. If the open fails, it should call archive_set_error() to register an error code and message and return ARCHIVE_FATAL. Please note that
|
||||
/// if open fails, close is not called and resources must be freed inside the open callback or with the free callback.
|
||||
func open(archive: ArchiveWriter) throws
|
||||
/// returns number of bytes written
|
||||
func write(archive: ArchiveWriter, buffer: UnsafeRawBufferPointer) throws -> Int
|
||||
/// The close callback is invoked by archive_close when the archive processing is complete. If the open callback fails, the close callback is not invoked. The callback should return ARCHIVE_OK on success. On failure, the callback should invoke archive_set_error() to register an
|
||||
/// error code and message and return
|
||||
func close(archive: ArchiveWriter) throws
|
||||
/// The free callback is always invoked on archive_free. The return code of this callback is not processed.
|
||||
func free(archive: ArchiveWriter)
|
||||
}
|
||||
@@ -18,8 +18,7 @@
|
||||
import Foundation
|
||||
import SystemPackage
|
||||
|
||||
public final class FileArchiveWriterDelegate: ArchiveWriterDelegate {
|
||||
|
||||
internal final class FileArchiveWriterDelegate {
|
||||
public let path: FilePath
|
||||
private var fd: FileDescriptor!
|
||||
|
||||
@@ -54,13 +53,3 @@ public final class FileArchiveWriterDelegate: ArchiveWriterDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ArchiveWriter {
|
||||
public convenience init(configuration: ArchiveWriterConfiguration, file: URL) throws {
|
||||
try self.init(configuration: configuration, delegate: FileArchiveWriterDelegate(url: file))
|
||||
}
|
||||
public convenience init(format: Format, filter: Filter, options: [Options] = [], file: URL) throws {
|
||||
try self.init(
|
||||
configuration: .init(format: format, filter: filter), delegate: FileArchiveWriterDelegate(url: file))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,24 @@
|
||||
import CArchive
|
||||
import Foundation
|
||||
|
||||
/// A class responsible for reading entries from an archive file.
|
||||
public final class ArchiveReader {
|
||||
/// A pointer to the underlying `archive` C structure.
|
||||
var underlying: OpaquePointer?
|
||||
/// The file handle associated with the archive file being read.
|
||||
let fileHandle: FileHandle?
|
||||
|
||||
/// Initializes an `ArchiveReader` to read from a specified file URL with an explicit `Format` and `Filter`.
|
||||
/// Note: This method must be used when it is known that the archive at the specified URL follows the specifed
|
||||
/// `Format` and `Filter`.
|
||||
public convenience init(format: Format, filter: Filter, file: URL) throws {
|
||||
let fileHandle = try FileHandle(forReadingFrom: file)
|
||||
try self.init(format: format, filter: filter, fileHandle: fileHandle)
|
||||
}
|
||||
|
||||
/// Initializes an `ArchiveReader` to read from the provided file descriptor with an explicit `Format` and `Filter`.
|
||||
/// Note: This method must be used when it is known that the archive pointed to by the file descriptor follows the specifed
|
||||
/// `Format` and `Filter`.
|
||||
public init(format: Format, filter: Filter, fileHandle: FileHandle) throws {
|
||||
self.underlying = archive_read_new()
|
||||
self.fileHandle = fileHandle
|
||||
@@ -41,7 +50,8 @@ public final class ArchiveReader {
|
||||
.checkOk(elseThrow: { .unableToOpenArchive($0) })
|
||||
}
|
||||
|
||||
// Initialize the archive reader by trying to auto detect the archive and compression format
|
||||
/// Initialize the `ArchiveReader` to read from a specified file URL
|
||||
/// by trying to auto determine the archives `Format` and `Filter`.
|
||||
public init(file: URL) throws {
|
||||
self.underlying = archive_read_new()
|
||||
let fileHandle = try FileHandle(forReadingFrom: file)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import ContainerizationExtras
|
||||
import Foundation
|
||||
|
||||
func createTemporaryDirectory(baseName: String) -> URL? {
|
||||
internal func createTemporaryDirectory(baseName: String) -> URL? {
|
||||
let url = FileManager.default.uniqueTemporaryDirectory().appendingPathComponent(
|
||||
"\(baseName).XXXXXX")
|
||||
guard let templatePathData = (url.absoluteURL.path as NSString).utf8String else {
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
import CArchive
|
||||
import Foundation
|
||||
|
||||
/// Represents a single entry (e.g., a file, directory, symbolic link)
|
||||
/// that is to be read/written into an archive.
|
||||
public final class WriteEntry {
|
||||
let underlying: OpaquePointer
|
||||
|
||||
@@ -35,6 +37,7 @@ public final class WriteEntry {
|
||||
}
|
||||
|
||||
extension WriteEntry {
|
||||
/// The size of the entry in bytes.
|
||||
public var size: Int64? {
|
||||
get {
|
||||
guard archive_entry_size_is_set(underlying) != 0 else { return nil }
|
||||
@@ -49,6 +52,7 @@ extension WriteEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The mode of the entry.
|
||||
public var permissions: mode_t {
|
||||
get {
|
||||
archive_entry_perm(underlying)
|
||||
@@ -58,6 +62,7 @@ extension WriteEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The owner id of the entry.
|
||||
public var owner: uid_t? {
|
||||
get {
|
||||
uid_t(exactly: archive_entry_uid(underlying))
|
||||
@@ -67,6 +72,7 @@ extension WriteEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The group id of the entry
|
||||
public var group: gid_t? {
|
||||
get {
|
||||
gid_t(exactly: archive_entry_gid(underlying))
|
||||
@@ -76,6 +82,7 @@ extension WriteEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The path of file this entry hardlinks to
|
||||
public var hardlink: String? {
|
||||
get {
|
||||
guard let cstr = archive_entry_hardlink(underlying) else {
|
||||
@@ -94,6 +101,7 @@ extension WriteEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The UTF-8 encoded path of file this entry hardlinks to
|
||||
public var hardlinkUtf8: String? {
|
||||
get {
|
||||
guard let cstr = archive_entry_hardlink_utf8(underlying) else {
|
||||
@@ -112,6 +120,7 @@ extension WriteEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The string representation of the permissions of the entry
|
||||
public var strmode: String? {
|
||||
if let cstr = archive_entry_strmode(underlying) {
|
||||
return String(cString: cstr)
|
||||
@@ -119,6 +128,7 @@ extension WriteEntry {
|
||||
return nil
|
||||
}
|
||||
|
||||
/// The type of file this entry represents.
|
||||
public var fileType: URLFileResourceType {
|
||||
get {
|
||||
switch archive_entry_filetype(underlying) {
|
||||
@@ -146,6 +156,7 @@ extension WriteEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The date that the entry was last accessed
|
||||
public var contentAccessDate: Date? {
|
||||
get {
|
||||
Date(
|
||||
@@ -162,6 +173,7 @@ extension WriteEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The date that the entry was created
|
||||
public var creationDate: Date? {
|
||||
get {
|
||||
Date(
|
||||
@@ -178,6 +190,7 @@ extension WriteEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The date that the entry was modified
|
||||
public var modificationDate: Date? {
|
||||
get {
|
||||
Date(
|
||||
@@ -194,6 +207,7 @@ extension WriteEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The file path of the entry
|
||||
public var path: String? {
|
||||
get {
|
||||
guard let pathname = archive_entry_pathname(underlying) else {
|
||||
@@ -212,6 +226,7 @@ extension WriteEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The UTF-8 encoded file path of the entry
|
||||
public var pathUtf8: String? {
|
||||
get {
|
||||
guard let pathname = archive_entry_pathname_utf8(underlying) else {
|
||||
@@ -230,6 +245,7 @@ extension WriteEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The symlink target that the entry points to
|
||||
public var symlinkTarget: String? {
|
||||
get {
|
||||
guard let target = archive_entry_symlink(underlying) else {
|
||||
@@ -248,6 +264,7 @@ extension WriteEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// The extended attributes of the entry
|
||||
public var xattrs: [String: Data] {
|
||||
get {
|
||||
archive_entry_xattr_reset(self.underlying)
|
||||
|
||||
Reference in New Issue
Block a user