diff --git a/Cargo.lock b/Cargo.lock index dc5c944359..33e95c453b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7766,6 +7766,7 @@ dependencies = [ "indoc", "itertools 0.15.0", "libc", + "liblzma", "maml", "miette", "nu-ansi-term", diff --git a/package.json b/package.json index 42af40b5e9..d09d80cfb2 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "macos:build": "nu scripts/macos/build.nu", "macos:check": "nu scripts/macos/check.nu", "macos:doctor": "nu scripts/macos/doctor.nu", + "macos:install": "nu scripts/macos/install.nu", "macos:release": "nu scripts/macos/release.nu", "macos:run": "nu scripts/macos/run.nu" }, diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 55b5faf47e..4d13b8c376 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -61,6 +61,7 @@ opentelemetry_sdk = { workspace = true } indexmap = { workspace = true } indoc = { workspace = true } libc = { workspace = true } +liblzma = { workspace = true } maml = { workspace = true } miette = { workspace = true } num = { workspace = true } diff --git a/packages/cli/test.nu b/packages/cli/test.nu index 5268d3799b..6d2d35ab33 100644 --- a/packages/cli/test.nu +++ b/packages/cli/test.nu @@ -114,6 +114,8 @@ def main [ # Build and install the current macOS app and file system extension. Isolate # its default-feature Cargo build from the all-features test binary. if $fskit { + force_unmount_vfs (fskit_temp_root) + stop_fskit_provider $release let build_args = if $release { ['--release'] } else { [] } let cargo_target_dir = ($repository_path | path join 'target/macos') ^bun run macos:build --cargo-target-dir $cargo_target_dir ...$build_args @@ -923,12 +925,15 @@ export def --env spawn [ --quickjs # Use QuickJS as the JS engine. --url (-u): string ] { - # Give the object store's lock semaphores a unique prefix per server. lmdb - # appends 'r' and 'w' to form the two POSIX semaphore names, which are global - # to the machine, so concurrent test servers must not share a prefix. The - # prefix plus one character must fit the platform limit, which is 31 - # characters on macOS. - let object_store_posix_sem_prefix = $'/tg-((random chars) | str lowercase | str substring 0..7)' + let use_fskit = (($env.TANGRAM_TEST_FSKIT? | default "") | str length) > 0 + + # Use unique semaphore names in the namespace FSKit can access. + let object_store_posix_sem_prefix = if $use_fskit { + let app_group_identifier = (identifiers).app_group_identifier + $'($app_group_identifier)/((random chars) | str lowercase | str substring 0..5)' + } else { + $'/tg-((random chars) | str lowercase | str substring 0..7)' + } mut default_config = { advanced: { @@ -978,7 +983,6 @@ export def --env spawn [ } } - let use_fskit = (($env.TANGRAM_TEST_FSKIT? | default "") | str length) > 0 if $use_fskit { $default_config = $default_config | merge deep { vfs: { @@ -1198,7 +1202,11 @@ export def --env spawn [ rm -rf $path } - { config: $config_path, directory: $directory_path, url: $url } + let vfs = $config | get --optional vfs + let cache_directory_name = if $vfs == null or $vfs == false { 'artifacts' } else { 'cache' } + let cache_directory = $directory_path | path join $cache_directory_name + + { cache_directory: $cache_directory, config: $config_path, directory: $directory_path, url: $url } } def clean_databases [id: string] { @@ -1674,6 +1682,35 @@ def force_unmount_vfs_macos [path: string] { } } +def stop_fskit_provider [release: bool] { + let app_name = if $release { 'Tangram' } else { 'Tangram Dev' } + let executable = ( + $env.HOME + | path join $'Applications/($app_name).app/Contents/Extensions/TangramFSKit.appex/Contents/MacOS/TangramFSKit' + ) + let pids = fskit_provider_pids $executable + for pid in $pids { + try { kill --quiet $pid } + } + for _ in 1..100 { + if (fskit_provider_pids $executable | is-empty) { + return + } + sleep 50ms + } + for pid in (fskit_provider_pids $executable) { + try { kill --force --quiet $pid } + } +} + +def fskit_provider_pids [executable: path] { + ps --long + | where { |process| + ($process.command? | default '') | str starts-with $executable + } + | get pid +} + def force_unmount_vfs_linux [path: string] { let mounted_artifacts_paths = ( try { diff --git a/packages/cli/tests/cache/branch_directory.nu b/packages/cli/tests/cache/branch_directory.nu index 89c74545cd..0d5d58b888 100644 --- a/packages/cli/tests/cache/branch_directory.nu +++ b/packages/cli/tests/cache/branch_directory.nu @@ -28,4 +28,4 @@ let id = tg checkin $path tg cache $id # Snapshot the artifacts directory. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/directory.nu b/packages/cli/tests/cache/directory.nu index 366269f730..aa8842ff8e 100644 --- a/packages/cli/tests/cache/directory.nu +++ b/packages/cli/tests/cache/directory.nu @@ -13,4 +13,4 @@ let id = tg put $artifact let output = tg cache $id -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/directory_containing_file_cycle.nu b/packages/cli/tests/cache/directory_containing_file_cycle.nu index eb501e98fb..4a86b65685 100644 --- a/packages/cli/tests/cache/directory_containing_file_cycle.nu +++ b/packages/cli/tests/cache/directory_containing_file_cycle.nu @@ -25,11 +25,11 @@ let path = artifact { '# } let id = tg build --no-cache-pointers $path -rm --recursive --force ($server.directory | path join "artifacts") -mkdir ($server.directory | path join "artifacts") +rm --recursive --force $server.cache_directory +mkdir $server.cache_directory # Cache. tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/directory_with_file_that_depends_on_directory.nu b/packages/cli/tests/cache/directory_with_file_that_depends_on_directory.nu index 7e23f967cc..65871115ca 100644 --- a/packages/cli/tests/cache/directory_with_file_that_depends_on_directory.nu +++ b/packages/cli/tests/cache/directory_with_file_that_depends_on_directory.nu @@ -21,10 +21,10 @@ let path = artifact { '# } let id = tg build --no-cache-pointers $path -rm --recursive --force ($server.directory | path join "artifacts") -mkdir ($server.directory | path join "artifacts") +rm --recursive --force $server.cache_directory +mkdir $server.cache_directory # Cache. tg cache $id -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/directory_with_file_with_dependency.nu b/packages/cli/tests/cache/directory_with_file_with_dependency.nu index 04ca6ca27b..516bc97722 100644 --- a/packages/cli/tests/cache/directory_with_file_with_dependency.nu +++ b/packages/cli/tests/cache/directory_with_file_with_dependency.nu @@ -23,4 +23,4 @@ let id = tg put $artifact tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/directory_with_symlink.nu b/packages/cli/tests/cache/directory_with_symlink.nu index 9c3bf22888..a14890c36c 100644 --- a/packages/cli/tests/cache/directory_with_symlink.nu +++ b/packages/cli/tests/cache/directory_with_symlink.nu @@ -19,4 +19,4 @@ let id = tg put $artifact tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/directory_with_symlink_cycle.nu b/packages/cli/tests/cache/directory_with_symlink_cycle.nu index f68217bbb8..a103a47ffd 100644 --- a/packages/cli/tests/cache/directory_with_symlink_cycle.nu +++ b/packages/cli/tests/cache/directory_with_symlink_cycle.nu @@ -33,11 +33,11 @@ let artifact = artifact { } let id = tg checkin --no-cache-pointers $artifact let id = tg build $id -rm --recursive --force ($server.directory | path join "artifacts") -mkdir ($server.directory | path join "artifacts") +rm --recursive --force $server.cache_directory +mkdir $server.cache_directory # Cache. tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/directory_with_symlink_with_dependency.nu b/packages/cli/tests/cache/directory_with_symlink_with_dependency.nu index b436fc6267..3569152adb 100644 --- a/packages/cli/tests/cache/directory_with_symlink_with_dependency.nu +++ b/packages/cli/tests/cache/directory_with_symlink_with_dependency.nu @@ -18,4 +18,4 @@ let id = tg put $artifact tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/directory_with_two_entries_in_same_graph.nu b/packages/cli/tests/cache/directory_with_two_entries_in_same_graph.nu index 467a607673..85c55fd27f 100644 --- a/packages/cli/tests/cache/directory_with_two_entries_in_same_graph.nu +++ b/packages/cli/tests/cache/directory_with_two_entries_in_same_graph.nu @@ -32,9 +32,9 @@ let path = artifact { '# } let id = tg build --no-cache-pointers $path -rm --recursive --force ($server.directory | path join "artifacts") -mkdir ($server.directory | path join "artifacts") +rm --recursive --force $server.cache_directory +mkdir $server.cache_directory tg cache $id | complete -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/directory_with_two_identical_files.nu b/packages/cli/tests/cache/directory_with_two_identical_files.nu index 22870701bb..f6acb601f6 100644 --- a/packages/cli/tests/cache/directory_with_two_identical_files.nu +++ b/packages/cli/tests/cache/directory_with_two_identical_files.nu @@ -17,4 +17,4 @@ let id = tg put $artifact tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/executable_file.nu b/packages/cli/tests/cache/executable_file.nu index c28a53d512..f2f4471b76 100644 --- a/packages/cli/tests/cache/executable_file.nu +++ b/packages/cli/tests/cache/executable_file.nu @@ -17,4 +17,4 @@ let id = tg put $artifact tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/file.nu b/packages/cli/tests/cache/file.nu index b233029ff0..3c6fc55f46 100644 --- a/packages/cli/tests/cache/file.nu +++ b/packages/cli/tests/cache/file.nu @@ -14,4 +14,4 @@ let id = tg put $artifact tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/file_cycle.nu b/packages/cli/tests/cache/file_cycle.nu index f17b4734bf..9b95216073 100644 --- a/packages/cli/tests/cache/file_cycle.nu +++ b/packages/cli/tests/cache/file_cycle.nu @@ -21,11 +21,11 @@ let path = artifact { '# } let id = tg build --no-cache-pointers $path -rm --recursive --force ($server.directory | path join "artifacts") -mkdir ($server.directory | path join "artifacts") +rm --recursive --force $server.cache_directory +mkdir $server.cache_directory # Cache. let output = tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/file_with_dependency.nu b/packages/cli/tests/cache/file_with_dependency.nu index f28833a898..ccd18f0291 100644 --- a/packages/cli/tests/cache/file_with_dependency.nu +++ b/packages/cli/tests/cache/file_with_dependency.nu @@ -21,4 +21,4 @@ let id = tg put $artifact tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/graph_directory.nu b/packages/cli/tests/cache/graph_directory.nu index 691ff0cada..9fd213c056 100644 --- a/packages/cli/tests/cache/graph_directory.nu +++ b/packages/cli/tests/cache/graph_directory.nu @@ -28,11 +28,11 @@ let artifact = artifact { } let id = tg checkin --no-cache-pointers $artifact let id = tg build $id -rm --recursive --force ($server.directory | path join "artifacts") -mkdir ($server.directory | path join "artifacts") +rm --recursive --force $server.cache_directory +mkdir $server.cache_directory # Cache. tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/graph_file.nu b/packages/cli/tests/cache/graph_file.nu index 26d5710403..bbdec4a1b2 100644 --- a/packages/cli/tests/cache/graph_file.nu +++ b/packages/cli/tests/cache/graph_file.nu @@ -25,4 +25,4 @@ let id = tg put $artifact tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/graph_symlink.nu b/packages/cli/tests/cache/graph_symlink.nu index 60dbed937e..d1529dbc93 100644 --- a/packages/cli/tests/cache/graph_symlink.nu +++ b/packages/cli/tests/cache/graph_symlink.nu @@ -25,7 +25,7 @@ let id = tg put $artifact tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") ' +snapshot --path $server.cache_directory ' { "kind": "directory", "entries": { diff --git a/packages/cli/tests/cache/reference_to_cycle.nu b/packages/cli/tests/cache/reference_to_cycle.nu index 7189fb392c..a36a256f84 100644 --- a/packages/cli/tests/cache/reference_to_cycle.nu +++ b/packages/cli/tests/cache/reference_to_cycle.nu @@ -21,9 +21,9 @@ let artifact = artifact { ' } let id = tg build --no-cache-pointers $artifact -rm --recursive --force ($server.directory | path join "artifacts") -mkdir ($server.directory | path join "artifacts") +rm --recursive --force $server.cache_directory +mkdir $server.cache_directory let output = tg cache $id -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/cli/tests/cache/symlink.nu b/packages/cli/tests/cache/symlink.nu index 51f08baf32..82af8463de 100644 --- a/packages/cli/tests/cache/symlink.nu +++ b/packages/cli/tests/cache/symlink.nu @@ -16,4 +16,4 @@ let id = tg put $artifact tg cache $id # Snapshot. -snapshot --path ($server.directory | path join "artifacts") +snapshot --path $server.cache_directory diff --git a/packages/macos/Tangram/Resources/TangramHelper.entitlements b/packages/macos/Tangram/Resources/TangramHelper.entitlements index cca812aeea..6cdb50709a 100644 --- a/packages/macos/Tangram/Resources/TangramHelper.entitlements +++ b/packages/macos/Tangram/Resources/TangramHelper.entitlements @@ -2,6 +2,10 @@ + com.apple.security.cs.allow-jit + + com.apple.security.cs.disable-library-validation + com.apple.security.inherit diff --git a/packages/macos/TangramFSKit/main.swift b/packages/macos/TangramFSKit/main.swift index ae42d02d10..a12afc41a3 100644 --- a/packages/macos/TangramFSKit/main.swift +++ b/packages/macos/TangramFSKit/main.swift @@ -307,11 +307,8 @@ final class TangramVolume: FSVolume, FSVolume.Operations, FSVolume.OpenCloseOper let objectStoreMapSize = options["object_store_map_size"].flatMap(UInt64.init) ?? 0 let objectStorePath = options["object_store_path"] ?? "" - // The object store's lock semaphores must be named so that the sandboxed - // extension and the server can both open them. When the server does not - // send a prefix, default to the app group identifier, which the sandbox - // permits both processes to open. LMDB appends an 'r' or 'w' character. - let objectStorePosixSemPrefix = options["object_store_posix_sem_prefix"] ?? appGroupIdentifier + // Share the LMDB lock through the app group. + let objectStorePosixSemPrefix = options["object_store_posix_sem_prefix"] ?? "\(appGroupIdentifier)/lmdb" // The client connects to the server over the unix socket the server sends as // a mount option, which lets concurrent servers each serve on their own @@ -654,6 +651,10 @@ final class TangramVolume: FSVolume, FSVolume.Operations, FSVolume.OpenCloseOper reply(nil) return } + if item.handle != nil { + reply(nil) + return + } submit({ response, status in var handle: UInt64 = 0 guard let response, tg_response_open(response, &handle, nil) == 0 else { @@ -668,7 +669,7 @@ final class TangramVolume: FSVolume, FSVolume.Operations, FSVolume.OpenCloseOper } func closeItem(_ item: FSItem, modes: FSVolume.OpenModes, replyHandler reply: @escaping (Error?) -> Void) { - if let item = item as? TangramItem, let handle = item.handle { + if modes.isEmpty, let item = item as? TangramItem, let handle = item.handle { _ = self.withProvider { tg_provider_close_sync($0, handle) } item.handle = nil } @@ -682,15 +683,50 @@ final class TangramVolume: FSVolume, FSVolume.Operations, FSVolume.OpenCloseOper into buffer: FSMutableFileDataBuffer, replyHandler reply: @escaping (Int, Error?) -> Void, ) { - guard let item = item as? TangramItem, let handle = item.handle else { - reply(0, posixError(EBADF)) + guard let item = item as? TangramItem else { + reply(0, posixError(EINVAL)) return } guard offset >= 0 else { reply(0, posixError(EINVAL)) return } + if let handle = item.handle { + read(handle: handle, offset: offset, length: length, into: buffer, closeAfterRead: false, replyHandler: reply) + return + } + // FSKit can read an executable before opening it. submit({ response, status in + var handle: UInt64 = 0 + guard let response, tg_response_open(response, &handle, nil) == 0 else { + reply(0, posixError(status < 0 ? -status : EIO)) + return + } + self.read( + handle: handle, + offset: offset, + length: length, + into: buffer, + closeAfterRead: true, + replyHandler: reply, + ) + }, { callback, context in + self.withProvider { tg_provider_open_async($0, item.nodeID, callback, context) } + }) + } + + private func read( + handle: UInt64, + offset: off_t, + length: Int, + into buffer: FSMutableFileDataBuffer, + closeAfterRead: Bool, + replyHandler reply: @escaping (Int, Error?) -> Void, + ) { + submit({ response, status in + if closeAfterRead { + _ = self.withProvider { tg_provider_close_sync($0, handle) } + } var pointer: UnsafePointer? var count = 0 guard let response, tg_response_bytes(response, &pointer, &count) == 0, let pointer else { diff --git a/packages/sandbox/src/seatbelt/spawn.rs b/packages/sandbox/src/seatbelt/spawn.rs index 9834a50f4b..5df3860172 100644 --- a/packages/sandbox/src/seatbelt/spawn.rs +++ b/packages/sandbox/src/seatbelt/spawn.rs @@ -102,6 +102,7 @@ fn create_sandbox_profile(arg: &crate::Arg) -> tg::Result { (deny default) ;; Allow most system operations. + (allow dynamic-code-generation) (allow syscall*) (allow system-socket) (allow mach*) diff --git a/packages/server/src/checkin/input.rs b/packages/server/src/checkin/input.rs index 9fdd3e71b5..ab771dbfb6 100644 --- a/packages/server/src/checkin/input.rs +++ b/packages/server/src/checkin/input.rs @@ -607,10 +607,20 @@ impl Session { return Ok(()); }; - // If the target is in the cache directory, then treat it as an artifact symlink. - if let Ok(path_in_cache_path) = absolute_target.strip_prefix(self.server.cache_path()) { + // Check for an artifact symlink. + let cache_path = self.server.cache_path(); + let artifacts_path = self.server.artifacts_path(); + let target_in_artifact_path = absolute_target + .strip_prefix(&cache_path) + .map(|path| (&cache_path, path)) + .or_else(|_| { + absolute_target + .strip_prefix(&artifacts_path) + .map(|path| (&artifacts_path, path)) + }); + if let Ok((artifact_path, path_in_artifact_path)) = target_in_artifact_path { // Get the artifact. - let mut components = path_in_cache_path.components(); + let mut components = path_in_artifact_path.components(); let Some(artifact) = components.next().and_then(|component| { if let std::path::Component::Normal(component) = component { component.to_str()?.parse::().ok() @@ -631,7 +641,7 @@ impl Session { // If this is a destructive checkin and the target is absolute, make it relative. if state.arg.options.destructive && target.is_absolute() { - let mut source = self.server.cache_path().join("_"); + let mut source = artifact_path.join("_"); if let Ok(path) = path.strip_prefix(state.root) { source.push(path); } diff --git a/packages/server/src/config.rs b/packages/server/src/config.rs index 5840c7fcb0..be32272c12 100644 --- a/packages/server/src/config.rs +++ b/packages/server/src/config.rs @@ -1160,9 +1160,12 @@ pub struct SyncPutStore { pub process_concurrency: usize, } -#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(default, deny_unknown_fields)] pub struct Vfs { + /// The macOS app group identifier. + pub app_group_identifier: Option, + pub kind: VfsKind, pub io: VfsIo, @@ -1549,13 +1552,14 @@ impl Default for LmdbObjectStore { } impl LmdbObjectStore { - /// Returns the POSIX semaphore prefix, falling back to the macOS app group identifier that the app passes in the environment. This lets the server and the sandboxed file system extension share the same lock by default, because the sandbox permits both processes to open a semaphore named for their shared app group. The server and the extension resolve the same value so the writer and the reader agree. + /// Returns the configured POSIX semaphore prefix or the app group default. #[must_use] pub fn resolved_posix_sem_prefix(&self) -> Option { self.posix_sem_prefix.clone().or_else(|| { std::env::var("TANGRAM_MACOS_APP_GROUP_IDENTIFIER") .ok() .filter(|value| !value.is_empty()) + .map(|identifier| format!("{identifier}/lmdb")) }) } } @@ -1783,6 +1787,7 @@ impl Default for SyncPutStore { impl Default for Vfs { fn default() -> Self { Self { + app_group_identifier: None, kind: VfsKind::Auto, io: VfsIo::Auto, passthrough: VfsPassthrough::Auto, @@ -1790,6 +1795,23 @@ impl Default for Vfs { } } +impl Vfs { + /// Resolves the macOS app group socket path. + #[must_use] + pub fn resolved_app_group_socket(&self) -> Option { + if let Some(path) = std::env::var_os("TANGRAM_MACOS_APP_GROUP_SOCKET") { + return Some(PathBuf::from(path)); + } + let identifier = self.app_group_identifier.as_ref()?; + let home = std::env::var_os("HOME")?; + let socket = PathBuf::from(home) + .join("Library/Group Containers") + .join(identifier) + .join("socket"); + Some(socket) + } +} + impl Default for Watch { fn default() -> Self { Self { diff --git a/packages/server/src/lib.rs b/packages/server/src/lib.rs index 9be3701ca6..2d9800c6cc 100644 --- a/packages/server/src/lib.rs +++ b/packages/server/src/lib.rs @@ -866,7 +866,7 @@ impl Server { // Start the VFS if enabled. let artifacts_path = server.artifacts_path(); let cache_path = server.path.join("cache"); - let vfs_kind = match server.config.vfs.unwrap_or_default().kind { + let vfs_kind = match server.config.vfs.clone().unwrap_or_default().kind { config::VfsKind::Auto => { if cfg!(target_os = "macos") && std::env::var_os("TANGRAM_MACOS_APP_SOCKET").is_some() @@ -897,7 +897,7 @@ impl Server { let cache_exists = tokio::fs::try_exists(&cache_path) .await .map_err(|error| tg::error!(!error, "failed to stat the path"))?; - if let Some(options) = server.config.vfs { + if let Some(options) = server.config.vfs.clone() { if artifacts_exists && !cache_exists { tokio::fs::rename(&artifacts_path, &cache_path) .await @@ -950,9 +950,14 @@ impl Server { } else { config.listeners.clone() }; - // On macOS, also listen on the socket in the shared app group - // container so the sandboxed file system extension can connect. - if let Some(path) = std::env::var_os("TANGRAM_MACOS_APP_GROUP_SOCKET") { + // Add the socket shared with the macOS extension. + let group_socket = server + .config + .vfs + .clone() + .unwrap_or_default() + .resolved_app_group_socket(); + if let Some(path) = group_socket { let url = Uri::builder() .scheme("http+unix") .authority(path.to_str().unwrap()) diff --git a/packages/server/src/read.rs b/packages/server/src/read.rs index d7f6a29b22..2fb82ae2eb 100644 --- a/packages/server/src/read.rs +++ b/packages/server/src/read.rs @@ -246,21 +246,25 @@ impl Reader { tg::object::Id::from(id.clone()), blob.state().token(), ); - session + let authorized = session .authorize(resource, permission) .await? - .filter(|permissions| permissions.contains(permission)) - .ok_or_else(|| tg::error!(%id, "failed to get the object"))?; - let arg = crate::object::store::TryGetArg { - id: id.clone().into(), + .is_some_and(|permissions| permissions.contains(permission)); + let cache_pointer = if authorized { + let arg = crate::object::store::TryGetArg { + id: id.clone().into(), + }; + session + .server + .object_store + .try_get(arg) + .await + .map_err(|error| tg::error!(!error, %id, "failed to get the object"))? + .object + .and_then(|object| object.cache_pointer) + } else { + None }; - let object = session - .server - .object_store - .try_get(arg) - .await - .map_err(|error| tg::error!(!error, %id, "failed to get the object"))?; - let cache_pointer = object.object.and_then(|object| object.cache_pointer); let reader = if let Some(cache_pointer) = cache_pointer { let mut path = session .server diff --git a/packages/server/src/vfs/fskit.rs b/packages/server/src/vfs/fskit.rs index 60c8d26957..cc7ed4fcee 100644 --- a/packages/server/src/vfs/fskit.rs +++ b/packages/server/src/vfs/fskit.rs @@ -55,6 +55,8 @@ struct Response { impl Server { pub async fn start(server: &crate::Server, path: &Path) -> tg::Result { + Self::unmount(path).await.ok(); + // Ask the app to mount if the app started this server, because only the app can prompt for the file system extension's approval. Otherwise mount directly, which is the path the test harness takes. if std::env::var_os("TANGRAM_MACOS_APP_SOCKET").is_some() { Self::start_with_app(server, path).await @@ -70,7 +72,7 @@ impl Server { let token = std::env::var("TANGRAM_MACOS_APP_TOKEN").map_err(|error| { tg::error!(!error, "missing the macos app token environment variable") })?; - let group_socket = Self::group_socket()?; + let group_socket = Self::group_socket(server)?; // Create the request. let object_store = Self::object_store(server); @@ -122,7 +124,7 @@ impl Server { } async fn start_with_mount(server: &crate::Server, path: &Path) -> tg::Result { - let group_socket = Self::group_socket()?; + let group_socket = Self::group_socket(server)?; let object_store = Self::object_store(server); let options = Self::options(object_store.as_ref(), &group_socket); let output = tokio::process::Command::new("/sbin/mount") @@ -165,10 +167,14 @@ impl Server { Ok(()) } - fn group_socket() -> tg::Result { - let group_socket = std::env::var_os("TANGRAM_MACOS_APP_GROUP_SOCKET") - .ok_or_else(|| tg::error!("missing the macos app group socket environment variable"))?; - Ok(PathBuf::from(group_socket)) + fn group_socket(server: &crate::Server) -> tg::Result { + let vfs = server.config.vfs.clone().unwrap_or_default(); + let group_socket = vfs.resolved_app_group_socket().ok_or_else(|| { + tg::error!( + "missing the macos app group socket environment variable and the vfs app group identifier is not configured" + ) + })?; + Ok(group_socket) } fn object_store(server: &crate::Server) -> Option {