123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- //
- // Copyright © 2023 osy. All rights reserved.
- //
- // 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
- //
- // http://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 Foundation
- private typealias SwtpmMainFunction = @convention(c) (_ argc: Int32, _ argv: UnsafeMutablePointer<UnsafePointer<CChar>>, _ prgname: UnsafePointer<CChar>, _ iface: UnsafePointer<CChar>) -> Int32
- private let kMaxAttempts = 15
- private let kRetryDelay = 1*NSEC_PER_SEC
- class UTMSWTPM: UTMProcess {
- private var swtpmMain: SwtpmMainFunction!
- private var hasProcessExited: Bool = false
- private var lastErrorLine: String?
-
- var ctrlSocketUrl: URL?
- var dataUrl: URL?
-
- private override init(arguments: [String]) {
- super.init(arguments: arguments)
- entry = { process, argc, argv, envp in
- let _self = process as! UTMSWTPM
- return _self.swtpmMain(argc, argv, "swtpm", "socket")
- }
- standardError = Pipe()
- standardError!.fileHandleForReading.readabilityHandler = { [weak self] handle in
- let string = String(data: handle.availableData, encoding: .utf8) ?? ""
- logger.debug("\(string)")
- self?.lastErrorLine = string
- }
- standardOutput = Pipe()
- standardOutput!.fileHandleForReading.readabilityHandler = { handle in
- let string = String(data: handle.availableData, encoding: .utf8) ?? ""
- logger.debug("\(string)")
- }
- }
-
- convenience init() {
- self.init(arguments: [])
- }
-
- override func didLoadDylib(_ handle: UnsafeMutableRawPointer) -> Bool {
- let sym = dlsym(handle, "swtpm_main")
- swtpmMain = unsafeBitCast(sym, to: SwtpmMainFunction.self)
- return swtpmMain != nil
- }
-
- override func processHasExited(_ exitCode: Int, message: String?) {
- hasProcessExited = true
- if let message = message {
- logger.error("SWTPM exited: \(message)")
- }
- }
-
- func start() async throws {
- guard let ctrlSocketUrl = ctrlSocketUrl else {
- throw UTMSWTPMError.socketNotSpecified
- }
- guard let dataUrl = dataUrl else {
- throw UTMSWTPMError.dataNotSpecified
- }
- let fm = FileManager.default
- if !fm.fileExists(atPath: dataUrl.path) {
- fm.createFile(atPath: dataUrl.path, contents: nil)
- }
- let dataBookmark = try dataUrl.bookmarkData()
- let (success, _, _) = await accessData(withBookmark: dataBookmark, securityScoped: false)
- guard success else {
- throw UTMSWTPMError.cannotAccessTpmData
- }
- clearArgv()
- pushArgv("--ctrl")
- pushArgv("type=unixio,path=\(ctrlSocketUrl.lastPathComponent),terminate")
- pushArgv("--tpmstate")
- pushArgv("backend-uri=file://\(dataUrl.path)")
- pushArgv("--tpm2")
- hasProcessExited = false
- try? fm.removeItem(at: ctrlSocketUrl)
- try await start("swtpm.0")
- // monitor for socket to be created
- try await Task {
- let fm = FileManager.default
- for _ in 0...kMaxAttempts {
- if hasProcessExited {
- throw UTMSWTPMError.swtpmStartupFailed(lastErrorLine)
- }
- if fm.fileExists(atPath: ctrlSocketUrl.path) {
- return
- }
- try await Task.sleep(nanoseconds: kRetryDelay)
- }
- }.value
- }
- }
- enum UTMSWTPMError: Error {
- case socketNotSpecified
- case dataNotSpecified
- case cannotAccessTpmData
- case swtpmStartupFailed(String?)
- }
- extension UTMSWTPMError: LocalizedError {
- var errorDescription: String? {
- switch self {
- case .socketNotSpecified: return NSLocalizedString("Socket not specified.", comment: "UTMSWTPM")
- case .dataNotSpecified: return NSLocalizedString("Data not specified.", comment: "UTMSWTPM")
- case .cannotAccessTpmData: return NSLocalizedString("Cannot access TPM data.", comment: "UTMSWTPM")
- case .swtpmStartupFailed(let message): return String.localizedStringWithFormat(NSLocalizedString("SW TPM failed to start. %@", comment: "UTMSWTPM"), message ?? "")
- }
- }
- }
|