diff --git a/Sources/ContainerizationOCI/Client/LocalOCILayoutClient.swift b/Sources/ContainerizationOCI/Client/LocalOCILayoutClient.swift index 7a433ca4..ad9a1c68 100644 --- a/Sources/ContainerizationOCI/Client/LocalOCILayoutClient.swift +++ b/Sources/ContainerizationOCI/Client/LocalOCILayoutClient.swift @@ -35,6 +35,26 @@ package final class LocalOCILayoutClient: ContentClient { return c } + private func calculateFileDigest(at url: URL) throws -> SHA256Digest { + let fileHandle = try FileHandle(forReadingFrom: url) + defer { + try? fileHandle.close() + } + + var hasher = SHA256() + let chunkSize = Int(getpagesize()) * 1024 + + while true { + let chunk = fileHandle.readData(ofLength: chunkSize) + if chunk.isEmpty { + break + } + hasher.update(data: chunk) + } + + return hasher.finalize() + } + package func fetch(name: String, descriptor: Descriptor) async throws -> T { let c = try await self._fetch(digest: descriptor.digest) return try c.decode() @@ -44,7 +64,8 @@ package final class LocalOCILayoutClient: ContentClient { let c = try await self._fetch(digest: descriptor.digest) let fileManager = FileManager.default let filePath = file.absolutePath() - if !fileManager.fileExists(atPath: filePath) { + + do { let src = c.path try fileManager.copyItem(at: src, to: file) @@ -53,7 +74,33 @@ package final class LocalOCILayoutClient: ContentClient { ProgressEvent(event: "add-size", value: fileSize) ]) } + } catch let error as NSError { + guard error.code == NSFileWriteFileExistsError else { + throw error + } + + do { + let expectedDigest = try c.digest() + let existingDigest = try calculateFileDigest(at: file) + + guard existingDigest.digestString == expectedDigest.digestString else { + throw ContainerizationError( + .internalError, + message: + "File \(filePath) exists but contains different content. Expected digest: \(expectedDigest.digestString), existing digest: \(existingDigest.digestString)" + ) + } + + if let progress, let fileSize = fileManager.fileSize(atPath: filePath) { + await progress([ + ProgressEvent(event: "add-size", value: fileSize) + ]) + } + } catch { + throw error + } } + let size = try Int64(c.size()) let digest = try c.digest() return (size, digest)