-
Notifications
You must be signed in to change notification settings - Fork 175
Expand file tree
/
Copy pathmod.rs
More file actions
165 lines (150 loc) · 5.61 KB
/
mod.rs
File metadata and controls
165 lines (150 loc) · 5.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
mod fs;
mod shebang;
mod which;
use std::{
ffi::{CStr, OsStr},
iter::once,
mem::replace,
os::unix::ffi::OsStrExt,
path::Path,
};
use bstr::{BStr, BString, ByteSlice};
use fspy_shared::ipc::{AccessMode, PathAccess};
use nix::unistd::{AccessFlags, access};
use shebang::{ParseShebangOptions, parse_shebang};
use crate::open_exec::open_executable;
#[derive(Debug, Clone)]
pub struct SearchPath<'a> {
/// Custom search path to use (like execvP), overrides PATH if Some
pub custom_path: Option<&'a BStr>,
}
/// Configuration for exec resolution behavior
#[derive(Debug, Clone)]
pub struct ExecResolveConfig<'a> {
/// If Some and the program doesn't contains `/`,
/// search the program in PATH (like execvp, execvpe, execlp) instead of finding it in current directory
pub search_path: Option<SearchPath<'a>>,
/// Options for parsing shebangs (all exec variants handle shebangs)
pub shebang_options: ParseShebangOptions,
}
impl<'a> ExecResolveConfig<'a> {
/// Configuration for execve - no PATH search, direct execution
pub fn search_path_disabled() -> Self {
Self { search_path: None, shebang_options: Default::default() }
}
/// execlp/execvp/execvP/execvpe
/// `custom_path` allows a customized path to be searched like in execvP (macOS extension)
pub fn search_path_enabled(custom_path: Option<&'a BStr>) -> Self {
Self { search_path: Some(SearchPath { custom_path }), shebang_options: Default::default() }
}
}
#[derive(Debug)]
pub struct Exec {
pub program: BString,
pub args: Vec<BString>,
/// vec of (name, value). value is None when the entry in environ doesn't contain a `=` character.
pub envs: Vec<(BString, Option<BString>)>,
}
fn getenv(name: &CStr) -> Option<&'static CStr> {
let value = unsafe { nix::libc::getenv(name.as_ptr().cast()) };
if value.is_null() { None } else { Some(unsafe { CStr::from_ptr(value) }) }
}
fn peek_executable(path: &Path, buf: &mut [u8]) -> nix::Result<usize> {
let fd = open_executable(path)?;
let mut total_read_size = 0;
loop {
let read_size = nix::unistd::read(&fd, &mut buf[total_read_size..])?;
if read_size == 0 {
break;
}
total_read_size += read_size;
}
Ok(total_read_size)
}
impl Exec {
/// Resolve the program path according to exec family semantics
///
/// This method replicates the behavior of execve/execvp/execvP/execvpe for program resolution,
/// including PATH searching and shebang handling.
///
/// # Returns
///
/// * `Ok(())` if resolution succeeds and `self` is updated with resolved paths
/// * `Err(nix::Error)` with appropriate errno, like the exec function would return
///
/// # Errors
///
/// Returns an error if:
/// - The program is not found in PATH (`ENOENT`)
/// - The program file cannot be accessed or read (`EACCES`, `EISDIR`, `EIO`)
/// - Shebang parsing fails due to I/O errors (`EIO`)
pub fn resolve(
&mut self,
mut on_path_access: impl FnMut(PathAccess<'_>),
config: ExecResolveConfig,
) -> nix::Result<()> {
if let Some(search_path) = config.search_path {
let path = if let Some(custom_path) = search_path.custom_path {
custom_path
} else if let Some(path) = getenv(c"PATH") {
path.to_bytes().as_bstr()
} else {
// https://github.com/kraj/musl/blob/1b06420abdf46f7d06ab4067e7c51b8b63731852/src/process/execvp.c#L21
b"/usr/local/bin:/bin:/usr/bin".as_bstr()
};
let program = which::which(
self.program.as_ref(),
path,
|path| {
on_path_access(PathAccess { path: path.into(), mode: AccessMode::Read });
access(OsStr::from_bytes(path), AccessFlags::X_OK)
},
|program| Ok(program.to_owned()),
)?;
self.program = program;
}
self.parse_shebang(on_path_access, config.shebang_options)?;
Ok(())
}
fn parse_shebang(
&mut self,
mut on_path_access: impl FnMut(PathAccess<'_>),
options: ParseShebangOptions,
) -> nix::Result<()> {
if let Some(shebang) = parse_shebang(
|path, buf| {
on_path_access(PathAccess::read(path));
peek_executable(path, buf)
},
Path::new(OsStr::from_bytes(&self.program)),
options,
)? {
self.args[0] = shebang.interpreter.clone();
let old_program = replace(&mut self.program, shebang.interpreter);
self.args.splice(1..1, shebang.arguments.into_iter().chain(once(old_program)));
}
Ok(())
}
}
/// Ensures an environment variable is set to the specified value
///
/// If the variable doesn't exist, it is added. If it exists with the same value,
/// no change is made. If it exists with a different value, an error is returned.
///
/// # Errors
///
/// Returns `Err(nix::Error::EINVAL)` if the environment variable already exists with a different value.
pub fn ensure_env(
envs: &mut Vec<(BString, Option<BString>)>,
name: impl AsRef<BStr>,
value: impl AsRef<BStr>,
) -> nix::Result<()> {
let name = name.as_ref();
let value = value.as_ref();
let existing_value = envs.iter().find_map(|(n, v)| if n == name { v.as_ref() } else { None });
if let Some(existing_value) = existing_value {
return if existing_value == value { Ok(()) } else { Err(nix::Error::EINVAL) };
};
envs.push((name.to_owned(), Some(value.to_owned())));
Ok(())
}