Skip to content

Commit 420b915

Browse files
authored
Write archive contents in chunks (#589)
Fix a bug in ArchiveWriter where large files would only partly be written into an archive. This is done so by writing the data in chunks. --------- Signed-off-by: Aditya Ramani <a_ramani@apple.com>
1 parent 84b71f1 commit 420b915

3 files changed

Lines changed: 44 additions & 9 deletions

File tree

Sources/ContainerizationArchive/ArchiveError.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public enum ArchiveError: Error, CustomStringConvertible {
3636
case failedToDetectFilter
3737
case failedToDetectFormat
3838
case failedToExtractArchive(String)
39+
case failedToCreateArchive(String)
40+
case invalidBaseAddressArchiveWrite
3941

4042
/// Description of the error
4143
public var description: String {
@@ -74,6 +76,10 @@ public enum ArchiveError: Error, CustomStringConvertible {
7476
return "failed to detect format from archive."
7577
case .failedToExtractArchive(let reason):
7678
return "failed to extract archive: \(reason)"
79+
case .failedToCreateArchive(let reason):
80+
return "failed to create archive: \(reason)"
81+
case .invalidBaseAddressArchiveWrite:
82+
return "got an invalid base address for pointer when writing data to archive"
7783
}
7884
}
7985
}

Sources/ContainerizationArchive/ArchiveReader.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public struct ArchiveEntryReader: ReadableStream {
4848

4949
/// A class responsible for reading entries from an archive file.
5050
public final class ArchiveReader {
51-
private static let blockSize = 65536
51+
private static let chunkSize = 4 * 1024 * 1024
5252

5353
/// A pointer to the underlying `archive` C structure.
5454
var underlying: OpaquePointer?
@@ -375,7 +375,7 @@ extension ArchiveReader {
375375
}
376376

377377
private static func copyDataReaderToFd(dataReader: ArchiveEntryReader, fileFd: Int32, memberPath: FilePath) throws {
378-
var buffer = [UInt8](repeating: 0, count: ArchiveReader.blockSize)
378+
var buffer = [UInt8](repeating: 0, count: ArchiveReader.chunkSize)
379379
while true {
380380
let bytesRead = buffer.withUnsafeMutableBufferPointer { bufferPtr in
381381
guard let baseAddress = bufferPtr.baseAddress else { return 0 }

Sources/ContainerizationArchive/ArchiveWriter.swift

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import SystemPackage
2020

2121
/// A class responsible for writing archives in various formats.
2222
public final class ArchiveWriter {
23+
private static let chunkSize = 4 * 1024 * 1024
24+
2325
var underlying: OpaquePointer!
2426

2527
/// Initialize a new `ArchiveWriter` with the given configuration.
@@ -166,9 +168,16 @@ extension ArchiveWriter {
166168
fileprivate func writeData(data: UnsafeRawBufferPointer) throws {
167169
guard let underlying = self.underlying else { throw ArchiveError.noUnderlyingArchive }
168170

169-
let result = archive_write_data(underlying, data.baseAddress, data.count)
170-
guard result >= 0 else {
171-
throw ArchiveError.unableToWriteData(result)
171+
var offset = 0
172+
while offset < data.count {
173+
guard let baseAddress = data.baseAddress?.advanced(by: offset) else {
174+
throw ArchiveError.invalidBaseAddressArchiveWrite
175+
}
176+
let result = archive_write_data(underlying, baseAddress, data.count - offset)
177+
guard result > 0 else {
178+
throw ArchiveError.unableToWriteData(result)
179+
}
180+
offset += Int(result)
172181
}
173182
}
174183
}
@@ -188,7 +197,7 @@ extension ArchiveWriter {
188197
var rootStat = stat()
189198
guard lstat(dirPath.string, &rootStat) == 0 else {
190199
let err = POSIXErrorCode(rawValue: errno) ?? .EINVAL
191-
throw ArchiveError.failedToExtractArchive("lstat failed for '\(dirPath)': \(POSIXError(err))")
200+
throw ArchiveError.failedToCreateArchive("lstat failed for '\(dirPath)': \(POSIXError(err))")
192201
}
193202
let rootEntry = WriteEntry()
194203
rootEntry.path = "./"
@@ -215,7 +224,7 @@ extension ArchiveWriter {
215224
guard lstat(fullPath.string, &statInfo) == 0 else {
216225
let errNo = errno
217226
let err = POSIXErrorCode(rawValue: errNo) ?? .EINVAL
218-
throw ArchiveError.failedToExtractArchive("lstat failed for '\(fullPath)': \(POSIXError(err))")
227+
throw ArchiveError.failedToCreateArchive("lstat failed for '\(fullPath)': \(POSIXError(err))")
219228
}
220229

221230
let mode = statInfo.st_mode
@@ -267,8 +276,28 @@ extension ArchiveWriter {
267276
entry.owner = uid
268277
entry.permissions = mode
269278
if type == .regular {
270-
let data = try Data(contentsOf: URL(fileURLWithPath: fullPath.string), options: .uncached)
271-
try self.writeEntry(entry: entry, data: data)
279+
let buf = UnsafeMutableRawBufferPointer.allocate(byteCount: Self.chunkSize, alignment: 1)
280+
guard let baseAddress = buf.baseAddress else {
281+
throw ArchiveError.failedToCreateArchive("cannot create temporary buffer of size \(Self.chunkSize)")
282+
}
283+
defer { buf.deallocate() }
284+
let fd = Foundation.open(fullPath.string, O_RDONLY)
285+
guard fd >= 0 else {
286+
let err = POSIXErrorCode(rawValue: errno) ?? .EINVAL
287+
throw ArchiveError.failedToCreateArchive("cannot open file \(fullPath.string) for reading: \(err)")
288+
}
289+
defer { close(fd) }
290+
try self.writeHeader(entry: entry)
291+
while true {
292+
let n = read(fd, baseAddress, Self.chunkSize)
293+
if n == 0 { break }
294+
if n < 0 {
295+
let err = POSIXErrorCode(rawValue: errno) ?? .EIO
296+
throw ArchiveError.failedToCreateArchive("failed to read from file \(fullPath.string): \(err)")
297+
}
298+
try self.writeData(data: UnsafeRawBufferPointer(start: baseAddress, count: n))
299+
}
300+
try self.finishEntry()
272301
} else {
273302
try self.writeEntry(entry: entry, data: nil)
274303
}

0 commit comments

Comments
 (0)