@braedonsaunders/appkit-desk 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,13 @@ import DeskNet
2
2
  import Foundation
3
3
  import Virtualization
4
4
 
5
+ /// A host directory shared into the guest over virtio-fs.
6
+ struct ShareRequest {
7
+ let tag: String
8
+ let directory: URL
9
+ let readOnly: Bool
10
+ }
11
+
5
12
  /// Everything `boot` needs, validated before anything is created.
6
13
  struct BootRequest {
7
14
  let kernel: URL
@@ -16,6 +23,7 @@ struct BootRequest {
16
23
  let machineIdentifier: Data?
17
24
  let consoleLog: URL?
18
25
  let network: NetworkConfiguration
26
+ let shares: [ShareRequest]
19
27
 
20
28
  init(_ body: [String: Any]) throws {
21
29
  kernel = URL(fileURLWithPath: try body.path("kernel"))
@@ -39,6 +47,7 @@ struct BootRequest {
39
47
  if restoreFrom != nil && machineIdentifier == nil {
40
48
  throw RequestError("restoreFrom requires the machineIdentifier the machine was saved with.")
41
49
  }
50
+ shares = try Self.shares(body["shares"])
42
51
 
43
52
  let network = (body["network"] as? [String: Any]) ?? [:]
44
53
  var configuration = NetworkConfiguration(allowLan: try network.bool("allowLan", default: false))
@@ -74,6 +83,27 @@ struct BootRequest {
74
83
  }
75
84
  self.network = configuration
76
85
  }
86
+
87
+ /// Tags are checked against Virtualization.framework's own rules when the
88
+ /// devices are made; here, only that each is present and unique.
89
+ private static func shares(_ value: Any?) throws -> [ShareRequest] {
90
+ guard let value, !(value is NSNull) else { return [] }
91
+ guard let entries = value as? [[String: Any]] else { throw RequestError("shares must be an array of objects.") }
92
+ var tags = Set<String>()
93
+ return try entries.enumerated().map { index, entry in
94
+ let tag = try entry.string("tag")
95
+ guard tags.insert(tag).inserted else { throw RequestError("shares[\(index)].tag \(tag) is used by another share.") }
96
+ let path = try entry.path("path")
97
+ var isDirectory: ObjCBool = false
98
+ guard FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory), isDirectory.boolValue else {
99
+ throw RequestError("shares[\(index)].path is not an existing directory: \(path)")
100
+ }
101
+ guard entry["readOnly"] is NSNumber else { throw RequestError("shares[\(index)].readOnly must be a boolean.") }
102
+ return ShareRequest(
103
+ tag: tag, directory: URL(fileURLWithPath: path, isDirectory: true),
104
+ readOnly: try entry.bool("readOnly", default: false))
105
+ }
106
+ }
77
107
  }
78
108
 
79
109
  /// One helper process runs one VM. It owns the VM, the guest's network, and
@@ -332,6 +362,21 @@ final class Helper: NSObject, VZVirtualMachineDelegate {
332
362
  network.guestMAC = MAC(nic.macAddress.string) ?? network.guestMAC
333
363
  attachNetwork(network, fd: helperEnd)
334
364
 
365
+ // Built from the request on every boot, so a restore has exactly the
366
+ // devices the machine was saved with as long as the host asks for the same
367
+ // shares — which it checks before restoring.
368
+ configuration.directorySharingDevices = try request.shares.map { share in
369
+ do {
370
+ try VZVirtioFileSystemDeviceConfiguration.validateTag(share.tag)
371
+ } catch {
372
+ throw RequestError("Share tag \(share.tag) is not a valid virtio-fs tag: \(describe(error))")
373
+ }
374
+ let device = VZVirtioFileSystemDeviceConfiguration(tag: share.tag)
375
+ device.share = VZSingleDirectoryShare(
376
+ directory: VZSharedDirectory(url: share.directory, readOnly: share.readOnly))
377
+ return device
378
+ }
379
+
335
380
  configuration.entropyDevices = [VZVirtioEntropyDeviceConfiguration()]
336
381
  configuration.memoryBalloonDevices = [VZVirtioTraditionalMemoryBalloonDeviceConfiguration()]
337
382
  configuration.socketDevices = [VZVirtioSocketDeviceConfiguration()]