From b386b6c2375dc623d123ea4acbf701af907d8856 Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sat, 31 Aug 2024 17:46:14 -0700 Subject: [PATCH] Allow Zed to run under multiple user accounts simultaneously (#14143) Closes #4607 This is an attempt to enable Zed to run under multiple user accounts on the same Mac, because it's a blocker to me really giving Zed a fair shot at being my primary editor. According to some helpful info from @ForLoveOfCats in #4607 the main reason why this doesn't work is because Zed is using a Unix socket or maybe a TCP socket with a hard-coded path and/or port. To me it looks like it's a TCP socket so I tried changing that code in here, but I'm stuck at trying to test it out because running `target/debug/zed` or `target/release/zed` seems to behave differently than running an actual app bundle. I had no luck copying the binary over to /Applications/Zed.app/Contents/MacOS/zed because it can't find WebRTC.framework which resides at a different relative path in the app bundle. If this seems like a desirable change to the core team then I'm looking for some guidance on how to build an app bundle or otherwise test out this change, or a nudge in the correct direction if I'm way off base with my current approach. Release Notes: - Added multiuser support for up to 100 users on the same machine. --------- Co-authored-by: Mikayla --- Cargo.lock | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/zed/mac_only_instance.rs | 56 +++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 949b2aa9a6..ae226ee437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14260,6 +14260,7 @@ dependencies = [ "smol", "snippet_provider", "supermaven", + "sysinfo", "tab_switcher", "task", "tasks_ui", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 0fbdbe1ca7..3f894af27c 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -113,6 +113,7 @@ vim.workspace = true welcome.workspace = true workspace.workspace = true zed_actions.workspace = true +sysinfo.workspace = true [target.'cfg(target_os = "windows")'.dependencies] windows.workspace = true diff --git a/crates/zed/src/zed/mac_only_instance.rs b/crates/zed/src/zed/mac_only_instance.rs index 27d8aa02b5..2c8f564201 100644 --- a/crates/zed/src/zed/mac_only_instance.rs +++ b/crates/zed/src/zed/mac_only_instance.rs @@ -5,22 +5,70 @@ use std::{ time::Duration, }; +use sysinfo::System; + use release_channel::ReleaseChannel; const LOCALHOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); const CONNECT_TIMEOUT: Duration = Duration::from_millis(10); const RECEIVE_TIMEOUT: Duration = Duration::from_millis(35); const SEND_TIMEOUT: Duration = Duration::from_millis(20); +const USER_BLOCK: u16 = 100; fn address() -> SocketAddr { + // These port numbers are offset by the user ID to avoid conflicts between + // different users on the same machine. In addition to that the ports for each + // release channel are spaced out by 100 to avoid conflicts between different + // users running different release channels on the same machine. This ends up + // interleaving the ports between different users and different release channels. + // + // On macOS user IDs start at 501 and on Linux they start at 1000. The first user + // on a Mac with ID 501 running a dev channel build will use port 44238, and the + // second user with ID 502 will use port 44239, and so on. User 501 will use ports + // 44338, 44438, and 44538 for the preview, stable, and nightly channels, + // respectively. User 502 will use ports 44339, 44439, and 44539 for the preview, + // stable, and nightly channels, respectively. let port = match *release_channel::RELEASE_CHANNEL { ReleaseChannel::Dev => 43737, - ReleaseChannel::Preview => 43738, - ReleaseChannel::Stable => 43739, - ReleaseChannel::Nightly => 43740, + ReleaseChannel::Preview => 43737 + USER_BLOCK, + ReleaseChannel::Stable => 43737 + (2 * USER_BLOCK), + ReleaseChannel::Nightly => 43737 + (3 * USER_BLOCK), }; + let mut user_port = port; + let mut sys = System::new_all(); + sys.refresh_all(); + if let Ok(current_pid) = sysinfo::get_current_pid() { + if let Some(uid) = sys + .process(current_pid) + .and_then(|process| process.user_id()) + { + let uid_u32 = get_uid_as_u32(uid); + // Ensure that the user ID is not too large to avoid overflow when + // calculating the port number. This seems unlikely but it doesn't + // hurt to be safe. + let max_port = 65535; + let max_uid: u32 = max_port - port as u32; + let wrapped_uid: u16 = (uid_u32 % max_uid) as u16; + user_port += wrapped_uid; + } + } - SocketAddr::V4(SocketAddrV4::new(LOCALHOST, port)) + SocketAddr::V4(SocketAddrV4::new(LOCALHOST, user_port)) +} + +#[cfg(unix)] +fn get_uid_as_u32(uid: &sysinfo::Uid) -> u32 { + *uid.clone() +} + +#[cfg(windows)] +fn get_uid_as_u32(uid: &sysinfo::Uid) -> u32 { + // Extract the RID which is an integer + uid.to_string() + .rsplit('-') + .next() + .and_then(|rid| rid.parse::().ok()) + .unwrap_or(0) } fn instance_handshake() -> &'static str {