//===----------------------------------------------------------------------===// // Copyright © 2025-2026 Apple Inc. and the Containerization project authors. // // 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 /// 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 { public static let defaultLocales = ["en_US.UTF-8", "C.UTF-8"] /// 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] = Self.defaultLocales ) { self.format = format self.filter = filter self.options = options self.locales = locales } } extension ArchiveWriter { 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) } } 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) } } internal func setOptions(_ options: [Options]) throws { guard let underlying = self.underlying else { throw ArchiveError.noUnderlyingArchive } try options.forEach { switch $0 { case .compressionLevel(let level): try wrap( archive_write_set_option(underlying, nil, "compression-level", "\(level)"), ArchiveError.unableToSetOption, underlying: self.underlying) case .compression(.store): try wrap( archive_write_set_option(underlying, nil, "compression", "store"), ArchiveError.unableToSetOption, underlying: self.underlying) case .compression(.deflate): try wrap( archive_write_set_option(underlying, nil, "compression", "deflate"), ArchiveError.unableToSetOption, underlying: self.underlying) case .xattrformat(let value): let v = value.description try wrap( archive_write_set_option(underlying, nil, "xattrheader", v), ArchiveError.unableToSetOption, underlying: self.underlying) } } } } public enum Options { case compressionLevel(UInt32) case compression(Compression) case xattrformat(XattrFormat) public enum Compression { case store case deflate } public enum XattrFormat: String, CustomStringConvertible { case schily case libarchive case all public var description: String { switch self { case .libarchive: return "LIBARCHIVE" case .schily: return "SCHILY" case .all: return "ALL" } } } } /// An enumeration of the supported archive formats. public enum Format: String, Sendable { /// POSIX-standard `ustar` archives case ustar case gnutar /// POSIX `pax interchange format` archives case pax case paxRestricted /// POSIX octet-oriented cpio archives case cpio case cpioNewc /// Zip archive case zip /// two different variants of shar archives case shar case sharDump /// ISO9660 CD images case iso9660 /// 7-Zip archives case sevenZip /// ar archives case arBSD case arGNU /// mtree file tree descriptions case mtree /// XAR archives case xar internal var code: CInt { switch self { case .ustar: return ARCHIVE_FORMAT_TAR_USTAR case .pax: return ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE case .paxRestricted: return ARCHIVE_FORMAT_TAR_PAX_RESTRICTED case .gnutar: return ARCHIVE_FORMAT_TAR_GNUTAR case .cpio: return ARCHIVE_FORMAT_CPIO_POSIX case .cpioNewc: return ARCHIVE_FORMAT_CPIO_AFIO_LARGE case .zip: return ARCHIVE_FORMAT_ZIP case .shar: return ARCHIVE_FORMAT_SHAR_BASE case .sharDump: return ARCHIVE_FORMAT_SHAR_DUMP case .iso9660: return ARCHIVE_FORMAT_ISO9660 case .sevenZip: return ARCHIVE_FORMAT_7ZIP case .arBSD: return ARCHIVE_FORMAT_AR_BSD case .arGNU: return ARCHIVE_FORMAT_AR_GNU case .mtree: return ARCHIVE_FORMAT_MTREE case .xar: return ARCHIVE_FORMAT_XAR } } } /// An enumeration of the supported filters (compression / encoding standards) for an archive. public enum Filter: String, Sendable { case none case gzip case bzip2 case compress case lzma case xz case uu case rpm case lzip case lrzip case lzop case grzip case lz4 case zstd internal var code: CInt { switch self { case .none: return ARCHIVE_FILTER_NONE case .gzip: return ARCHIVE_FILTER_GZIP case .bzip2: return ARCHIVE_FILTER_BZIP2 case .compress: return ARCHIVE_FILTER_COMPRESS case .lzma: return ARCHIVE_FILTER_LZMA case .xz: return ARCHIVE_FILTER_XZ case .uu: return ARCHIVE_FILTER_UU case .rpm: return ARCHIVE_FILTER_RPM case .lzip: return ARCHIVE_FILTER_LZIP case .lrzip: return ARCHIVE_FILTER_LRZIP case .lzop: return ARCHIVE_FILTER_LZOP case .grzip: return ARCHIVE_FILTER_GRZIP case .lz4: return ARCHIVE_FILTER_LZ4 case .zstd: return ARCHIVE_FILTER_ZSTD } } }