mirror of
https://github.com/apple/container.git
synced 2026-07-15 22:17:01 +00:00
7358102c68
- Closes #1526 and #1527. ## Type of Change - [x] Bug fix - [ ] New feature - [ ] Breaking change - [ ] Documentation update ## Motivation and Context This PR fixes `system df` to report actual on-disk allocated bytes (content blobs + snapshots) instead of summing per-image snapshot sizes. Orphaned blobs are now included as reclaimable, and storage shared across tags is no longer double counted. Also consolidates three identical `calculateDirectorySize` implementations into a shared `FileManager.allocatedSize(of:)` extension. ## Testing - [x] Tested locally - [x] Added/updated tests - [ ] Added/updated docs
139 lines
6.7 KiB
Swift
139 lines
6.7 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 2026 Apple Inc. and the container 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 ArgumentParser
|
|
import ContainerImagesService
|
|
import ContainerImagesServiceClient
|
|
import ContainerLog
|
|
import ContainerPersistence
|
|
import ContainerPlugin
|
|
import ContainerVersion
|
|
import ContainerXPC
|
|
import Containerization
|
|
import Foundation
|
|
import Logging
|
|
import SystemPackage
|
|
|
|
@main
|
|
struct ImagesHelper: AsyncParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
commandName: "container-core-images",
|
|
abstract: "XPC service for managing OCI images",
|
|
version: ReleaseVersion.singleLine(appName: "container-core-images"),
|
|
subcommands: [
|
|
Start.self
|
|
]
|
|
)
|
|
}
|
|
|
|
extension ImagesHelper {
|
|
struct Start: AsyncParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
commandName: "start",
|
|
abstract: "Starts the image plugin"
|
|
)
|
|
|
|
@Flag(name: .long, help: "Enable debug logging")
|
|
var debug = false
|
|
|
|
@Option(name: .long, help: "XPC service prefix")
|
|
var serviceIdentifier: String = "com.apple.container.core.container-core-images"
|
|
|
|
var appRoot = ApplicationRoot.path
|
|
|
|
var installRoot = InstallRoot.path
|
|
|
|
var logRoot = LogRoot.path
|
|
|
|
func run() async throws {
|
|
let containerSystemConfig: ContainerSystemConfig = try await ConfigurationLoader.load()
|
|
let commandName = ImagesHelper._commandName
|
|
let logPath = logRoot.map { $0.appending("\(commandName).log") }
|
|
let log = ServiceLogger.bootstrap(category: "ImagesHelper", debug: debug, logPath: logPath)
|
|
log.info("starting helper", metadata: ["name": "\(commandName)"])
|
|
defer {
|
|
log.info("stopping helper", metadata: ["name": "\(commandName)"])
|
|
}
|
|
|
|
do {
|
|
log.info("configuring XPC server")
|
|
var routes = [String: XPCServer.RouteHandler]()
|
|
try self.initializeContentService(root: appRoot, log: log, routes: &routes)
|
|
try self.initializeImagesService(root: appRoot, containerSystemConfig: containerSystemConfig, log: log, routes: &routes)
|
|
let xpc = XPCServer(
|
|
identifier: serviceIdentifier,
|
|
routes: routes,
|
|
log: log
|
|
)
|
|
log.info("starting XPC server")
|
|
try await xpc.listen()
|
|
} catch {
|
|
log.error(
|
|
"helper failed",
|
|
metadata: [
|
|
"name": "\(commandName)",
|
|
"error": "\(error)",
|
|
])
|
|
ImagesHelper.exit(withError: error)
|
|
}
|
|
}
|
|
|
|
private func initializeImagesService(root: FilePath, containerSystemConfig: ContainerSystemConfig, log: Logger, routes: inout [String: XPCServer.RouteHandler]) throws {
|
|
// TODO: remove as part of ImageStore URL removal PR
|
|
let rootURL = URL(fileURLWithPath: root.string)
|
|
let contentStore = RemoteContentStoreClient()
|
|
let imageStore = try ImageStore(path: rootURL, contentStore: contentStore)
|
|
let unpackStrategy = SnapshotStore.defaultUnpackStrategy(initImage: containerSystemConfig.vminit.image)
|
|
let snapshotStore = try SnapshotStore(path: rootURL, unpackStrategy: unpackStrategy, log: log)
|
|
let service = try ImagesService(
|
|
contentStore: contentStore,
|
|
imageStore: imageStore,
|
|
snapshotStore: snapshotStore,
|
|
log: log
|
|
)
|
|
let harness = ImagesServiceHarness(service: service, log: log)
|
|
|
|
routes[ImagesServiceXPCRoute.imagePull.rawValue] = XPCServer.route(harness.pull)
|
|
routes[ImagesServiceXPCRoute.imageList.rawValue] = XPCServer.route(harness.list)
|
|
routes[ImagesServiceXPCRoute.imageDelete.rawValue] = XPCServer.route(harness.delete)
|
|
routes[ImagesServiceXPCRoute.imageTag.rawValue] = XPCServer.route(harness.tag)
|
|
routes[ImagesServiceXPCRoute.imagePush.rawValue] = XPCServer.route(harness.push)
|
|
routes[ImagesServiceXPCRoute.imageSave.rawValue] = XPCServer.route(harness.save)
|
|
routes[ImagesServiceXPCRoute.imageLoad.rawValue] = XPCServer.route(harness.load)
|
|
routes[ImagesServiceXPCRoute.imageUnpack.rawValue] = XPCServer.route(harness.unpack)
|
|
routes[ImagesServiceXPCRoute.imageCleanupOrphanedBlobs.rawValue] = XPCServer.route(harness.cleanUpOrphanedBlobs)
|
|
routes[ImagesServiceXPCRoute.imageDiskUsage.rawValue] = XPCServer.route(harness.calculateDiskUsage)
|
|
routes[ImagesServiceXPCRoute.snapshotDelete.rawValue] = XPCServer.route(harness.deleteSnapshot)
|
|
routes[ImagesServiceXPCRoute.snapshotGet.rawValue] = XPCServer.route(harness.getSnapshot)
|
|
}
|
|
|
|
private func initializeContentService(root: FilePath, log: Logger, routes: inout [String: XPCServer.RouteHandler]) throws {
|
|
// TODO: remove as part of ImageStore URL removal PR
|
|
let rootURL = URL(fileURLWithPath: root.string)
|
|
let service = try ContentStoreService(root: rootURL, log: log)
|
|
let harness = ContentServiceHarness(service: service, log: log)
|
|
|
|
routes[ImagesServiceXPCRoute.contentClean.rawValue] = XPCServer.route(harness.clean)
|
|
routes[ImagesServiceXPCRoute.contentGet.rawValue] = XPCServer.route(harness.get)
|
|
routes[ImagesServiceXPCRoute.contentDelete.rawValue] = XPCServer.route(harness.delete)
|
|
routes[ImagesServiceXPCRoute.contentSize.rawValue] = XPCServer.route(harness.totalSize)
|
|
routes[ImagesServiceXPCRoute.contentIngestStart.rawValue] = XPCServer.route(harness.newIngestSession)
|
|
routes[ImagesServiceXPCRoute.contentIngestCancel.rawValue] = XPCServer.route(harness.cancelIngestSession)
|
|
routes[ImagesServiceXPCRoute.contentIngestComplete.rawValue] = XPCServer.route(harness.completeIngestSession)
|
|
}
|
|
}
|
|
}
|