proc/fs: allow refreshing filetable data via dup2 (fix O_CLOEXEC / stale inherited fds)

Backport of upstream kernel 37ffa2e2. The proc scheme's filetable dup2
only accepted 'copy'; a 'refresh' buffer now re-materializes the
filetable's descriptor list in place. dup2() no longer short-circuits a
self-dup (fd==new_fd) when a buffer is supplied, and duplicates before
replacing the target descriptor. Together with relibc's
FdTbl::from_binary_fd issuing dup2(ft, ft, 'refresh'), an exec'd process
(e.g. a login shell) rebuilds its fd table from the CURRENT parent state
instead of a stale snapshot -- fixing interactive shells whose pty-slave
stdin returned nothing while writes to fd 1/2 worked. fs.rs dup2 adapted
to the local duplicate_file(cloexec) signature.
This commit is contained in:
2026-07-18 14:58:46 +09:00
parent 229046c634
commit 72be9f9f46
2 changed files with 82 additions and 26 deletions
+51 -17
View File
@@ -1228,25 +1228,59 @@ impl KernelScheme for ProcScheme {
} => {
// TODO: Maybe allow userspace to either copy or transfer recently dupped file
// descriptors between file tables.
if buf != b"copy" {
return Err(Error::new(EINVAL));
}
let filetable = filetable.upgrade().ok_or(Error::new(EOWNERDEAD))?;
let new_handle = match buf {
b"copy" => {
let filetable = filetable.upgrade().ok_or(Error::new(EOWNERDEAD))?;
let new_filetable =
Arc::new(RwLock::new(filetable.read(token.token()).clone()));
let new_filetable =
Arc::new(RwLock::new(filetable.read(token.token()).clone()));
handle(
Handle {
kind: ContextHandle::NewFiletable {
filetable: new_filetable,
binary_format,
data: data.clone(),
},
context,
},
true,
)
Handle {
kind: ContextHandle::NewFiletable {
filetable: new_filetable,
binary_format,
data: data.clone(),
},
context,
}
}
b"refresh" => {
let filetable = filetable.upgrade().ok_or(Error::new(EOWNERDEAD))?;
let new_data = if binary_format {
let mut data = Vec::new();
for index in filetable
.read(token.token())
.enumerate()
.filter_map(|(idx, val)| val.as_ref().map(|_| idx))
{
data.extend((index as u64).to_le_bytes());
}
data.into_boxed_slice()
} else {
use core::fmt::Write;
let mut data = String::new();
for index in filetable
.read(token.token())
.enumerate()
.filter_map(|(idx, val)| val.as_ref().map(|_| idx))
{
writeln!(data, "{}", index).unwrap();
}
data.into_bytes().into_boxed_slice()
};
Handle {
kind: ContextHandle::Filetable {
filetable: Arc::downgrade(&filetable),
binary_format,
data: new_data,
},
context,
}
}
_ => return Err(Error::new(EINVAL)),
};
handle(new_handle, true)
}
Handle {
kind: ContextHandle::AddrSpace { ref addrspace },
+31 -9
View File
@@ -311,19 +311,41 @@ pub fn dup2(
buf: UserSliceRo,
token: &mut CleanLockToken,
) -> Result<FileHandle> {
if fd == new_fd {
Ok(new_fd)
} else {
let _ = close(new_fd, token);
let new_file = duplicate_file(fd, buf, false, token)?;
// A self-dup2 (fd == new_fd) with an EMPTY buffer is a no-op. But a
// self-dup2 WITH a buffer is meaningful — e.g. relibc issues dup2(ft, ft,
// "refresh") to re-materialize the inherited filetable — so it must fall
// through to duplicate_file() rather than short-circuiting.
if fd == new_fd && buf.is_empty() {
return Ok(new_fd);
}
// Duplicate FIRST (fd may equal new_fd), then replace new_fd's descriptor.
let new_file = duplicate_file(fd, buf, false, token)?;
let old_file = {
let current_lock = context::current();
let mut current = current_lock.read(token.token());
let (context, mut token) = current.token_split();
context
.insert_file(new_fd, new_file, &mut token)
.ok_or(Error::new(EMFILE))
let (context, mut split_token) = current.token_split();
let old_file = context.remove_file(new_fd, &mut split_token);
if context
.insert_file(new_fd, new_file, &mut split_token)
.is_none()
{
if let Some(old) = old_file {
context.insert_file(new_fd, old, &mut split_token);
}
return Err(Error::new(EMFILE));
}
old_file
};
if let Some(old) = old_file {
let _ = old.close(token);
}
Ok(new_fd)
}
pub fn call(
fd: FileHandle,