login: hand the shell login's validated cwd, not raw home

The pre-exec child runs chdir(home) before execvp; chdir(/home/user) fails with
ENOENT on the live image (open succeeds, chdir does not) which aborted the whole
spawn before execve - the real reason the login shell never started. Use login's
already-validated cwd (home if reachable, else /). Diagnostics retained.
This commit is contained in:
2026-07-18 21:26:27 +09:00
parent 02a86400cc
commit fbbb1d88cb
2 changed files with 41 additions and 31 deletions
+5 -1
View File
@@ -166,7 +166,11 @@ pub fn main() {
// this chdir here, while still in the full namespace, points
// the CWD at `file:` (home, fallback `/`), which remains
// valid inside the restricted login namespace.
if std::env::set_current_dir(&user.home).is_err() {
if let Err(e) = std::env::set_current_dir(&user.home) {
eprintln!(
"login-diag: chdir home {:?} FAILED: {:?}",
user.home, e
);
let _ = std::env::set_current_dir("/");
}
+36 -30
View File
@@ -130,39 +130,45 @@ pub fn spawn_shell<T: Default>(user: &User<T>) -> IoResult<i32> {
}
}
eprintln!("login-diag: cwd={:?}", std::env::current_dir());
eprintln!(
"login-diag: open home {:?} = {:?}",
user.home,
std::fs::File::open(&user.home).map(|_| "OK")
);
eprintln!(
"login-diag: open shell {:?} = {:?}",
user.shell,
std::fs::File::open(&user.shell).map(|_| "OK")
);
eprintln!(
"login-diag: open /usr/bin = {:?}",
std::fs::File::open("/usr/bin").map(|_| "OK")
);
let mut command = user.shell_cmd();
// The relibc Redox spawn does not reliably carry inherited stdio (fds 0/1/2)
// to the child through filetable inheritance for a login shell: the fds
// arrive disconnected, so the shell has no console, reads EOF immediately,
// exits, and getty re-spawns login in a tight loop. getty itself sidesteps
// this by handing `login` its console fds *explicitly* as Stdio (see
// getty.rs), which the spawn lowers to dup2 file-actions that actually
// connect the child's descriptors. Do the same for the shell: duplicate the
// current console fds and pass them explicitly. Stdio takes ownership of the
// dups and closes them after the spawn; login keeps its own 0/1/2.
#[cfg(target_os = "redox")]
unsafe {
use std::os::fd::{FromRawFd, RawFd};
use std::process::Stdio;
let din = libc::dup(0);
let dout = libc::dup(1);
let derr = libc::dup(2);
if din >= 0 && dout >= 0 && derr >= 0 {
command
.stdin(Stdio::from_raw_fd(din as RawFd))
.stdout(Stdio::from_raw_fd(dout as RawFd))
.stderr(Stdio::from_raw_fd(derr as RawFd));
} else {
for d in [din, dout, derr] {
if d >= 0 {
libc::close(d);
}
}
}
// redox_users' shell_cmd() forces current_dir(home). Rust's pre-exec child
// runs chdir(home) before execvp; if that chdir fails (observed on the live
// image: chdir("/home/user") errors even though the directory opens fine),
// the whole spawn aborts with the chdir errno BEFORE ever reaching execve —
// which is why the login shell never started. login has already established
// a valid working directory (the user's home if reachable, otherwise "/"),
// so hand the shell THAT validated cwd instead of the raw home path.
if let Ok(cwd) = std::env::current_dir() {
command.current_dir(cwd);
}
eprintln!("login-diag: spawning shell {:?}", user.shell);
let mut child = command.spawn()?;
let mut child = match command.spawn() {
Ok(c) => {
eprintln!("login-diag: spawn OK");
c
}
Err(e) => {
eprintln!("login-diag: spawn ERR {:?}", e);
return Err(e);
}
};
match child.wait()?.code() {
Some(code) => Ok(code),
None => Ok(1),