Detailed changes
@@ -13984,6 +13984,7 @@ dependencies = [
"git2",
"globset",
"itertools 0.13.0",
+ "libc",
"log",
"rand 0.8.5",
"regex",
@@ -16045,7 +16046,6 @@ dependencies = [
"language_selector",
"language_tools",
"languages",
- "libc",
"log",
"markdown",
"markdown_preview",
@@ -18,6 +18,12 @@ use std::{
use tempfile::NamedTempFile;
use util::paths::PathWithPosition;
+#[cfg(any(target_os = "linux", target_os = "freebsd"))]
+use {
+ std::io::IsTerminal,
+ util::{load_login_shell_environment, load_shell_from_passwd, ResultExt},
+};
+
struct Detect;
trait InstalledApp {
@@ -161,7 +167,16 @@ fn main() -> Result<()> {
None
};
+ // On Linux, desktop entry uses `cli` to spawn `zed`, so we need to load env vars from the shell
+ // since it doesn't inherit env vars from the terminal.
+ #[cfg(any(target_os = "linux", target_os = "freebsd"))]
+ if !std::io::stdout().is_terminal() {
+ load_shell_from_passwd().log_err();
+ load_login_shell_environment().log_err();
+ }
+
let env = Some(std::env::vars().collect::<HashMap<_, _>>());
+
let exit_status = Arc::new(Mutex::new(None));
let mut paths = vec![];
let mut urls = vec![];
@@ -36,6 +36,9 @@ take-until = "0.2.0"
tempfile = { workspace = true, optional = true }
unicase.workspace = true
+[target.'cfg(unix)'.dependencies]
+libc.workspace = true
+
[target.'cfg(windows)'.dependencies]
tendril = "0.4.3"
dunce = "1.0"
@@ -6,6 +6,7 @@ pub mod serde;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
+use anyhow::{anyhow, Context as _, Result};
use futures::Future;
use itertools::Either;
@@ -110,6 +111,106 @@ where
}
}
+#[cfg(unix)]
+pub fn load_shell_from_passwd() -> Result<()> {
+ let buflen = match unsafe { libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) } {
+ n if n < 0 => 1024,
+ n => n as usize,
+ };
+ let mut buffer = Vec::with_capacity(buflen);
+
+ let mut pwd: std::mem::MaybeUninit<libc::passwd> = std::mem::MaybeUninit::uninit();
+ let mut result: *mut libc::passwd = std::ptr::null_mut();
+
+ let uid = unsafe { libc::getuid() };
+ let status = unsafe {
+ libc::getpwuid_r(
+ uid,
+ pwd.as_mut_ptr(),
+ buffer.as_mut_ptr() as *mut libc::c_char,
+ buflen,
+ &mut result,
+ )
+ };
+ let entry = unsafe { pwd.assume_init() };
+
+ anyhow::ensure!(
+ status == 0,
+ "call to getpwuid_r failed. uid: {}, status: {}",
+ uid,
+ status
+ );
+ anyhow::ensure!(!result.is_null(), "passwd entry for uid {} not found", uid);
+ anyhow::ensure!(
+ entry.pw_uid == uid,
+ "passwd entry has different uid ({}) than getuid ({}) returned",
+ entry.pw_uid,
+ uid,
+ );
+
+ let shell = unsafe { std::ffi::CStr::from_ptr(entry.pw_shell).to_str().unwrap() };
+ if env::var("SHELL").map_or(true, |shell_env| shell_env != shell) {
+ log::info!(
+ "updating SHELL environment variable to value from passwd entry: {:?}",
+ shell,
+ );
+ env::set_var("SHELL", shell);
+ }
+
+ Ok(())
+}
+
+pub fn load_login_shell_environment() -> Result<()> {
+ let marker = "ZED_LOGIN_SHELL_START";
+ let shell = env::var("SHELL").context(
+ "SHELL environment variable is not assigned so we can't source login environment variables",
+ )?;
+
+ // If possible, we want to `cd` in the user's `$HOME` to trigger programs
+ // such as direnv, asdf, mise, ... to adjust the PATH. These tools often hook
+ // into shell's `cd` command (and hooks) to manipulate env.
+ // We do this so that we get the env a user would have when spawning a shell
+ // in home directory.
+ let shell_cmd_prefix = std::env::var_os("HOME")
+ .and_then(|home| home.into_string().ok())
+ .map(|home| format!("cd '{home}';"));
+
+ // The `exit 0` is the result of hours of debugging, trying to find out
+ // why running this command here, without `exit 0`, would mess
+ // up signal process for our process so that `ctrl-c` doesn't work
+ // anymore.
+ // We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'` would
+ // do that, but it does, and `exit 0` helps.
+ let shell_cmd = format!(
+ "{}printf '%s' {marker}; /usr/bin/env; exit 0;",
+ shell_cmd_prefix.as_deref().unwrap_or("")
+ );
+
+ let output = std::process::Command::new(&shell)
+ .args(["-l", "-i", "-c", &shell_cmd])
+ .output()
+ .context("failed to spawn login shell to source login environment variables")?;
+ if !output.status.success() {
+ Err(anyhow!("login shell exited with error"))?;
+ }
+
+ let stdout = String::from_utf8_lossy(&output.stdout);
+
+ if let Some(env_output_start) = stdout.find(marker) {
+ let env_output = &stdout[env_output_start + marker.len()..];
+
+ parse_env_output(env_output, |key, value| env::set_var(key, value));
+
+ log::info!(
+ "set environment variables from shell:{}, path:{}",
+ shell,
+ env::var("PATH").unwrap_or_default(),
+ );
+ }
+
+ Ok(())
+}
+
/// Parse the result of calling `usr/bin/env` with no arguments
pub fn parse_env_output(env: &str, mut f: impl FnMut(String, String)) {
let mut current_key: Option<String> = None;
@@ -68,7 +68,6 @@ language_models.workspace = true
language_selector.workspace = true
language_tools.workspace = true
languages = { workspace = true, features = ["load-grammars"] }
-libc.workspace = true
log.workspace = true
markdown.workspace = true
markdown_preview.workspace = true
@@ -37,7 +37,6 @@ use settings::{
handle_settings_file_changes, watch_config_file, InvalidSettingsError, Settings, SettingsStore,
};
use simplelog::ConfigBuilder;
-use smol::process::Command;
use std::{
env,
fs::OpenOptions,
@@ -48,7 +47,7 @@ use std::{
};
use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
use time::UtcOffset;
-use util::{maybe, parse_env_output, ResultExt, TryFutureExt};
+use util::{load_login_shell_environment, maybe, ResultExt, TryFutureExt};
use uuid::Uuid;
use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN};
use workspace::{
@@ -63,6 +62,9 @@ use zed::{
use crate::zed::inline_completion_registry;
+#[cfg(unix)]
+use util::load_shell_from_passwd;
+
#[cfg(feature = "mimalloc")]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
@@ -219,9 +221,9 @@ fn main() {
.spawn(async {
#[cfg(unix)]
{
- load_shell_from_passwd().await.log_err();
+ load_shell_from_passwd().log_err();
}
- load_login_shell_environment().await.log_err();
+ load_login_shell_environment().log_err();
})
.detach()
};
@@ -998,107 +1000,6 @@ fn init_stdout_logger() {
.init();
}
-#[cfg(unix)]
-async fn load_shell_from_passwd() -> Result<()> {
- let buflen = match unsafe { libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) } {
- n if n < 0 => 1024,
- n => n as usize,
- };
- let mut buffer = Vec::with_capacity(buflen);
-
- let mut pwd: std::mem::MaybeUninit<libc::passwd> = std::mem::MaybeUninit::uninit();
- let mut result: *mut libc::passwd = std::ptr::null_mut();
-
- let uid = unsafe { libc::getuid() };
- let status = unsafe {
- libc::getpwuid_r(
- uid,
- pwd.as_mut_ptr(),
- buffer.as_mut_ptr() as *mut libc::c_char,
- buflen,
- &mut result,
- )
- };
- let entry = unsafe { pwd.assume_init() };
-
- anyhow::ensure!(
- status == 0,
- "call to getpwuid_r failed. uid: {}, status: {}",
- uid,
- status
- );
- anyhow::ensure!(!result.is_null(), "passwd entry for uid {} not found", uid);
- anyhow::ensure!(
- entry.pw_uid == uid,
- "passwd entry has different uid ({}) than getuid ({}) returned",
- entry.pw_uid,
- uid,
- );
-
- let shell = unsafe { std::ffi::CStr::from_ptr(entry.pw_shell).to_str().unwrap() };
- if env::var("SHELL").map_or(true, |shell_env| shell_env != shell) {
- log::info!(
- "updating SHELL environment variable to value from passwd entry: {:?}",
- shell,
- );
- env::set_var("SHELL", shell);
- }
-
- Ok(())
-}
-
-async fn load_login_shell_environment() -> Result<()> {
- let marker = "ZED_LOGIN_SHELL_START";
- let shell = env::var("SHELL").context(
- "SHELL environment variable is not assigned so we can't source login environment variables",
- )?;
-
- // If possible, we want to `cd` in the user's `$HOME` to trigger programs
- // such as direnv, asdf, mise, ... to adjust the PATH. These tools often hook
- // into shell's `cd` command (and hooks) to manipulate env.
- // We do this so that we get the env a user would have when spawning a shell
- // in home directory.
- let shell_cmd_prefix = std::env::var_os("HOME")
- .and_then(|home| home.into_string().ok())
- .map(|home| format!("cd '{home}';"));
-
- // The `exit 0` is the result of hours of debugging, trying to find out
- // why running this command here, without `exit 0`, would mess
- // up signal process for our process so that `ctrl-c` doesn't work
- // anymore.
- // We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'` would
- // do that, but it does, and `exit 0` helps.
- let shell_cmd = format!(
- "{}printf '%s' {marker}; /usr/bin/env; exit 0;",
- shell_cmd_prefix.as_deref().unwrap_or("")
- );
-
- let output = Command::new(&shell)
- .args(["-l", "-i", "-c", &shell_cmd])
- .output()
- .await
- .context("failed to spawn login shell to source login environment variables")?;
- if !output.status.success() {
- Err(anyhow!("login shell exited with error"))?;
- }
-
- let stdout = String::from_utf8_lossy(&output.stdout);
-
- if let Some(env_output_start) = stdout.find(marker) {
- let env_output = &stdout[env_output_start + marker.len()..];
-
- parse_env_output(env_output, |key, value| env::set_var(key, value));
-
- log::info!(
- "set environment variables from shell:{}, path:{}",
- shell,
- env::var("PATH").unwrap_or_default(),
- );
- }
-
- Ok(())
-}
-
fn stdout_is_a_pty() -> bool {
std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && std::io::stdout().is_terminal()
}