Make fish installable

When built with the default "installable" feature, the data files (share/) are
included in the fish binary itself.

Run `fish --install` or `fish --install=noconfirm` (for
non-interactive use) to install fish's data files into ~/.local/share/fish/install

To figure out if the data files are out of date, we write the current version
to a file on install, and read it on start.

CMake disables the default features so nothing changes for that, but this allows installing via `cargo install`,
and even making a static binary that you can then just upload and have extract itself.

We set $__fish_help_dir to empty for installable builds, because we do not have
a way to generate html docs (because we need fish_indent for highlighting).
The man pages are found via $__fish_data_dir/man
This commit is contained in:
Fabian Boehm 2024-01-31 15:50:13 +01:00
parent 7827a8e533
commit 7c73c5fec0
7 changed files with 425 additions and 33 deletions

View File

@ -58,6 +58,7 @@ function(CREATE_TARGET target)
$<$<CONFIG:Release>:--release> $<$<CONFIG:Release>:--release>
$<$<CONFIG:RelWithDebInfo>:--release> $<$<CONFIG:RelWithDebInfo>:--release>
--target ${Rust_CARGO_TARGET} --target ${Rust_CARGO_TARGET}
--no-default-features
${CARGO_FLAGS} ${CARGO_FLAGS}
${FEATURES_ARG} ${FEATURES_ARG}
&& &&

158
Cargo.lock generated
View File

@ -20,6 +20,15 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.1.30" version = "1.1.30"
@ -43,6 +52,25 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "cpufeatures"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]] [[package]]
name = "dashmap" name = "dashmap"
version = "5.5.3" version = "5.5.3"
@ -56,6 +84,16 @@ dependencies = [
"parking_lot_core", "parking_lot_core",
] ]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
@ -69,7 +107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@ -90,6 +128,7 @@ dependencies = [
"portable-atomic", "portable-atomic",
"rand", "rand",
"rsconf", "rsconf",
"rust-embed",
"serial_test", "serial_test",
"terminfo", "terminfo",
"widestring", "widestring",
@ -115,6 +154,16 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.5" version = "0.14.5"
@ -371,6 +420,49 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "rust-embed"
version = "8.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.79",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d"
dependencies = [
"sha2",
"walkdir",
]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@ -397,7 +489,18 @@ checksum = "079a83df15f85d89a68d64ae1238f142f172b1fa915d0d76b26a7cba1b659a69"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 1.0.109",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
] ]
[[package]] [[package]]
@ -429,6 +532,17 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "syn"
version = "2.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]] [[package]]
name = "terminfo" name = "terminfo"
version = "0.9.0" version = "0.9.0"
@ -441,18 +555,49 @@ dependencies = [
"phf_codegen", "phf_codegen",
] ]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.13" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]] [[package]]
name = "widestring" name = "widestring"
version = "1.1.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.52.0"
@ -462,6 +607,15 @@ dependencies = [
"windows-targets", "windows-targets",
] ]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.6" version = "0.52.6"

View File

@ -53,6 +53,7 @@ rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }
widestring = "1.1.0" widestring = "1.1.0"
# We need 0.9.0 specifically for some crash fixes. # We need 0.9.0 specifically for some crash fixes.
terminfo = "0.9.0" terminfo = "0.9.0"
rust-embed = { version = "8.2.0", optional = true }
[target.'cfg(not(target_has_atomic = "64"))'.dependencies] [target.'cfg(not(target_has_atomic = "64"))'.dependencies]
portable-atomic = { version = "1", default-features = false, features = [ portable-atomic = { version = "1", default-features = false, features = [
@ -83,8 +84,9 @@ name = "fish_key_reader"
path = "src/bin/fish_key_reader.rs" path = "src/bin/fish_key_reader.rs"
[features] [features]
default = [] default = ["installable"]
benchmark = [] benchmark = []
installable = ["dep:rust-embed"]
# The following features are auto-detected by the build-script and should not be enabled manually. # The following features are auto-detected by the build-script and should not be enabled manually.
asan = [] asan = []

View File

@ -223,10 +223,18 @@ fn setup_paths() {
var var
} }
let prefix = PathBuf::from(env::var("PREFIX").unwrap_or("/usr/local".to_string())); let (prefix_from_home, prefix) = if let Ok(pre) = env::var("PREFIX") {
if prefix.is_relative() { (false, PathBuf::from(pre))
} else {
(true, PathBuf::from(".local/"))
};
// If someone gives us a $PREFIX, we need it to be absolute.
// Otherwise we would try to get it from $HOME and that won't really work.
if !prefix_from_home && prefix.is_relative() {
panic!("Can't have relative prefix"); panic!("Can't have relative prefix");
} }
rsconf::rebuild_if_env_changed("PREFIX"); rsconf::rebuild_if_env_changed("PREFIX");
rsconf::set_env_value("PREFIX", prefix.to_str().unwrap()); rsconf::set_env_value("PREFIX", prefix.to_str().unwrap());
@ -234,11 +242,24 @@ fn setup_paths() {
rsconf::set_env_value("DATADIR", datadir.to_str().unwrap()); rsconf::set_env_value("DATADIR", datadir.to_str().unwrap());
rsconf::rebuild_if_env_changed("DATADIR"); rsconf::rebuild_if_env_changed("DATADIR");
let datadir_subdir = if prefix_from_home {
"fish/install"
} else {
"fish"
};
rsconf::set_env_value("DATADIR_SUBDIR", datadir_subdir);
let bindir = get_path("BINDIR", "bin/", prefix.clone()); let bindir = get_path("BINDIR", "bin/", prefix.clone());
rsconf::set_env_value("BINDIR", bindir.to_str().unwrap()); rsconf::set_env_value("BINDIR", bindir.to_str().unwrap());
rsconf::rebuild_if_env_changed("BINDIR"); rsconf::rebuild_if_env_changed("BINDIR");
let sysconfdir = get_path("SYSCONFDIR", "etc/", datadir.clone()); let sysconfdir = get_path(
"SYSCONFDIR",
// If we get our prefix from $HOME, we should use the system's /etc/
// ~/.local/share/etc/ makes no sense
if prefix_from_home { "/etc/" } else { "etc/" },
datadir.clone(),
);
rsconf::set_env_value("SYSCONFDIR", sysconfdir.to_str().unwrap()); rsconf::set_env_value("SYSCONFDIR", sysconfdir.to_str().unwrap());
rsconf::rebuild_if_env_changed("SYSCONFDIR"); rsconf::rebuild_if_env_changed("SYSCONFDIR");

View File

@ -181,7 +181,7 @@ function help --description 'Show help for the fish shell'
set -l version_string (string split . -f 1,2 -- $version | string join .) set -l version_string (string split . -f 1,2 -- $version | string join .)
set -l ext_url https://fishshell.com/docs/$version_string/$fish_help_page set -l ext_url https://fishshell.com/docs/$version_string/$fish_help_page
set -l page_url set -l page_url
if test -f $__fish_help_dir/index.html; and not set -lq chromeos_linux_garcon if set -q __fish_help_dir[1]; and test -f $__fish_help_dir/index.html; and not set -lq chromeos_linux_garcon
# Help is installed, use it # Help is installed, use it
set page_url file://$__fish_help_dir/$fish_help_page set page_url file://$__fish_help_dir/$fish_help_page

View File

@ -21,6 +21,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#![allow(unstable_name_collisions)] #![allow(unstable_name_collisions)]
#![allow(clippy::uninlined_format_args)] #![allow(clippy::uninlined_format_args)]
#[allow(unused_imports)]
use fish::future::IsSomeAnd;
use fish::{ use fish::{
ast::Ast, ast::Ast,
builtins::shared::{ builtins::shared::{
@ -71,9 +73,103 @@ use std::{env, ops::ControlFlow};
const DOC_DIR: &str = env!("DOCDIR"); const DOC_DIR: &str = env!("DOCDIR");
const DATA_DIR: &str = env!("DATADIR"); const DATA_DIR: &str = env!("DATADIR");
const DATA_DIR_SUBDIR: &str = env!("DATADIR_SUBDIR");
const SYSCONF_DIR: &str = env!("SYSCONFDIR"); const SYSCONF_DIR: &str = env!("SYSCONFDIR");
const BIN_DIR: &str = env!("BINDIR"); const BIN_DIR: &str = env!("BINDIR");
#[cfg(feature = "installable")]
fn install(confirm: bool) {
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
#[folder = "share/"]
struct Asset;
use std::fs;
use std::io::ErrorKind;
use std::io::Write;
use std::io::{stderr, stdin};
let Some(home) = fish::env::get_home() else {
eprintln!("Can't find $HOME",);
std::process::exit(1);
};
let dir = PathBuf::from(home).join(DATA_DIR).join(DATA_DIR_SUBDIR);
// TODO: Translation,
// FLOG?
// - Install: Translations
// - Install: Manpages (build via build.rs)
// - Don't install: __fish_build_paths.fish.in
if confirm {
if isatty(libc::STDIN_FILENO) {
eprintln!(
"This will write fish's data files to '{}'.\n\
Please enter 'yes' to continue.",
dir.display()
);
eprint!("> ");
let _ = stderr().flush();
}
let mut input = String::new();
if let Err(error) = stdin().read_line(&mut input) {
eprintln!("error: {error}")
}
if input != "yes\n" {
eprintln!("Exiting without writing any files\n");
std::process::exit(1);
}
} else {
eprintln!("Installing fish's data files to '{}'.", dir.display());
}
// Remove the install directory first, to clean out any removed files.
if let Err(err) = fs::remove_dir_all(dir.clone()) {
if err.kind() != ErrorKind::NotFound {
eprintln!("Removing '{}' failed: {}", dir.display(), err);
std::process::exit(1);
}
}
for file in Asset::iter() {
let path = dir.join(file.as_ref());
let Ok(_) = fs::create_dir_all(path.parent().unwrap()) else {
eprintln!(
"Creating directory '{}' failed",
path.parent().unwrap().display()
);
std::process::exit(1);
};
let res = File::create(&path);
let Ok(mut f) = res else {
eprintln!("Creating file '{}' failed", path.display());
continue;
};
// This should be impossible.
let d = Asset::get(&file).expect("File was somehow not included???");
if let Err(error) = f.write_all(&d.data) {
eprintln!("error: {error}");
std::process::exit(1);
}
}
let verfile = dir.join("fish-install-version");
let res = File::create(&verfile);
if let Ok(mut f) = res {
f.write_all(fish::BUILD_VERSION.as_bytes())
.expect("FAILED TO WRITE");
} else {
eprintln!("Creating file '{}' failed", verfile.display());
};
std::process::exit(0);
}
#[cfg(not(feature = "installable"))]
fn install(_confirm: bool) {
eprintln!("Fish was built without support for self-installation");
std::process::exit(1);
}
/// container to hold the options specified within the command line /// container to hold the options specified within the command line
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct FishCmdOpts { struct FishCmdOpts {
@ -207,12 +303,31 @@ fn determine_config_directory_paths(argv0: impl AsRef<Path>) -> ConfigPaths {
if !done { if !done {
// Fall back to what got compiled in. // Fall back to what got compiled in.
let data = if cfg!(feature = "installable") {
let Some(home) = fish::env::get_home() else {
FLOG!(
error,
"Cannot find home directory and will refuse to read configuration"
);
return paths;
};
PathBuf::from(home).join(DATA_DIR).join(DATA_DIR_SUBDIR)
} else {
PathBuf::from(DATA_DIR).join(DATA_DIR_SUBDIR)
};
let bin = if cfg!(feature = "installable") {
exec_path.parent().map(|x| x.to_path_buf())
} else {
Some(PathBuf::from(BIN_DIR))
};
FLOG!(config, "Using compiled in paths:"); FLOG!(config, "Using compiled in paths:");
paths = ConfigPaths { paths = ConfigPaths {
data: PathBuf::from(DATA_DIR).join("fish"), data,
sysconf: PathBuf::from(SYSCONF_DIR).join("fish"), sysconf: PathBuf::from(SYSCONF_DIR).join("fish"),
doc: DOC_DIR.into(), doc: DOC_DIR.into(),
bin: Some(BIN_DIR.into()), bin,
} }
} }
@ -266,6 +381,49 @@ fn source_config_in_directory(parser: &Parser, dir: &wstr) -> bool {
/// Parse init files. exec_path is the path of fish executable as determined by argv[0]. /// Parse init files. exec_path is the path of fish executable as determined by argv[0].
fn read_init(parser: &Parser, paths: &ConfigPaths) { fn read_init(parser: &Parser, paths: &ConfigPaths) {
let datapath = str2wcstring(paths.data.as_os_str().as_bytes()); let datapath = str2wcstring(paths.data.as_os_str().as_bytes());
#[cfg(feature = "installable")]
{
// (false-positive, is_none_or is a backport, this builds with 1.70)
#[allow(clippy::incompatible_msrv)]
if paths
.bin
.clone()
.is_none_or(|x| !x.starts_with(env!("CARGO_MANIFEST_DIR")))
{
// When fish is installable, we write the version to a file,
// now we check it.
let verfile =
PathBuf::from(fish::common::wcs2osstring(&datapath)).join("fish-install-version");
let version = match std::fs::read_to_string(verfile) {
Ok(x) => x,
Err(err) => {
let escaped_pathname = escape(&datapath);
FLOGF!(
error,
"Fish cannot find its asset files in '%ls'.\n\
Refusing to read configuration because of this.\n\
The underlying error is: '%ls'",
escaped_pathname,
err.to_string()
);
return;
}
};
if version != fish::BUILD_VERSION {
FLOGF!(
error,
"Asset files are version %s, this fish is version %s. Please run `fish --install` again",
version,
fish::BUILD_VERSION
);
// We could refuse to read any config,
// but that seems a bit harsh.
// return;
}
}
}
if !source_config_in_directory(parser, &datapath) { if !source_config_in_directory(parser, &datapath) {
// If we cannot read share/config.fish, our internal configuration, // If we cannot read share/config.fish, our internal configuration,
// something is wrong. // something is wrong.
@ -274,8 +432,14 @@ fn read_init(parser: &Parser, paths: &ConfigPaths) {
let escaped_pathname = escape(&datapath); let escaped_pathname = escape(&datapath);
FLOGF!( FLOGF!(
error, error,
"Fish cannot find its asset files in '%ls'. Refusing to read configuration.", "Fish cannot find its asset files in '%ls'.\n\
escaped_pathname Refusing to read configuration because of this.",
escaped_pathname,
);
#[cfg(feature = "installable")]
FLOG!(
error,
"If you installed via `cargo install`, please run `fish --install` and restart fish."
); );
return; return;
} }
@ -336,6 +500,7 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
wopt(L!("no-config"), NoArgument, 'N'), wopt(L!("no-config"), NoArgument, 'N'),
wopt(L!("no-execute"), NoArgument, 'n'), wopt(L!("no-execute"), NoArgument, 'n'),
wopt(L!("print-rusage-self"), NoArgument, RUSAGE_ARG), wopt(L!("print-rusage-self"), NoArgument, RUSAGE_ARG),
wopt(L!("install"), OptionalArgument, 'I'),
wopt( wopt(
L!("print-debug-categories"), L!("print-debug-categories"),
NoArgument, NoArgument,
@ -370,6 +535,21 @@ fn fish_parse_opt(args: &mut [WString], opts: &mut FishCmdOpts) -> ControlFlow<i
'f' => opts.features = w.woptarg.unwrap().to_owned(), 'f' => opts.features = w.woptarg.unwrap().to_owned(),
'h' => opts.batch_cmds.push("__fish_print_help fish".into()), 'h' => opts.batch_cmds.push("__fish_print_help fish".into()),
'i' => opts.is_interactive_session = true, 'i' => opts.is_interactive_session = true,
'I' => {
let noconfirm = match w.woptarg {
None => false,
Some(n) if n == L!("noconfirm") => true,
_ => {
FLOGF!(
error,
"Unknown argument to --install: '%ls'",
w.woptarg.unwrap()
);
std::process::exit(1);
}
};
install(!noconfirm);
}
'l' => opts.is_login = true, 'l' => opts.is_login = true,
'N' => { 'N' => {
opts.no_config = true; opts.no_config = true;

View File

@ -452,6 +452,39 @@ fn get_hostname_identifier() -> Option<WString> {
} }
} }
/// Get values for $HOME via getpwuid,
/// without trusting $USER or $HOME.
pub fn get_home() -> Option<String> {
let uid: uid_t = geteuid();
let mut userinfo: MaybeUninit<libc::passwd> = MaybeUninit::uninit();
let mut result: *mut libc::passwd = std::ptr::null_mut();
let mut buf = [0 as libc::c_char; 8192];
// We need to get the data via the uid and don't trust $USER.
let retval = unsafe {
libc::getpwuid_r(
uid,
userinfo.as_mut_ptr(),
buf.as_mut_ptr(),
buf.len(),
&mut result,
)
};
if retval != 0 || result.is_null() {
return None;
}
let userinfo = unsafe { userinfo.assume_init() };
if !userinfo.pw_dir.is_null() {
let home = unsafe { CStr::from_ptr(userinfo.pw_dir) };
let home = home.to_str().ok().map(|x| x.to_owned());
home
} else {
None
}
}
/// Set up the USER and HOME variable. /// Set up the USER and HOME variable.
fn setup_user(vars: &EnvStack) { fn setup_user(vars: &EnvStack) {
let uid: uid_t = geteuid(); let uid: uid_t = geteuid();
@ -595,22 +628,30 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
.set(inherited_vars) .set(inherited_vars)
.expect("env_init is being called multiple times"); .expect("env_init is being called multiple times");
// Set $USER, $HOME and $EUID
// This involves going to passwd and stuff.
vars.set_one(L!("EUID"), EnvMode::GLOBAL, geteuid().to_wstring());
setup_user(vars);
if let Some(paths) = paths { if let Some(paths) = paths {
vars.set_one( let datadir = str2wcstring(paths.data.as_os_str().as_bytes());
FISH_DATADIR_VAR,
EnvMode::GLOBAL, vars.set_one(FISH_DATADIR_VAR, EnvMode::GLOBAL, datadir.clone());
str2wcstring(paths.data.as_os_str().as_bytes()),
);
vars.set_one( vars.set_one(
FISH_SYSCONFDIR_VAR, FISH_SYSCONFDIR_VAR,
EnvMode::GLOBAL, EnvMode::GLOBAL,
str2wcstring(paths.sysconf.as_os_str().as_bytes()), str2wcstring(paths.sysconf.as_os_str().as_bytes()),
); );
if !cfg!(feature = "installable") {
vars.set_one( vars.set_one(
FISH_HELPDIR_VAR, FISH_HELPDIR_VAR,
EnvMode::GLOBAL, EnvMode::GLOBAL,
str2wcstring(paths.doc.as_os_str().as_bytes()), str2wcstring(paths.doc.as_os_str().as_bytes()),
); );
} else {
vars.set_empty(FISH_HELPDIR_VAR, EnvMode::GLOBAL);
}
if let Some(bp) = &paths.bin { if let Some(bp) = &paths.bin {
vars.set_one( vars.set_one(
FISH_BIN_DIR, FISH_BIN_DIR,
@ -622,21 +663,14 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
}; };
if default_paths { if default_paths {
let mut scstr = paths.data.clone(); let mut scstr = datadir;
scstr.push("functions"); // This is generated by PathBuf.join() everywhere currently
vars.set_one( assert!(!scstr.ends_with("/"));
L!("fish_function_path"), scstr.push_str("/functions");
EnvMode::GLOBAL, vars.set_one(L!("fish_function_path"), EnvMode::GLOBAL, scstr);
str2wcstring(scstr.as_os_str().as_bytes()),
);
} }
} }
// Set $USER, $HOME and $EUID
// This involves going to passwd and stuff.
vars.set_one(L!("EUID"), EnvMode::GLOBAL, geteuid().to_wstring());
setup_user(vars);
let user_config_dir = path_get_config(); let user_config_dir = path_get_config();
vars.set_one( vars.set_one(
FISH_CONFIG_DIR, FISH_CONFIG_DIR,