diff --git a/Sources/tart/Commands/Run.swift b/Sources/tart/Commands/Run.swift index 4f06c52..c29814e 100644 --- a/Sources/tart/Commands/Run.swift +++ b/Sources/tart/Commands/Run.swift @@ -224,7 +224,7 @@ struct Run: AsyncParsableCommand { // configuration file, otherwise we will loose the lock. // // [1]: https://man.openbsd.org/fcntl - let lock = try PIDLock(lockURL: vmDir.configURL) + let lock = try vmDir.lock() if try !lock.trylock() { throw RuntimeError.VMAlreadyRunning("VM \"\(name)\" is already running!") } diff --git a/Sources/tart/Commands/Stop.swift b/Sources/tart/Commands/Stop.swift index 9c97c1e..85f5d2c 100644 --- a/Sources/tart/Commands/Stop.swift +++ b/Sources/tart/Commands/Stop.swift @@ -29,12 +29,12 @@ struct Stop: AsyncParsableCommand { } func stopRunning(_ vmDir: VMDirectory) async throws { - let lock = try PIDLock(lockURL: vmDir.configURL) + let lock = try vmDir.lock() // Find the VM's PID var pid = try lock.pid() if pid == 0 { - throw RuntimeError.VMNotRunning("VM \"\(name)\" is not running") + throw RuntimeError.VMNotRunning(name) } // Try to gracefully terminate the VM diff --git a/Sources/tart/Commands/Suspend.swift b/Sources/tart/Commands/Suspend.swift index c5a638c..b476240 100644 --- a/Sources/tart/Commands/Suspend.swift +++ b/Sources/tart/Commands/Suspend.swift @@ -11,7 +11,7 @@ struct Suspend: AsyncParsableCommand { func run() async throws { let vmDir = try VMStorageLocal().open(name) - let lock = try PIDLock(lockURL: vmDir.configURL) + let lock = try vmDir.lock() // Find the VM's PID var pid = try lock.pid() diff --git a/Sources/tart/VMDirectory.swift b/Sources/tart/VMDirectory.swift index ec755f6..eb174eb 100644 --- a/Sources/tart/VMDirectory.swift +++ b/Sources/tart/VMDirectory.swift @@ -30,13 +30,17 @@ struct VMDirectory: Prunable { baseURL } + func lock() throws -> PIDLock { + try PIDLock(lockURL: configURL) + } + func running() throws -> Bool { // The most common reason why PIDLock() instantiation fails is a race with "tart delete" (ENOENT), // which is fine to report as "not running". // // The other reasons are unlikely and the cost of getting a false positive is way less than // the cost of crashing with an exception when calling "tart list" on a busy machine, for example. - guard let lock = try? PIDLock(lockURL: configURL) else { + guard let lock = try? lock() else { return false } @@ -137,7 +141,15 @@ struct VMDirectory: Prunable { } func delete() throws { + let lock = try lock() + + if try !lock.trylock() { + throw RuntimeError.VMIsRunning(name) + } + try FileManager.default.removeItem(at: baseURL) + + try lock.unlock() } func accessDate() throws -> Date { diff --git a/Sources/tart/VMStorageHelper.swift b/Sources/tart/VMStorageHelper.swift index 91ff29d..3bc2fe9 100644 --- a/Sources/tart/VMStorageHelper.swift +++ b/Sources/tart/VMStorageHelper.swift @@ -50,7 +50,8 @@ enum RuntimeError : Error { case VMConfigurationError(_ message: String) case VMDoesNotExist(name: String) case VMMissingFiles(_ message: String) - case VMNotRunning(_ message: String) + case VMIsRunning(_ name: String) + case VMNotRunning(_ name: String) case VMAlreadyRunning(_ message: String) case NoIPAddressFound(_ message: String) case DiskAlreadyInUse(_ message: String) @@ -81,8 +82,10 @@ extension RuntimeError : CustomStringConvertible { return "the specified VM \"\(name)\" does not exist" case .VMMissingFiles(let message): return message - case .VMNotRunning(let message): - return message + case .VMIsRunning(let name): + return "VM \"\(name)\" is running" + case .VMNotRunning(let name): + return "VM \"\(name)\" is not running" case .VMAlreadyRunning(let message): return message case .NoIPAddressFound(let message): diff --git a/Sources/tart/VMStorageLocal.swift b/Sources/tart/VMStorageLocal.swift index b1fc576..a67bb9d 100644 --- a/Sources/tart/VMStorageLocal.swift +++ b/Sources/tart/VMStorageLocal.swift @@ -39,7 +39,7 @@ class VMStorageLocal: PrunableStorage { } func delete(_ name: String) throws { - try FileManager.default.removeItem(at: vmURL(name)) + try VMDirectory(baseURL: vmURL(name)).delete() } func list() throws -> [(String, VMDirectory)] {