Skip to content

Commit e3ee2da

Browse files
authored
Validate custom TART_HOME and provide a human-friendly error message (#1138)
* Validate custom TART_HOME and provide a human-friendly error message * Safer way to calculate "descendingURLs"
1 parent 84147f2 commit e3ee2da

File tree

11 files changed

+44
-15
lines changed

11 files changed

+44
-15
lines changed

Sources/tart/Commands/Clone.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ struct Clone: AsyncParsableCommand {
4545
}
4646

4747
func run() async throws {
48-
let ociStorage = VMStorageOCI()
49-
let localStorage = VMStorageLocal()
48+
let ociStorage = try VMStorageOCI()
49+
let localStorage = try VMStorageLocal()
5050

5151
if let remoteName = try? RemoteName(sourceName), !ociStorage.exists(remoteName) {
5252
// Pull the VM in case it's OCI-based and doesn't exist locally yet

Sources/tart/Commands/Import.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ struct Import: AsyncParsableCommand {
1717
}
1818

1919
func run() async throws {
20-
let localStorage = VMStorageLocal()
20+
let localStorage = try VMStorageLocal()
2121

2222
// Create a temporary VM directory to which we will load the export file
2323
let tmpVMDir = try VMDirectory.temporary()

Sources/tart/Commands/Prune.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ struct Prune: AsyncParsableCommand {
5353

5454
switch entries {
5555
case "caches":
56-
prunableStorages = [VMStorageOCI(), try IPSWCache()]
56+
prunableStorages = [try VMStorageOCI(), try IPSWCache()]
5757
case "vms":
58-
prunableStorages = [VMStorageLocal()]
58+
prunableStorages = [try VMStorageLocal()]
5959
default:
6060
throw ValidationError("unsupported --entries value, please specify either \"caches\" or \"vms\"")
6161
}
@@ -152,7 +152,7 @@ struct Prune: AsyncParsableCommand {
152152
let transaction = SentrySDK.startTransaction(name: "Pruning cache", operation: "prune", bindToScope: true)
153153
defer { transaction.finish() }
154154

155-
let prunableStorages: [PrunableStorage] = [VMStorageOCI(), try IPSWCache()]
155+
let prunableStorages: [PrunableStorage] = [try VMStorageOCI(), try IPSWCache()]
156156
let prunables: [Prunable] = try prunableStorages
157157
.flatMap { try $0.prunables() }
158158
.sorted { try $0.accessDate() < $1.accessDate() }

Sources/tart/Commands/Pull.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct Pull: AsyncParsableCommand {
3535
func run() async throws {
3636
// Be more liberal when accepting local image as argument,
3737
// see https://github.com/cirruslabs/tart/issues/36
38-
if VMStorageLocal().exists(remoteName) {
38+
if try VMStorageLocal().exists(remoteName) {
3939
print("\"\(remoteName)\" is a local image, nothing to pull here!")
4040

4141
return

Sources/tart/Commands/Push.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ struct Push: AsyncParsableCommand {
3939
var populateCache: Bool = false
4040

4141
func run() async throws {
42-
let ociStorage = VMStorageOCI()
42+
let ociStorage = try VMStorageOCI()
4343
let localVMDir = try VMStorageHelper.open(localName)
4444
let lock = try localVMDir.lock()
4545
if try !lock.trylock() {

Sources/tart/Commands/Rename.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ struct Rename: AsyncParsableCommand {
1717
}
1818

1919
func run() async throws {
20-
let localStorage = VMStorageLocal()
20+
let localStorage = try VMStorageLocal()
2121

2222
if !localStorage.exists(name) {
2323
throw ValidationError("failed to rename a non-existent local VM: \(name)")

Sources/tart/Commands/Run.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ struct Run: AsyncParsableCommand {
301301
}
302302
}
303303

304-
let localStorage = VMStorageLocal()
304+
let localStorage = try VMStorageLocal()
305305
let vmDir = try localStorage.open(name)
306306
if try vmDir.state() == .Suspended {
307307
suspendable = true
@@ -334,7 +334,7 @@ struct Run: AsyncParsableCommand {
334334

335335
@MainActor
336336
func run() async throws {
337-
let localStorage = VMStorageLocal()
337+
let localStorage = try VMStorageLocal()
338338
let vmDir = try localStorage.open(name)
339339

340340
// Validate disk format support

Sources/tart/Config.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ struct Config {
99
var tartHomeDir: URL
1010

1111
if let customTartHome = ProcessInfo.processInfo.environment["TART_HOME"] {
12-
tartHomeDir = URL(fileURLWithPath: customTartHome)
12+
tartHomeDir = URL(fileURLWithPath: customTartHome, isDirectory: true)
13+
try Self.validateTartHome(url: tartHomeDir)
1314
} else {
1415
tartHomeDir = FileManager.default
1516
.homeDirectoryForCurrentUser
@@ -49,4 +50,24 @@ struct Config {
4950
static func jsonDecoder() -> JSONDecoder {
5051
JSONDecoder()
5152
}
53+
54+
private static func validateTartHome(url: URL) throws {
55+
let urlComponents = url.pathComponents
56+
57+
let descendingURLs = urlComponents.indices.map { i in
58+
URL(fileURLWithPath: urlComponents[0...i].joined(separator: "/"))
59+
}
60+
61+
for descendingURL in descendingURLs {
62+
if FileManager.default.fileExists(atPath: descendingURL.path) {
63+
continue
64+
}
65+
66+
do {
67+
try FileManager.default.createDirectory(at: descendingURL, withIntermediateDirectories: false)
68+
} catch {
69+
throw RuntimeError.Generic("TART_HOME is invalid: \(descendingURL.path) does not exist, yet we can't create it: \(error.localizedDescription)")
70+
}
71+
}
72+
}
5273
}

Sources/tart/Root.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ struct Root: AsyncParsableCommand {
9292
do {
9393
try Config().gc()
9494
} catch {
95-
fputs("Failed to perform garbage collection!\n\(error)\n", stderr)
95+
fputs("Failed to perform garbage collection: \(error)\n", stderr)
9696
}
9797
}
9898

Sources/tart/VMStorageLocal.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import Foundation
22

33
class VMStorageLocal: PrunableStorage {
4-
let baseURL: URL = try! Config().tartHomeDir.appendingPathComponent("vms", isDirectory: true)
4+
let baseURL: URL
5+
6+
init() throws {
7+
baseURL = try Config().tartHomeDir.appendingPathComponent("vms", isDirectory: true)
8+
}
59

610
private func vmURL(_ name: String) -> URL {
711
baseURL.appendingPathComponent(name, isDirectory: true)

0 commit comments

Comments
 (0)