From 35ebe365ba10da6054bd3c002b80e30e2fcdb17a Mon Sep 17 00:00:00 2001 From: Raj Date: Fri, 21 Nov 2025 14:50:28 -0800 Subject: [PATCH] Add `calculateOrphanedBlobsSize()` to calculate the size of orphaned blobs (#428) Adds `calculateOrphanedBlobsSize()` to calculate the size of orphaned blobs, will need this to include them under size and reclaimable space for images in the `container system df` command so it matches up with what `container image prune` frees up on disk. --- .../Image/ImageStore/ImageStore.swift | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Sources/Containerization/Image/ImageStore/ImageStore.swift b/Sources/Containerization/Image/ImageStore/ImageStore.swift index a87b58f9..439ae868 100644 --- a/Sources/Containerization/Image/ImageStore/ImageStore.swift +++ b/Sources/Containerization/Image/ImageStore/ImageStore.swift @@ -141,6 +141,15 @@ extension ImageStore { } } + /// Calculate the size of orphaned blobs without deleting them. + /// + /// - Returns: The total size in bytes of blobs that are not referenced by any image. + public func calculateOrphanedBlobsSize() async throws -> UInt64 { + try await self.lock.withLock { lockCtx in + try await self._calculateOrphanedBlobsSize(lockCtx) + } + } + @discardableResult private func _cleanupOrphanedBlobs(_ lock: AsyncLock.Context) async throws -> (deleted: [String], freed: UInt64) { let images = try await self.list() @@ -152,6 +161,39 @@ extension ImageStore { return (deleted, size) } + private func _calculateOrphanedBlobsSize(_ lock: AsyncLock.Context) async throws -> UInt64 { + let images = try await self.list() + var referenced: [String] = [] + for image in images { + try await referenced.append(contentsOf: image.referencedDigests().uniqued()) + } + + // Calculate size of blobs not in the referenced list + let referencedSet = Set(referenced.map { $0.trimmingDigestPrefix }) + let blobsPath = self.path.appendingPathComponent("content/blobs/sha256") + + let fileManager = FileManager.default + let allBlobs = try fileManager.contentsOfDirectory( + at: blobsPath, + includingPropertiesForKeys: [.fileSizeKey], + options: [.skipsHiddenFiles] + ) + + var orphanedSize: UInt64 = 0 + for blobURL in allBlobs { + let digest = blobURL.lastPathComponent + if !referencedSet.contains(digest) { + if let resourceValues = try? blobURL.resourceValues(forKeys: [.fileSizeKey]), + let size = resourceValues.fileSize + { + orphanedSize += UInt64(size) + } + } + } + + return orphanedSize + } + /// Tag an existing image such that it can be referenced by another name. /// /// - Parameters: