#![cfg(feature = "daemon")]
use std::path::PathBuf;
use crate::daemon::paths::CachePaths;
use crate::daemon::recorder_shard::{
TypedParam, ARGV_SEP, AUTOLOAD_PATHS_EXTRA, MATH_FUNCTIONS_EXTRA, MODULES_END_EXTRA,
BINDKEYS_EXTRA, PARAMS_END_EXTRA, PID_PLACEHOLDER, RESOURCE_FILES_EXTRA, TTY_PLACEHOLDER,
UNSET_PARAMS_EXTRA, WIDGETS_EXTRA, ZSTYLES_EXTRA,
};
use crate::daemon::shard::{list_shards, read_canonical_shard, CanonicalShard};
use crate::vm_helper::{zstyle_entry, AutoloadFlags, ShellExecutor};
pub fn replay_startup() -> bool {
crate::fusevm_bridge::try_with_executor(replay_startup_into).unwrap_or(false)
}
pub fn replay_startup_into(executor: &mut ShellExecutor) -> bool {
if !crate::daemon_presence::should_skip_configs() {
return false;
}
let _ctx = crate::fusevm_bridge::ExecutorContext::enter(executor);
let rows = apply_latest(executor, true);
tracing::info!(rows, "skip_configs: startup files bypassed, recorder shard replayed");
rows > 0
}
pub fn apply_all(executor: &mut ShellExecutor) -> usize {
apply_latest(executor, false)
}
fn apply_latest(executor: &mut ShellExecutor, startup: bool) -> usize {
let t0 = std::time::Instant::now();
let paths = match CachePaths::resolve() {
Ok(p) => p,
Err(e) => {
tracing::warn!(error = %e, "canonical_apply: cache paths unresolved");
return 0;
}
};
let shard_path = match latest_recorder_shard(&paths) {
Some(p) => p,
None => {
tracing::info!(
"canonical_apply: no recorder shard found in {} — vanilla fallback",
paths.images.display()
);
return 0;
}
};
let shard = match read_canonical_shard(&shard_path) {
Ok(s) => s,
Err(e) => {
tracing::warn!(error = %e, path = %shard_path.display(), "canonical_apply: shard read failed");
return 0;
}
};
crate::startup_trace::mark("replay: shard read");
let sourced = if startup { shard.sourced_files.clone() } else { Vec::new() };
let resources = if startup { argv_rows(&shard.extras, RESOURCE_FILES_EXTRA) } else { Vec::new() };
let total = apply_shard(executor, shard);
for path in &sourced {
let _ = crate::p10k::maybe_intercept_theme_source(std::slice::from_ref(path));
}
crate::startup_trace::mark("replay: p10k");
for argv in resources {
crate::fusevm_bridge::dispatch_builtin_raw("source", argv);
}
crate::startup_trace::mark("replay: resource files");
let elapsed_us = t0.elapsed().as_micros();
tracing::info!(
rows = total,
elapsed_us,
path = %shard_path.display(),
"canonical state applied from rkyv shard (no IPC)"
);
total
}
fn latest_recorder_shard(paths: &CachePaths) -> Option<PathBuf> {
let entries = list_shards(paths).ok()?;
entries
.into_iter()
.filter(|p| {
p.file_name()
.and_then(|s| s.to_str())
.map(|s| s.ends_with("-recorder.rkyv"))
.unwrap_or(false)
})
.max_by_key(|p| std::fs::metadata(p).and_then(|m| m.modified()).ok())
}
fn apply_shard(executor: &mut ShellExecutor, shard: CanonicalShard) -> usize {
let mut total = 0;
if let Some(modules) = shard.extras.get(MODULES_END_EXTRA) {
for (module, features) in modules {
let argv = if features.is_empty() {
vec![module.clone()]
} else {
let mut argv = vec!["-F".to_string(), module.clone()];
argv.extend(features.split(' ').map(str::to_string));
argv
};
crate::fusevm_bridge::dispatch_builtin_raw("zmodload", argv);
total += 1;
}
}
crate::startup_trace::mark("replay: modules");
for opt in shard.setopts {
crate::ported::options::opt_state_set(&opt, true);
total += 1;
}
for opt in shard.unsetopts {
crate::ported::options::opt_state_set(&opt, false);
total += 1;
}
crate::startup_trace::mark("replay: options");
{
let mut traps = Vec::new();
if let Ok(mut tab) = crate::ported::hashtable::shfunctab_lock().write() {
for (name, body) in shard.functions {
if name.starts_with("TRAP") {
traps.push((name, body));
} else {
tab.add(crate::ported::hashtable::shfunc_with_body(&name, &body));
}
total += 1;
}
}
for (name, body) in traps {
crate::ported::modules::parameter::setfunction(&name, body, 0);
}
}
crate::startup_trace::mark("replay: functions");
for (n, v) in shard.aliases {
executor.set_alias(n, v);
total += 1;
}
for (n, v) in shard.global_aliases {
executor.set_global_alias(n, v);
total += 1;
}
for (n, v) in shard.suffix_aliases {
executor.set_suffix_alias(n, v);
total += 1;
}
crate::startup_trace::mark("replay: aliases");
if let Some(typed) = shard.extras.get(PARAMS_END_EXTRA) {
if let Some(unsets) = shard.extras.get(UNSET_PARAMS_EXTRA) {
for name in unsets.keys() {
crate::ported::params::unsetparam(name);
total += 1;
}
}
let tty = crate::ported::params::getsparam("TTY").unwrap_or_default();
let pid = crate::ported::params::mypid.load(std::sync::atomic::Ordering::Relaxed).to_string();
let params: Vec<TypedParam> = typed
.values()
.filter_map(|json| serde_json::from_str::<TypedParam>(json).ok())
.map(|mut p| {
for v in p.value.iter_mut().chain(p.elements.iter_mut().flatten()) {
if v.contains('\0') {
*v = v.replace(TTY_PLACEHOLDER, &tty).replace(PID_PLACEHOLDER, &pid);
}
}
p
})
.collect();
for p in ¶ms {
if let Some((array, sep)) = &p.tie {
let argv = vec!["-gT".to_string(), p.name.clone(), array.clone(), sep.clone()];
crate::fusevm_bridge::dispatch_builtin_raw("typeset", argv);
}
}
for p in params {
apply_typed_param(executor, p);
total += 1;
}
} else {
for (n, v) in shard.env_exports {
std::env::set_var(&n, &v);
executor.set_scalar(n, v);
total += 1;
}
for (n, v) in shard.params {
executor.set_scalar(n, v);
total += 1;
}
}
if shard.extras.contains_key(PARAMS_END_EXTRA) {
executor.fpath = crate::ported::params::getaparam("fpath")
.unwrap_or_default()
.into_iter()
.map(PathBuf::from)
.collect();
} else {
if !shard.path.is_empty() {
let joined = shard.path.join(":");
std::env::set_var("PATH", &joined);
executor.set_scalar("PATH".to_string(), joined);
total += shard.path.len();
executor.set_array("path".to_string(), shard.path);
}
if !shard.fpath.is_empty() {
let joined = shard.fpath.join(":");
std::env::set_var("FPATH", &joined);
executor.set_scalar("FPATH".to_string(), joined);
total += shard.fpath.len();
executor.fpath = shard.fpath.iter().map(PathBuf::from).collect();
executor.set_array("fpath".to_string(), shard.fpath);
}
}
crate::startup_trace::mark("replay: parameters");
for (name, path) in shard.named_dirs {
crate::fusevm_bridge::dispatch_builtin_raw("hash", vec!["-d".to_string(), format!("{name}={path}")]);
total += 1;
}
crate::startup_trace::mark("replay: named dirs");
let _ = AutoloadFlags::NO_ALIAS;
total +=
crate::compsys::ported::compinit::register_autoload_stubs(shard.autoload_functions.keys());
crate::startup_trace::mark("replay: autoload stubs");
for (extra, builtin) in [
(AUTOLOAD_PATHS_EXTRA, "autoload"),
(MATH_FUNCTIONS_EXTRA, "functions"),
(WIDGETS_EXTRA, "zle"),
(BINDKEYS_EXTRA, "bindkey"),
] {
for argv in argv_rows(&shard.extras, extra) {
crate::fusevm_bridge::dispatch_builtin_raw(builtin, argv);
total += 1;
}
}
crate::startup_trace::mark("replay: autoload dirs, math, widgets, bindkey");
for words in argv_rows(&shard.extras, ZSTYLES_EXTRA) {
let mut words = words.into_iter();
let (Some(pat), Some(style), Some(eval)) = (words.next(), words.next(), words.next()) else {
continue;
};
let vals: Vec<String> = words.collect();
crate::ported::modules::zutil::setstypat(&style, &pat, None, vals, (eval == "e") as i32);
total += 1;
}
for (pattern, rest) in shard.zstyle {
let mut parts = rest.split_whitespace();
let style = match parts.next() {
Some(s) => s.to_string(),
None => continue,
};
let values: Vec<String> = parts.map(str::to_string).collect();
executor.zstyles.push(zstyle_entry {
pattern,
style,
values,
});
total += 1;
}
{
for (keyseq, value) in shard.bindkeys {
let (keymap, widget) = parse_bindkey_value(&value);
crate::ported::zle::zle_bindings::bindkey_by_name(keymap, &keyseq, widget);
total += 1;
}
}
crate::startup_trace::mark("replay: zstyle");
if !shard.compdef.is_empty() {
for (function, cmds_joined) in shard.compdef {
let mut args: Vec<String> = Vec::with_capacity(8);
args.push(function);
for cmd in cmds_joined.split_whitespace() {
args.push(cmd.to_string());
}
if args.len() < 2 {
continue; }
let _rc = crate::compsys::ported::compinit::compdef(&args);
total += 1;
}
}
if let Some(zle_widgets) = shard.extras.get("zle") {
for (name, body) in zle_widgets {
let w =
std::sync::Arc::new(crate::ported::zle::zle_h::widget::user_defined(name, body));
crate::ported::zle::zle_thingy::rthingy(name);
crate::ported::zle::zle_thingy::bindwidget(w, name);
total += 1;
}
}
let _ = shard.zmodload;
let _ = shard.manpath;
let _ = shard.plugins;
let _ = shard.extras;
crate::startup_trace::mark("replay: compdef");
total
}
fn argv_rows(
extras: &std::collections::HashMap<String, std::collections::HashMap<String, String>>,
extra: &str,
) -> Vec<Vec<String>> {
let Some(rows) = extras.get(extra) else {
return Vec::new();
};
let mut keys: Vec<&String> = rows.keys().collect();
keys.sort();
keys.into_iter()
.map(|k| rows[k].split(ARGV_SEP).map(str::to_string).collect())
.collect()
}
fn apply_typed_param(executor: &mut ShellExecutor, p: TypedParam) {
use crate::ported::params::{addenv, getsparam, sethparam, setiparam, setnparam};
use crate::ported::zsh_h::{mnumber, MN_FLOAT};
let TypedParam {
name,
kind,
export,
value,
elements,
attrs,
tie: _,
} = p;
let existing = crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| t.get(&name).map(|pm| crate::ported::modules::parameter::paramtypestr(pm)));
if let Some(ty) = existing {
let parts: Vec<&str> = ty.split('-').collect();
if parts[0] != kind && !parts.contains(&"special") {
crate::ported::params::unsetparam(&name);
}
}
let value = value.unwrap_or_default();
match kind.as_str() {
"array" => executor.set_array(name.clone(), elements.unwrap_or_default()),
"association" => {
let _ = sethparam(&name, elements.unwrap_or_default());
}
"integer" => match value.parse::<i64>() {
Ok(n) => {
let _ = setiparam(&name, n);
}
Err(_) => executor.set_scalar(name.clone(), value),
},
"float" => match value.parse::<f64>() {
Ok(d) => {
let _ = setnparam(&name, mnumber { l: 0, d, type_: MN_FLOAT });
}
Err(_) => executor.set_scalar(name.clone(), value),
},
_ => executor.set_scalar(name.clone(), value),
}
let exported_now = crate::ported::params::paramtab()
.read()
.ok()
.and_then(|t| t.get(&name).map(|pm| pm.node.flags as u32 & crate::ported::zsh_h::PM_EXPORTED != 0))
.unwrap_or(false);
if export && !exported_now {
if matches!(kind.as_str(), "array" | "association") {
crate::fusevm_bridge::dispatch_builtin_raw("typeset", vec!["-gx".to_string(), name.clone()]);
} else {
let _ = addenv(&name, &getsparam(&name).unwrap_or_default());
}
} else if !export && exported_now {
crate::fusevm_bridge::dispatch_builtin_raw("typeset", vec!["-g".to_string(), "+x".to_string(), name.clone()]);
}
if !attrs.is_empty() {
crate::fusevm_bridge::dispatch_builtin_raw("typeset", vec![format!("-g{attrs}"), name]);
}
}
fn parse_bindkey_value(value: &str) -> (&str, &str) {
if let Some(rest) = value.strip_prefix('[') {
if let Some(close_idx) = rest.find(']') {
let keymap_str = &rest[..close_idx];
let widget = rest[close_idx + 1..].trim_start();
return (keymap_str, widget);
}
}
("main", value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bindkey_value_parses_main_keymap_default() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("history-search-backward");
assert_eq!(km, "main");
assert_eq!(w, "history-search-backward");
}
#[test]
fn bindkey_value_parses_explicit_keymap() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("[viins] backward-delete-char");
assert_eq!(km, "viins");
assert_eq!(w, "backward-delete-char");
}
#[test]
fn bindkey_value_unknown_keymap_falls_back_to_main() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("[totally-not-real] do-thing");
assert_eq!(km, "totally-not-real");
assert_eq!(w, "do-thing");
}
#[test]
fn bindkey_value_handles_extra_whitespace_after_close_bracket() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("[vicmd] forward-word");
assert_eq!(km, "vicmd");
assert_eq!(w, "forward-word");
}
#[test]
fn bindkey_value_handles_empty_keymap_brackets() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("[] do-thing");
assert_eq!(km, "");
assert_eq!(w, "do-thing");
}
#[test]
fn bindkey_value_handles_empty_widget_part() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("[viins]");
assert_eq!(km, "viins");
assert_eq!(w, "");
}
#[test]
fn bindkey_value_with_unclosed_bracket_falls_back_to_main() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("[viins backward-delete-char");
assert_eq!(km, "main");
assert_eq!(w, "[viins backward-delete-char");
}
#[test]
fn bindkey_value_empty_string_returns_main_and_empty() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("");
assert_eq!(km, "main");
assert_eq!(w, "");
}
#[test]
fn bindkey_value_widget_with_dashes_and_dots_preserved() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("[viins] zle-line-init");
assert_eq!(km, "viins");
assert_eq!(w, "zle-line-init");
}
#[test]
fn bindkey_value_does_not_strip_close_bracket_inside_widget() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("[viins] weird[name");
assert_eq!(km, "viins");
assert_eq!(w, "weird[name");
}
#[test]
fn bindkey_value_keymap_with_dashes() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("[my-keymap] do-thing");
assert_eq!(km, "my-keymap");
assert_eq!(w, "do-thing");
}
#[test]
fn bindkey_value_no_bracket_means_main_keymap() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("backward-kill-word");
assert_eq!(km, "main");
assert_eq!(w, "backward-kill-word");
}
#[test]
fn bindkey_value_widget_after_tab_whitespace() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("[main]\tup-line-or-history");
assert_eq!(km, "main");
assert_eq!(w, "up-line-or-history");
}
#[test]
fn bindkey_value_nested_open_bracket_uses_first_close() {
let _g = crate::test_util::global_state_lock();
let (km, w) = parse_bindkey_value("[ke[ymap] widget");
assert_eq!(km, "ke[ymap");
assert_eq!(w, "widget");
}
}