use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result};
pub(crate) fn current_process_group_id() -> Option<u32> {
let process_group = unsafe { libc::getpgrp() };
u32::try_from(process_group).ok().filter(|id| *id > 1)
}
pub(crate) fn resolve_lf_binary() -> PathBuf {
if let Some(path) = select_binary_override(
crate::build_info::provenance(),
std::env::var_os(crate::store::CONTROL_BIN_ENV),
std::env::var_os("LF_BIN"),
) {
return path;
}
if let Ok(path) = std::env::var("CARGO_BIN_EXE_lf") {
let trimmed = path.trim();
if !trimmed.is_empty() {
return PathBuf::from(trimmed);
}
}
if let Ok(current) = std::env::current_exe() {
if current
.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| name == "lf")
{
return current;
}
if let Some(parent) = current.parent() {
let sibling = parent.join("lf");
if sibling.exists() {
return sibling;
}
}
}
PathBuf::from("lf")
}
fn select_binary_override(
provenance: crate::build_info::BuildProvenance,
control: Option<std::ffi::OsString>,
ordinary: Option<std::ffi::OsString>,
) -> Option<PathBuf> {
let selected = if provenance.is_release() {
control.or(ordinary)
} else {
ordinary
}?;
if selected.is_empty() {
None
} else {
Some(PathBuf::from(selected))
}
}
fn select_current_home_binary(ordinary: Option<std::ffi::OsString>) -> Option<PathBuf> {
ordinary
.filter(|value| !value.is_empty())
.map(PathBuf::from)
}
pub(crate) fn resolve_pinned_lf_binary() -> Result<PathBuf> {
let candidate = resolve_lf_binary();
if candidate.is_absolute() {
return if candidate.exists() {
Ok(candidate)
} else {
Err(anyhow!(
"lf binary {} does not exist; set LF_BIN to the lf this Session should run",
candidate.display()
))
};
}
which_on_path(&candidate).ok_or_else(|| {
anyhow!(
"cannot resolve an absolute path for `{}`; set LF_BIN to the lf this Session should run",
candidate.display()
)
})
}
pub(crate) fn pin_control_binary(lf_bin: &Path) -> PathBuf {
std::fs::canonicalize(lf_bin).unwrap_or_else(|_| lf_bin.to_path_buf())
}
pub(crate) fn pinned_execution_context() -> Result<crate::child::ChildExecutionContext> {
let db_path = crate::store::database_path_from_env()
.map_err(|error| anyhow!("cannot resolve the Run database path: {error}"))?;
Ok(crate::child::ChildExecutionContext {
lf_bin: resolve_pinned_lf_binary()?,
db_path,
lf_home: crate::store::lf_home_dir(),
})
}
fn resolve_current_home_lf_binary() -> PathBuf {
if let Some(bin) = select_current_home_binary(std::env::var_os("LF_BIN")) {
return bin;
}
if let Ok(path) = std::env::var("CARGO_BIN_EXE_lf") {
let trimmed = path.trim();
if !trimmed.is_empty() {
return PathBuf::from(trimmed);
}
}
if let Some(installed) = which_on_path(Path::new("lf")) {
return installed;
}
if let Ok(current) = std::env::current_exe() {
if current
.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| name == "lf")
{
return current;
}
if let Some(parent) = current.parent() {
let sibling = parent.join("lf");
if sibling.exists() {
return sibling;
}
}
}
PathBuf::from("lf")
}
fn resolve_current_home_lf_binary_checked() -> Result<PathBuf> {
let candidate = resolve_current_home_lf_binary();
if candidate.is_absolute() {
return if candidate.exists() {
Ok(candidate)
} else {
Err(anyhow!(
"lf binary {} does not exist; set LF_BIN to the current Home lf",
candidate.display()
))
};
}
which_on_path(&candidate).ok_or_else(|| {
anyhow!(
"cannot resolve an absolute path for `{}`; set LF_BIN to the current Home lf",
candidate.display()
)
})
}
pub(crate) fn current_home_execution_context() -> Result<crate::child::ChildExecutionContext> {
let db_path = crate::store::current_home_database_path()
.map_err(|error| anyhow!("cannot resolve the current Home database path: {error}"))?;
Ok(crate::child::ChildExecutionContext {
lf_bin: resolve_current_home_lf_binary_checked()?,
db_path,
lf_home: crate::store::current_home_lf_home_dir(),
})
}
fn which_on_path(name: &Path) -> Option<PathBuf> {
let path = std::env::var_os("PATH")?;
std::env::split_paths(&path)
.map(|dir| dir.join(name))
.find(|candidate| candidate.is_file())
}
pub(crate) fn shell_escape(value: &str) -> String {
let escaped = value.replace('\'', "'\\''");
format!("'{escaped}'")
}
pub(crate) fn tmux_installed() -> bool {
which_on_path(Path::new("tmux")).is_some()
}
pub(crate) async fn tmux_session_exists(session_name: &str) -> Result<bool> {
let status = tokio::process::Command::new("tmux")
.args(["has-session", "-t", session_name])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.await
.map_err(|err| anyhow!("tmux session probe failed: {err}"))?;
Ok(status.success())
}
pub(crate) async fn tmux_live_sessions() -> Result<std::collections::HashSet<String>> {
let output = tokio::process::Command::new("tmux")
.args(["list-sessions", "-F", "#{session_name}"])
.stderr(std::process::Stdio::null())
.output()
.await
.map_err(|err| anyhow!("tmux session list failed: {err}"))?;
if !output.status.success() {
return Ok(std::collections::HashSet::new());
}
Ok(String::from_utf8_lossy(&output.stdout)
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.map(str::to_string)
.collect())
}
pub(crate) async fn start_lf_session(session: &str, cwd: &Path, argv: &[String]) -> Result<()> {
start_lf_session_with_env(session, cwd, argv, &[]).await
}
pub(crate) async fn start_lf_session_with_env(
session: &str,
cwd: &Path,
argv: &[String],
env: &[(&str, &str)],
) -> Result<()> {
reject_detached_forwarded_account(crate::provider_account::lease::account_lease_active())?;
let context = pinned_execution_context()?;
let inherited_context = ["LF_TRACE_ID", "LF_PROCESS_ID"]
.into_iter()
.filter(|key| !env.iter().any(|(explicit, _)| explicit == key))
.filter_map(|key| std::env::var(key).ok().map(|value| (key, value)))
.collect::<Vec<_>>();
let mut child_env = env
.iter()
.map(|(key, value)| ((*key).to_string(), (*value).to_string()))
.collect::<Vec<_>>();
child_env.extend(
inherited_context
.iter()
.map(|(key, value)| ((*key).to_string(), value.clone())),
);
extend_session_control_context(&mut child_env, &context, crate::build_info::provenance());
let environment = child_env
.iter()
.map(|(key, value)| (key.as_str(), value.as_str()))
.collect::<Vec<_>>();
let shell_command = lf_session_shell_command(argv, &environment);
start_tmux_session(session, &cwd.display().to_string(), &shell_command).await
}
fn reject_detached_forwarded_account(forwarded: bool) -> Result<()> {
if forwarded {
return Err(anyhow!(
"cannot launch a detached session from an ephemeral forwarded provider account; \
keep the remote command in the foreground or authenticate on the remote host"
));
}
Ok(())
}
fn extend_session_control_context(
child_env: &mut Vec<(String, String)>,
context: &crate::child::ChildExecutionContext,
provenance: crate::build_info::BuildProvenance,
) {
let pinned = [
(
crate::store::CONTROL_BIN_ENV,
context.lf_bin.to_string_lossy().to_string(),
),
(
crate::store::CONTROL_HOME_ENV,
context.lf_home.to_string_lossy().to_string(),
),
(
crate::store::CONTROL_DB_PATH_ENV,
context.db_path.to_string_lossy().to_string(),
),
];
for (key, value) in pinned {
if !child_env.iter().any(|(existing, _)| existing == key) {
child_env.push((key.to_string(), value));
}
}
if !provenance.is_release() {
for (ordinary, control) in [
("LF_HOME", crate::store::CONTROL_HOME_ENV),
("LF_DB_PATH", crate::store::CONTROL_DB_PATH_ENV),
] {
if child_env.iter().any(|(existing, _)| existing == ordinary) {
continue;
}
let value = child_env
.iter()
.find(|(key, _)| key == control)
.map(|(_, value)| value.clone());
if let Some(value) = value {
child_env.push((ordinary.to_string(), value));
}
}
}
}
pub(crate) fn lf_session_shell_command(argv: &[String], env: &[(&str, &str)]) -> String {
let command = argv
.iter()
.map(|arg| shell_escape(arg))
.collect::<Vec<_>>()
.join(" ");
let env = env
.iter()
.map(|(key, value)| format!("{}={}", shell_escape(key), shell_escape(value)))
.collect::<Vec<_>>()
.join(" ");
let clear_context = "unset LF_TRACE_ID LF_PROCESS_ID LF_WAVE_ID LF_CHANNEL LF_RUN_CONTEXT LF_RUN_LEASE LF_BIN LF_HOME LF_DB_PATH LF_CONTROL_BIN LF_CONTROL_HOME LF_CONTROL_DB_PATH";
if env.is_empty() {
format!("{clear_context}; exec {command}")
} else {
format!("{clear_context}; exec env {env} {command}")
}
}
pub(crate) async fn start_tmux_session(
session: &str,
cwd: &str,
shell_command: &str,
) -> Result<()> {
let status = tokio::process::Command::new("tmux")
.args([
"new-session",
"-d",
"-s",
session,
"-c",
cwd,
"/bin/zsh",
"-lc",
shell_command,
])
.status()
.await
.map_err(|err| anyhow!("tmux failed to spawn: {err}"))?;
if !status.success() {
return Err(anyhow!("tmux failed to launch session '{session}'"));
}
let _ = tokio::process::Command::new("tmux")
.args(["set-option", "-t", session, "mouse", "on"])
.status()
.await;
Ok(())
}
pub(crate) fn tmux_session_slug(value: &str) -> String {
value
.chars()
.map(|ch| {
if ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_') {
ch
} else {
'-'
}
})
.collect()
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::{
extend_session_control_context, lf_session_shell_command, pin_control_binary,
reject_detached_forwarded_account, select_binary_override, select_current_home_binary,
tmux_installed,
};
use crate::build_info::BuildProvenance;
use crate::child::ChildExecutionContext;
#[test]
fn a_body_generation_keeps_one_binary_across_a_global_repoint() {
let dir = tempfile::tempdir().unwrap();
let old = dir.path().join("lf-old");
let new = dir.path().join("lf-new");
let installed = dir.path().join("lf");
std::fs::write(&old, b"old").unwrap();
std::fs::write(&new, b"new").unwrap();
std::os::unix::fs::symlink(&old, &installed).unwrap();
let pinned = pin_control_binary(&installed);
assert_eq!(pinned, std::fs::canonicalize(&old).unwrap());
std::fs::remove_file(&installed).unwrap();
std::os::unix::fs::symlink(&new, &installed).unwrap();
assert_eq!(std::fs::read(&pinned).unwrap(), b"old");
assert_eq!(
std::fs::read(std::fs::canonicalize(&installed).unwrap()).unwrap(),
b"new"
);
}
#[test]
fn development_ignores_stale_control_binary_override() {
assert_eq!(
select_binary_override(
BuildProvenance::Development,
Some("/production/lf".into()),
Some("/development/lf".into()),
),
Some(PathBuf::from("/development/lf"))
);
assert_eq!(
select_binary_override(
BuildProvenance::Release,
Some("/production/lf".into()),
Some("/ambient/lf".into()),
),
Some(PathBuf::from("/production/lf"))
);
}
#[test]
fn current_home_binary_never_resolves_through_the_control_pin() {
assert_eq!(
select_binary_override(
BuildProvenance::Release,
Some("/old/A/lf".into()),
Some("/current/B/lf".into()),
),
Some(PathBuf::from("/old/A/lf")),
);
assert_eq!(
select_current_home_binary(Some("/current/B/lf".into())),
Some(PathBuf::from("/current/B/lf")),
);
assert_eq!(select_current_home_binary(None), None);
assert_eq!(select_current_home_binary(Some("".into())), None);
}
#[test]
fn persisted_control_binary_wins_over_relaunching_callers_binary() {
let mut environment = vec![(
crate::store::CONTROL_BIN_ENV.to_string(),
"/persisted/lf".to_string(),
)];
let caller = ChildExecutionContext {
lf_bin: PathBuf::from("/caller/lf"),
lf_home: PathBuf::from("/caller/home"),
db_path: PathBuf::from("/caller/loopflow.db"),
};
extend_session_control_context(&mut environment, &caller, BuildProvenance::Release);
assert!(environment.iter().any(|(key, value)| {
key == crate::store::CONTROL_BIN_ENV && value == "/persisted/lf"
}));
assert!(!environment
.iter()
.any(|(key, value)| { key == crate::store::CONTROL_BIN_ENV && value == "/caller/lf" }));
}
#[test]
fn tmux_probe_agrees_with_running_tmux() {
let _env_lock = crate::journal::test_env_lock();
let runnable = std::process::Command::new("tmux")
.arg("-V")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.map(|status| status.success())
.unwrap_or(false);
assert_eq!(tmux_installed(), runnable);
}
#[test]
fn lf_session_clears_parent_identity_and_exports_its_own() {
let argv = vec![
"lf".to_string(),
"__work".to_string(),
"task".to_string(),
"tsk_123".to_string(),
];
let command = lf_session_shell_command(
&argv,
&[("LF_RUN_CONTEXT", "agent"), ("LF_WAVE_ID", "infra")],
);
assert_eq!(
command,
"unset LF_TRACE_ID LF_PROCESS_ID LF_WAVE_ID LF_CHANNEL LF_RUN_CONTEXT LF_RUN_LEASE LF_BIN LF_HOME LF_DB_PATH LF_CONTROL_BIN LF_CONTROL_HOME LF_CONTROL_DB_PATH; exec env 'LF_RUN_CONTEXT'='agent' 'LF_WAVE_ID'='infra' 'lf' '__work' 'task' 'tsk_123'"
);
}
#[test]
fn lf_session_without_explicit_identity_does_not_inherit_its_parent() {
let argv = vec!["lf".to_string(), "wave".to_string(), "child".to_string()];
let command = lf_session_shell_command(&argv, &[]);
assert_eq!(
command,
"unset LF_TRACE_ID LF_PROCESS_ID LF_WAVE_ID LF_CHANNEL LF_RUN_CONTEXT LF_RUN_LEASE LF_BIN LF_HOME LF_DB_PATH LF_CONTROL_BIN LF_CONTROL_HOME LF_CONTROL_DB_PATH; exec 'lf' 'wave' 'child'"
);
}
#[test]
fn lf_session_replaces_tmux_invocation_context() {
let argv = vec![
"lf".to_string(),
"__work".to_string(),
"task".to_string(),
"tsk_123".to_string(),
];
let command = lf_session_shell_command(
&argv,
&[
("LF_TRACE_ID", "run-1"),
("LF_PROCESS_ID", "process-1"),
("LF_DB_PATH", "/tmp/current.db"),
("LF_HOME", "/tmp/lf"),
],
);
assert_eq!(
command,
"unset LF_TRACE_ID LF_PROCESS_ID LF_WAVE_ID LF_CHANNEL LF_RUN_CONTEXT LF_RUN_LEASE LF_BIN LF_HOME LF_DB_PATH LF_CONTROL_BIN LF_CONTROL_HOME LF_CONTROL_DB_PATH; exec env 'LF_TRACE_ID'='run-1' 'LF_PROCESS_ID'='process-1' 'LF_DB_PATH'='/tmp/current.db' 'LF_HOME'='/tmp/lf' 'lf' '__work' 'task' 'tsk_123'"
);
}
#[test]
fn detached_session_rejects_an_ephemeral_forwarded_account() {
assert!(reject_detached_forwarded_account(false).is_ok());
assert!(reject_detached_forwarded_account(true)
.unwrap_err()
.to_string()
.contains("cannot launch a detached session"));
}
}