update docs
This commit is contained in:
@@ -69,11 +69,36 @@ pub fn start(
|
||||
) -> Result<Child> {
|
||||
start_group(&format!("bay{bay}"), arguments, working, environment)
|
||||
}
|
||||
pub fn foreground(
|
||||
bay: Bay,
|
||||
arguments: &[String],
|
||||
working: &str,
|
||||
environment: &[(String, String)],
|
||||
descriptors: [OwnedFd; 3],
|
||||
terminal: bool,
|
||||
) -> Result<Child> {
|
||||
spawn(
|
||||
&format!("bay{bay}"),
|
||||
arguments,
|
||||
working,
|
||||
environment,
|
||||
Some((descriptors, terminal)),
|
||||
)
|
||||
}
|
||||
pub fn start_group(
|
||||
name: &str,
|
||||
arguments: &[String],
|
||||
working: &str,
|
||||
environment: &[(String, String)],
|
||||
) -> Result<Child> {
|
||||
spawn(name, arguments, working, environment, None)
|
||||
}
|
||||
fn spawn(
|
||||
name: &str,
|
||||
arguments: &[String],
|
||||
working: &str,
|
||||
environment: &[(String, String)],
|
||||
io: Option<([OwnedFd; 3], bool)>,
|
||||
) -> Result<Child> {
|
||||
if !fds_common::manifest::identifier(name) {
|
||||
return Err(Error("Invalid process group".into()));
|
||||
@@ -95,11 +120,11 @@ pub fn start_group(
|
||||
.custom_flags(libc::O_CLOEXEC)
|
||||
.open(path.join("cgroup.procs"))?;
|
||||
let mut command = Command::new(&arguments[0]);
|
||||
let working = c(working)?;
|
||||
command
|
||||
.args(&arguments[1..])
|
||||
.current_dir(working)
|
||||
.env_clear()
|
||||
.env("PATH", "/usr/bin:/bin")
|
||||
.env("PATH", "/usr/bin:/bin:/run/fds/bin")
|
||||
.env("HOME", "/home/fds")
|
||||
.env("USER", "fds")
|
||||
.env("LOGNAME", "fds")
|
||||
@@ -109,6 +134,14 @@ pub fn start_group(
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
command.envs(environment.iter().cloned());
|
||||
let mut terminal = false;
|
||||
if let Some(([input, output, errors], tty)) = io {
|
||||
command
|
||||
.stdin(Stdio::from(input))
|
||||
.stdout(Stdio::from(output))
|
||||
.stderr(Stdio::from(errors));
|
||||
terminal = tty;
|
||||
}
|
||||
// Only async-signal-safe syscalls are used in the forked child. Writing 0
|
||||
// moves the child itself, avoiding PID reuse and parent/child migration races.
|
||||
unsafe {
|
||||
@@ -119,6 +152,9 @@ pub fn start_group(
|
||||
if libc::setsid() < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
if terminal && libc::ioctl(0, libc::TIOCSCTTY, 0) < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
if libc::setgroups(0, std::ptr::null()) < 0
|
||||
|| libc::setgid(1000) < 0
|
||||
|| libc::setuid(1000) < 0
|
||||
@@ -128,6 +164,10 @@ pub fn start_group(
|
||||
if libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
// Resolve client-selected working directories only as the user.
|
||||
if libc::chdir(working.as_ptr()) < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
let mut mask: libc::sigset_t = std::mem::zeroed();
|
||||
libc::sigemptyset(&mut mask);
|
||||
if libc::sigprocmask(libc::SIG_SETMASK, &mask, std::ptr::null_mut()) < 0 {
|
||||
|
||||
@@ -4,6 +4,7 @@ mod data_sessions;
|
||||
mod media;
|
||||
mod power;
|
||||
mod profiles;
|
||||
mod programs;
|
||||
mod recovery;
|
||||
mod server;
|
||||
mod software;
|
||||
|
||||
@@ -228,7 +228,7 @@ impl Mounted {
|
||||
("FDS_APP".into(), app.display().to_string()),
|
||||
(
|
||||
"PATH".into(),
|
||||
format!("{}/bin:/usr/bin:/bin", app.display()),
|
||||
format!("{}/bin:/usr/bin:/bin:/run/fds/bin", app.display()),
|
||||
),
|
||||
(
|
||||
"LD_LIBRARY_PATH".into(),
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
//! A stable PATH directory is updated as validated cartridges appear/disappear.
|
||||
use crate::media::Mounted;
|
||||
use fds_common::{
|
||||
Bay, Error, Result,
|
||||
manifest::{Class, identifier},
|
||||
};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs,
|
||||
os::unix::fs::{PermissionsExt, symlink},
|
||||
path::Path,
|
||||
};
|
||||
pub const BIN: &str = "/run/fds/bin";
|
||||
pub type Commands = BTreeMap<String, (Bay, String)>;
|
||||
|
||||
pub fn collect(mounts: &BTreeMap<Bay, Mounted>) -> Result<Commands> {
|
||||
let mut result = BTreeMap::new();
|
||||
for (&bay, mount) in mounts {
|
||||
if mount.manifest.cartridge.class != Class::Program || mount.fault.is_some() {
|
||||
continue;
|
||||
}
|
||||
let mut commands = Vec::new();
|
||||
if let Some(software) = &mount.software {
|
||||
for entry in &software.catalogue.software {
|
||||
for name in entry.commands.keys() {
|
||||
commands.push((
|
||||
name.clone(),
|
||||
format!("{}:{name}", entry.id),
|
||||
format!("b{bay}:{}:{name}", entry.id),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let root = Path::new(&mount.path).join("app/bin");
|
||||
if root.is_dir() {
|
||||
for entry in fs::read_dir(root)? {
|
||||
let entry = entry?;
|
||||
let Some(name) = entry.file_name().to_str().map(str::to_owned) else {
|
||||
continue;
|
||||
};
|
||||
if identifier(&name)
|
||||
&& entry.path().is_file()
|
||||
&& entry
|
||||
.path()
|
||||
.canonicalize()?
|
||||
.starts_with(Path::new(&mount.path).join("app"))
|
||||
&& entry.path().metadata()?.permissions().mode() & 0o111 != 0
|
||||
{
|
||||
commands.push((name.clone(), name.clone(), format!("b{bay}:{name}")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
commands.sort();
|
||||
for (name, selector, qualified) in commands {
|
||||
result.entry(name).or_insert((bay, selector.clone()));
|
||||
// A fully qualified spelling always identifies this cartridge.
|
||||
result.insert(qualified, (bay, selector));
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
pub fn publish(commands: &Commands) -> Result<()> {
|
||||
fs::create_dir_all(BIN)?;
|
||||
fs::set_permissions(BIN, fs::Permissions::from_mode(0o755))?;
|
||||
for entry in fs::read_dir(BIN)? {
|
||||
let entry = entry?;
|
||||
if !commands.contains_key(&entry.file_name().to_string_lossy().into_owned()) {
|
||||
if !entry.file_type()?.is_symlink() {
|
||||
return Err(Error(
|
||||
"Unexpected file in cartridge command directory".into(),
|
||||
));
|
||||
}
|
||||
fs::remove_file(entry.path())?;
|
||||
}
|
||||
}
|
||||
for name in commands.keys() {
|
||||
let path = Path::new(BIN).join(name);
|
||||
if path.symlink_metadata().is_ok() {
|
||||
if fs::read_link(&path)? != Path::new("/usr/bin/fds-program") {
|
||||
return Err(Error("Unexpected cartridge command link".into()));
|
||||
}
|
||||
} else {
|
||||
symlink("/usr/bin/fds-program", path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -32,6 +32,7 @@ struct State {
|
||||
profiles: profiles::Manager,
|
||||
burning: burning::Manager,
|
||||
power: power::Manager,
|
||||
commands: crate::programs::Commands,
|
||||
}
|
||||
impl State {
|
||||
fn scan(&mut self) -> Result<()> {
|
||||
@@ -63,6 +64,7 @@ impl State {
|
||||
software: None,
|
||||
mount: None,
|
||||
consumers: consumers::count(bay)?,
|
||||
commands: Vec::new(),
|
||||
};
|
||||
// A hub in a bay may contain several functions, but multiple actual
|
||||
// devices are ambiguous until an explicit composite policy exists.
|
||||
@@ -109,8 +111,113 @@ impl State {
|
||||
if !self.power.frozen() {
|
||||
self.profiles.reconcile(&self.mounts, &devices)?;
|
||||
}
|
||||
self.commands = crate::programs::collect(&self.mounts)?;
|
||||
crate::programs::publish(&self.commands)?;
|
||||
for entry in &mut self.bays {
|
||||
entry.commands = self
|
||||
.commands
|
||||
.iter()
|
||||
.filter_map(|(alias, (bay, selector))| {
|
||||
(*bay == entry.bay && alias.starts_with(&format!("b{bay}:"))).then(|| {
|
||||
fds_common::control::PublishedCommand {
|
||||
selector: selector.clone(),
|
||||
alias: alias.clone(),
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn program(&mut self, name: &str) -> Result<(Bay, fds_common::launch::Program)> {
|
||||
if self.recovery || self.power.frozen() {
|
||||
return Err(Error(
|
||||
"Program launches are unavailable during recovery or shutdown".into(),
|
||||
));
|
||||
}
|
||||
let (bay, selector) = self
|
||||
.commands
|
||||
.get(name)
|
||||
.cloned()
|
||||
.ok_or_else(|| Error(format!("Cartridge command {name} is no longer available")))?;
|
||||
let mount = self
|
||||
.mounts
|
||||
.get_mut(&bay)
|
||||
.ok_or_else(|| Error("Cartridge was removed".into()))?;
|
||||
let (arguments, environment) = mount.program(&[selector])?;
|
||||
Ok((
|
||||
bay,
|
||||
fds_common::launch::Program {
|
||||
arguments,
|
||||
environment,
|
||||
},
|
||||
))
|
||||
}
|
||||
fn foreground(
|
||||
&mut self,
|
||||
name: &str,
|
||||
arguments: &[String],
|
||||
terminal: bool,
|
||||
working: &str,
|
||||
term: &str,
|
||||
client: &mut Client,
|
||||
) -> Result<Response> {
|
||||
self.program(name)?;
|
||||
if arguments.len() > 120
|
||||
|| arguments.iter().any(|a| a.contains('\0'))
|
||||
|| !working.starts_with('/')
|
||||
|| working.len() > 4096
|
||||
|| working.contains('\0')
|
||||
|| term.len() > 128
|
||||
|| term.chars().any(char::is_control)
|
||||
{
|
||||
return Err(Error(
|
||||
"Invalid foreground program arguments or environment".into(),
|
||||
));
|
||||
}
|
||||
client.socket.set_nonblocking(false)?;
|
||||
client
|
||||
.socket
|
||||
.set_read_timeout(Some(Duration::from_secs(5)))?;
|
||||
client
|
||||
.socket
|
||||
.set_write_timeout(Some(Duration::from_secs(5)))?;
|
||||
let result = (|| {
|
||||
client.socket.write_all(b"R")?;
|
||||
let descriptors: [OwnedFd; 3] = fds_common::launch::receive_fds(&mut client.socket, 3)?
|
||||
.try_into()
|
||||
.map_err(|_| Error("Expected three program I/O descriptors".into()))?;
|
||||
let (bay, mut program) = self.program(name)?;
|
||||
program.arguments.extend_from_slice(arguments);
|
||||
program.environment.push(("TERM".into(), term.into()));
|
||||
let mut child = consumers::foreground(
|
||||
bay,
|
||||
&program.arguments,
|
||||
working,
|
||||
&program.environment,
|
||||
descriptors,
|
||||
terminal,
|
||||
)?;
|
||||
let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, child.id(), 0) } as i32;
|
||||
if fd < 0 {
|
||||
let _ = child.kill();
|
||||
self.children.push(child);
|
||||
return Err(std::io::Error::last_os_error().into());
|
||||
}
|
||||
let pidfd = unsafe { OwnedFd::from_raw_fd(fd) };
|
||||
if let Err(error) = fds_common::launch::send_fds(&client.socket, &[pidfd.as_raw_fd()]) {
|
||||
let _ = child.kill();
|
||||
self.children.push(child);
|
||||
return Err(error);
|
||||
}
|
||||
client.foreground = Some(child);
|
||||
let mut reply = Response::failure("");
|
||||
reply.error = None;
|
||||
Ok(reply)
|
||||
})();
|
||||
client.socket.set_nonblocking(true)?;
|
||||
result
|
||||
}
|
||||
fn mapped_devices(&self) -> Vec<(Bay, topology::UsbDevice)> {
|
||||
self.bays
|
||||
.iter()
|
||||
@@ -628,6 +735,7 @@ impl State {
|
||||
},
|
||||
media_job: None,
|
||||
recovery: None,
|
||||
exit_status: None,
|
||||
disk: None,
|
||||
power: None,
|
||||
})
|
||||
@@ -796,6 +904,7 @@ struct Client {
|
||||
offset: usize,
|
||||
deadline: Instant,
|
||||
uid: u32,
|
||||
foreground: Option<std::process::Child>,
|
||||
waiting: Option<(String, u64)>,
|
||||
}
|
||||
fn peer_uid(socket: &UnixStream) -> Result<Option<u32>> {
|
||||
@@ -910,6 +1019,7 @@ pub fn run(notify: bool) -> Result<()> {
|
||||
profiles: profiles::Manager::new(!recovery),
|
||||
burning: burning::Manager::load()?,
|
||||
power: power::Manager::load()?,
|
||||
commands: BTreeMap::new(),
|
||||
};
|
||||
for n in 1..=12 {
|
||||
let bay = Bay::try_from(n)?;
|
||||
@@ -965,6 +1075,7 @@ pub fn run(notify: bool) -> Result<()> {
|
||||
state.profiles.shutdown()?;
|
||||
}
|
||||
cleanup_stale_mounts()?;
|
||||
crate::programs::publish(&BTreeMap::new())?;
|
||||
state.scan()?;
|
||||
let mut clients: Vec<Client> = Vec::new();
|
||||
loop {
|
||||
@@ -1000,7 +1111,7 @@ pub fn run(notify: bool) -> Result<()> {
|
||||
fd: client.socket.as_raw_fd(),
|
||||
events: if client.output.is_some() {
|
||||
libc::POLLOUT
|
||||
} else if client.waiting.is_some() {
|
||||
} else if client.waiting.is_some() || client.foreground.is_some() {
|
||||
0
|
||||
} else {
|
||||
libc::POLLIN
|
||||
@@ -1010,6 +1121,7 @@ pub fn run(notify: bool) -> Result<()> {
|
||||
}
|
||||
let timeout = clients
|
||||
.iter()
|
||||
.filter(|c| c.foreground.is_none())
|
||||
.map(|c| {
|
||||
c.deadline
|
||||
.saturating_duration_since(Instant::now())
|
||||
@@ -1071,9 +1183,25 @@ pub fn run(notify: bool) -> Result<()> {
|
||||
if (fds[0].revents != 0 && consume_events(&events)?) || console_changed {
|
||||
state.scan()?;
|
||||
}
|
||||
// Reserve connection capacity for eject/status even with many foreground jobs.
|
||||
let foreground_count = clients.iter().filter(|c| c.foreground.is_some()).count();
|
||||
// Process existing clients before accepting more; vectors stay aligned.
|
||||
for index in (0..clients.len()).rev() {
|
||||
let client = &mut clients[index];
|
||||
if let Some(child) = &mut client.foreground {
|
||||
if let Some(status) = child.try_wait()? {
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
let mut reply = Response::failure("");
|
||||
reply.error = None;
|
||||
reply.exit_status = Some(status.into_raw());
|
||||
let mut output =
|
||||
serde_json::to_vec(&reply).map_err(|e| Error(e.to_string()))?;
|
||||
output.push(b'\n');
|
||||
client.output = Some(output);
|
||||
client.foreground = None;
|
||||
client.deadline = Instant::now() + Duration::from_secs(5);
|
||||
}
|
||||
}
|
||||
let ready = fds[index + 5].revents;
|
||||
if let Some((id, sequence)) = &client.waiting {
|
||||
let job = state.burning.status(id);
|
||||
@@ -1097,8 +1225,8 @@ pub fn run(notify: bool) -> Result<()> {
|
||||
client.deadline = Instant::now() + Duration::from_secs(5);
|
||||
}
|
||||
}
|
||||
let mut remove =
|
||||
Instant::now() >= client.deadline || ready & (libc::POLLERR | libc::POLLNVAL) != 0;
|
||||
let mut remove = (client.foreground.is_none() && Instant::now() >= client.deadline)
|
||||
|| ready & (libc::POLLERR | libc::POLLNVAL) != 0;
|
||||
if !remove && ready & libc::POLLIN != 0 && client.output.is_none() {
|
||||
let mut chunk = [0u8; 4096];
|
||||
match client.socket.read(&mut chunk) {
|
||||
@@ -1124,9 +1252,31 @@ pub fn run(notify: bool) -> Result<()> {
|
||||
client.waiting = Some((id.clone(), *sequence));
|
||||
}
|
||||
}
|
||||
state
|
||||
.reply(request, client.uid)
|
||||
.unwrap_or_else(Response::failure)
|
||||
if let Request::Program {
|
||||
name,
|
||||
arguments,
|
||||
terminal,
|
||||
working,
|
||||
term,
|
||||
} = request
|
||||
{
|
||||
if foreground_count >= 64 {
|
||||
Response::failure(
|
||||
"Too many foreground programs; close a program and retry",
|
||||
)
|
||||
} else {
|
||||
state
|
||||
.foreground(
|
||||
&name, &arguments, terminal, &working,
|
||||
&term, client,
|
||||
)
|
||||
.unwrap_or_else(Response::failure)
|
||||
}
|
||||
} else {
|
||||
state
|
||||
.reply(request, client.uid)
|
||||
.unwrap_or_else(Response::failure)
|
||||
}
|
||||
}
|
||||
Err(_) => Response::failure("Invalid control request"),
|
||||
}
|
||||
@@ -1140,7 +1290,9 @@ pub fn run(notify: bool) -> Result<()> {
|
||||
.unwrap();
|
||||
}
|
||||
output.push(b'\n');
|
||||
if client.waiting.is_some() {
|
||||
if client.foreground.is_some() {
|
||||
// SIGCHLD wakes the loop when this foreground command exits.
|
||||
} else if client.waiting.is_some() {
|
||||
client.deadline = Instant::now() + Duration::from_secs(90);
|
||||
} else {
|
||||
client.output = Some(output);
|
||||
@@ -1173,7 +1325,11 @@ pub fn run(notify: bool) -> Result<()> {
|
||||
remove = true;
|
||||
}
|
||||
if remove {
|
||||
clients.swap_remove(index);
|
||||
let mut removed = clients.swap_remove(index);
|
||||
if let Some(mut child) = removed.foreground.take() {
|
||||
let _ = child.kill();
|
||||
state.children.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
if fds[1].revents & libc::POLLIN != 0 {
|
||||
@@ -1181,7 +1337,7 @@ pub fn run(notify: bool) -> Result<()> {
|
||||
match listener.accept() {
|
||||
Ok((socket, _)) => {
|
||||
let uid = peer_uid(&socket)?;
|
||||
if clients.len() >= 16 || uid.is_none() {
|
||||
if clients.len() >= 128 || uid.is_none() {
|
||||
continue;
|
||||
}
|
||||
socket.set_nonblocking(true)?;
|
||||
@@ -1192,6 +1348,7 @@ pub fn run(notify: bool) -> Result<()> {
|
||||
offset: 0,
|
||||
deadline: Instant::now() + Duration::from_secs(5),
|
||||
uid: uid.unwrap(),
|
||||
foreground: None,
|
||||
waiting: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use crate::media::{self, c, checked};
|
||||
use fds_burn::{device::Disk, image};
|
||||
use fds_common::{Bay, Error, Result, read_text, sysfs::BlockPartition};
|
||||
use fds_software::{Catalogue, archive};
|
||||
use fds_software::{Catalogue, archive, tree};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fs::{self, File, OpenOptions},
|
||||
@@ -136,7 +136,14 @@ impl Mounted {
|
||||
&format!("/proc/self/fd/{}", source.as_raw_fd()),
|
||||
&path,
|
||||
"erofs",
|
||||
libc::MS_RDONLY | libc::MS_NOEXEC | libc::MS_NOSUID | libc::MS_NODEV,
|
||||
libc::MS_RDONLY
|
||||
| libc::MS_NOSUID
|
||||
| libc::MS_NODEV
|
||||
| if result.catalogue.format == 1 {
|
||||
libc::MS_NOEXEC
|
||||
} else {
|
||||
0
|
||||
},
|
||||
"",
|
||||
)?;
|
||||
result.payloads.insert(
|
||||
@@ -148,7 +155,11 @@ impl Mounted {
|
||||
key,
|
||||
},
|
||||
);
|
||||
let bundles = Path::new(&path).join("bundles");
|
||||
let bundles = Path::new(&path).join(if result.catalogue.format == 2 {
|
||||
"programs"
|
||||
} else {
|
||||
"bundles"
|
||||
});
|
||||
if !fs::symlink_metadata(&bundles)?.is_dir() {
|
||||
return Err(Error("Payload bundles must be a real directory".into()));
|
||||
}
|
||||
@@ -157,14 +168,20 @@ impl Mounted {
|
||||
.software
|
||||
.iter()
|
||||
.filter(|s| s.partition == spec.number)
|
||||
.map(|s| format!("{}.tar.xz", s.id))
|
||||
.map(|s| {
|
||||
if s.installed {
|
||||
s.id.clone()
|
||||
} else {
|
||||
format!("{}.tar.xz", s.id)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let actual: BTreeSet<_> = fs::read_dir(&bundles)?
|
||||
.map(|e| Ok(e?.file_name().to_string_lossy().into_owned()))
|
||||
.collect::<Result<_>>()?;
|
||||
if actual != expected {
|
||||
if actual != expected || fs::read_dir(&path)?.count() != 1 {
|
||||
return Err(Error(
|
||||
"Payload archive inventory disagrees with catalogue".into(),
|
||||
"Payload software inventory disagrees with catalogue".into(),
|
||||
));
|
||||
}
|
||||
for software in result
|
||||
@@ -173,6 +190,10 @@ impl Mounted {
|
||||
.iter()
|
||||
.filter(|s| s.partition == spec.number)
|
||||
{
|
||||
if software.installed {
|
||||
tree::verify(&Path::new(&path).join(software.root_path()), software)?;
|
||||
continue;
|
||||
}
|
||||
if archive::open(&Path::new(&path).join(software.archive_path()))?
|
||||
.metadata()?
|
||||
.len()
|
||||
@@ -226,6 +247,32 @@ impl Mounted {
|
||||
if media::key(&payload.partition)? != payload.key {
|
||||
return Err(Error("Software payload was removed".into()));
|
||||
}
|
||||
if software.installed {
|
||||
let root = Path::new(&payload.path).join(software.root_path());
|
||||
let mut args = tree::executable(&root, executable)?;
|
||||
args.extend_from_slice(&arguments[1..]);
|
||||
let root = root.display().to_string();
|
||||
return Ok((
|
||||
args,
|
||||
vec![
|
||||
("FDS_APP".into(), root.clone()),
|
||||
(
|
||||
"PATH".into(),
|
||||
format!("{root}/usr/bin:{root}/bin:/usr/bin:/bin:/run/fds/bin"),
|
||||
),
|
||||
(
|
||||
"LD_LIBRARY_PATH".into(),
|
||||
format!("{root}/usr/lib:{root}/lib"),
|
||||
),
|
||||
(
|
||||
"XDG_DATA_DIRS".into(),
|
||||
format!("{root}/usr/share:/usr/share"),
|
||||
),
|
||||
("DISPLAY".into(), ":0".into()),
|
||||
("XAUTHORITY".into(), "/run/fds/x11/authority".into()),
|
||||
],
|
||||
));
|
||||
}
|
||||
if !self.caches.contains_key(id) {
|
||||
// Each executable tree receives its own bounded, read-only tmpfs.
|
||||
// Root owns every path; the consumer only receives ordinary UID 1000.
|
||||
|
||||
Reference in New Issue
Block a user