From 1d551905fce9fbfe5dc1e0027fff7425f7d8735f Mon Sep 17 00:00:00 2001 From: Weidong Cui Date: Wed, 21 Oct 2026 22:14:10 -0700 Subject: [PATCH 1/4] Resolve /proc/self/fd links through the broker Record each broker-opened file's absolute path, with symbolic links resolved, on its File and expose it through a new handle-based HandlePath file request. The Linux shim uses it to answer readlink of /proc/self/fd/N for files and names non-file descriptors by kind, so ttyname() on /dev/tty no longer hits unimplemented!(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1172b488-1351-40c1-a6d0-32f5277af568 --- litebox/src/broker/mod.rs | 13 ++++ litebox/src/fs/errors.rs | 10 +++ litebox/src/fs/file.rs | 22 ++++++- litebox_broker_core/src/fs/mod.rs | 6 +- litebox_broker_core/src/fs/resolver.rs | 60 ++++++++++++------ litebox_broker_core/src/fs/service.rs | 23 +++++-- litebox_broker_core/src/fs/tests.rs | 50 +++++++++++++++ litebox_broker_host/src/lib.rs | 74 +++++++++++++++++++++-- litebox_broker_local/src/fs.rs | 39 ++++++++++-- litebox_broker_protocol/src/fs.rs | 9 +++ litebox_broker_protocol/src/message.rs | 18 ++++-- litebox_broker_protocol/src/wire.rs | 17 ++++-- litebox_broker_protocol/src/wire/fs.rs | 31 ++++++++-- litebox_common_linux/src/errno/mod.rs | 10 +++ litebox_runner_linux_userland/tests/tty.c | 2 + litebox_shim_linux/src/syscalls/file.rs | 64 +++++++++++++------- litebox_shim_linux/src/syscalls/tests.rs | 58 ++++++++++++++++++ 17 files changed, 425 insertions(+), 81 deletions(-) diff --git a/litebox/src/broker/mod.rs b/litebox/src/broker/mod.rs index 973abc157e..15afab3466 100644 --- a/litebox/src/broker/mod.rs +++ b/litebox/src/broker/mod.rs @@ -450,6 +450,11 @@ pub(crate) trait BrokerControl: Send + Sync { handle: ObjectHandle, ) -> core::result::Result, BrokerControlError>; + fn handle_file_path( + &self, + handle: ObjectHandle, + ) -> core::result::Result, BrokerControlError>; + fn is_terminal_file( &self, handle: ObjectHandle, @@ -1431,6 +1436,14 @@ where self.request(|local| local.handle_file_status(handle)) } + fn handle_file_path( + &self, + handle: ObjectHandle, + ) -> core::result::Result, BrokerControlError> { + let lease = self.acquire_shared_buffer(SHARED_BUFFER_SLOT_SIZE as usize)?; + self.request(|local| local.handle_file_path(handle, lease.sequence())) + } + fn is_terminal_file( &self, handle: ObjectHandle, diff --git a/litebox/src/fs/errors.rs b/litebox/src/fs/errors.rs index ab0fed4106..95f275c8f3 100644 --- a/litebox/src/fs/errors.rs +++ b/litebox/src/fs/errors.rs @@ -238,6 +238,16 @@ pub enum WalkError { PathError(#[from] PathError), } +/// Possible errors from [`super::File::path`]. +#[non_exhaustive] +#[derive(Error, Debug)] +pub enum FilePathError { + #[error("fd has been closed already")] + ClosedFd, + #[error("I/O error")] + Io, +} + /// Possible errors from [`super::File::is_terminal`]. #[non_exhaustive] #[derive(Error, Debug)] diff --git a/litebox/src/fs/file.rs b/litebox/src/fs/file.rs index ea270e9ec7..afef5bba2e 100644 --- a/litebox/src/fs/file.rs +++ b/litebox/src/fs/file.rs @@ -29,9 +29,9 @@ use crate::{LiteBox, sync}; use litebox_platform::buffer::{Buffer, ReadBuffer, WriteBuffer}; use super::errors::{ - ChmodError, ChownError, FileStatusError, IsTerminalError, MkdirError, OpenError, PathError, - ReadDirError, ReadError, RmdirError, SeekError, StatusFlagsError, TerminalControlError, - TruncateError, UnlinkError, WalkError, WriteError, + ChmodError, ChownError, FilePathError, FileStatusError, IsTerminalError, MkdirError, OpenError, + PathError, ReadDirError, ReadError, RmdirError, SeekError, StatusFlagsError, + TerminalControlError, TruncateError, UnlinkError, WalkError, WriteError, }; impl LiteBox { @@ -421,6 +421,16 @@ impl File { .map_err(FileStatusError::from) } + /// Returns the absolute path of the file, with symbolic links resolved, as of when it was + /// opened. + /// + /// Later renames or removals of the file are not reflected. + pub fn path(&self) -> Result { + self.broker + .handle_file_path(self.handle)? + .map_err(|_| FilePathError::Io) + } + /// Determines whether the file is connected to a terminal. pub fn is_terminal(&self) -> Result { self.broker @@ -637,6 +647,12 @@ impl From for FileStatusError { } } +impl From for FilePathError { + fn from(error: BrokerControlError) -> Self { + broker_fd_error(error, Self::ClosedFd, Self::Io, Self::Io) + } +} + impl From for IsTerminalError { fn from(error: BrokerControlError) -> Self { broker_fd_error(error, Self::ClosedFd, Self::Io, Self::Io) diff --git a/litebox_broker_core/src/fs/mod.rs b/litebox_broker_core/src/fs/mod.rs index a17922a106..aabb3d6278 100644 --- a/litebox_broker_core/src/fs/mod.rs +++ b/litebox_broker_core/src/fs/mod.rs @@ -33,9 +33,9 @@ pub use litebox_broker_protocol::fs::{ }; pub(crate) use service::{File, get_status_flags, open_without_reference, set_status_flags}; pub use service::{ - FileResult, FileService, UnsupportedFileService, chmod, chown, handle_status, is_terminal, - mkdir, open, path_status, read, read_directory, read_link, resolve_path, rmdir, seek, - terminal_control, truncate, unlink, write, + FileResult, FileService, UnsupportedFileService, chmod, chown, handle_path, handle_status, + is_terminal, mkdir, open, path_status, read, read_directory, read_link, resolve_path, rmdir, + seek, terminal_control, truncate, unlink, write, }; bitflags! { diff --git a/litebox_broker_core/src/fs/resolver.rs b/litebox_broker_core/src/fs/resolver.rs index c928ea2ff9..13a0569894 100644 --- a/litebox_broker_core/src/fs/resolver.rs +++ b/litebox_broker_core/src/fs/resolver.rs @@ -97,6 +97,16 @@ struct ResolvedPath { } impl ResolvedPath { + /// The path as an absolute string. + fn to_absolute(&self) -> String { + if self.components.is_empty() { + return String::from("/"); + } + self.components + .iter() + .fold(String::new(), |path, component| path + "/" + component) + } + fn parent_and_name(&self) -> Option<(Vec<&str>, &str)> { let (name, parent) = self.components.split_last()?; Some((parent.iter().map(String::as_str).collect(), name.as_str())) @@ -130,14 +140,9 @@ impl Resolver Result { - let path = self.resolve_path(&Context::new(user), path, true, false)?; - if path.components.is_empty() { - return Ok(String::from("/")); - } - Ok(path - .components - .iter() - .fold(String::new(), |path, component| path + "/" + component)) + Ok(self + .resolve_path(&Context::new(user), path, true, false)? + .to_absolute()) } // XXX(jayb): callers rewalk the result, so this is non-atomic and walks twice; returning the @@ -574,9 +579,22 @@ impl Resolver Result, OpenError> { + self.open_with_path(user, path, flags, mode) + .map(|(entry, _)| entry) + } + + /// Opens a file like [`Self::open`], also returning the absolute path of the opened object, with + /// symbolic links resolved. + pub(crate) fn open_with_path( + &self, + user: UserInfo, + path: &str, + mut flags: OFlags, + mode: Mode, + ) -> Result<(ResolverEntry, String), OpenError> { const CURRENTLY_SUPPORTED_OFLAGS: OFlags = OFlags::CREAT .union(OFlags::RDONLY) .union(OFlags::WRONLY) @@ -618,16 +636,20 @@ impl Resolver = Result>; /// Behaviorless type-erasure envelope for one broker-owned [`ResolverEntry`]. /// /// This does not introduce another open-file-description layer. The erased resolver entry remains -/// the only per-open semantic state. The surrounding lock serializes shared position and append +/// the only mutable per-open state. The surrounding lock serializes shared position and append /// updates across duplicated broker references, while operations that cannot update position use /// shared access so blocking device I/O does not prevent status and other stateless operations. #[derive(Clone)] @@ -42,6 +42,10 @@ pub struct File { /// Readiness of a file whose reads or writes can fail with `WouldBlock`. readiness: Option>, terminal: Option>, + /// Absolute path of the file, with symbolic links resolved, as of when it was opened. + /// + /// Later renames or removals of the file are not reflected. + path: Arc, } impl File { @@ -234,16 +238,18 @@ where mode: FileMode, ) -> ServiceResult { let flags = open_flags(access, flags)?; - let entry = match Resolver::open(self, user, path, flags, mode & FileMode::SUPPORTED) { - Ok(entry) => entry, - Err(error) => return Ok(Err(file_open_error(error))), - }; + let (entry, path) = + match Resolver::open_with_path(self, user, path, flags, mode & FileMode::SUPPORTED) { + Ok(opened) => opened, + Err(error) => return Ok(Err(file_open_error(error))), + }; let readiness = Resolver::readiness_source(self, &entry); let terminal = Resolver::terminal(self, &entry); Ok(Ok(File { state: Arc::new(RwLock::::new(entry)), readiness, terminal, + path: path.into(), })) } @@ -622,6 +628,13 @@ pub fn handle_status( process.core.fs.handle_status(&file) } +/// Returns the absolute path of a broker-owned open object, with symbolic links resolved, as of +/// when it was opened. +pub fn handle_path(process: &BrokerProcess, handle: ObjectHandle) -> Result> { + let file = file_with_any_rights(process, handle, ObjectRights::WAIT | ObjectRights::WRITE)?; + Ok(Ok(String::from(&*file.path))) +} + /// Returns whether a broker-owned open object refers to a terminal, which it does exactly when it /// has a terminal endpoint. pub fn is_terminal(process: &BrokerProcess, handle: ObjectHandle) -> Result> { diff --git a/litebox_broker_core/src/fs/tests.rs b/litebox_broker_core/src/fs/tests.rs index 8754cf48a9..86427318f2 100644 --- a/litebox_broker_core/src/fs/tests.rs +++ b/litebox_broker_core/src/fs/tests.rs @@ -933,6 +933,56 @@ mod in_mem { .expect("Failed to open file for reading"); assert_eq!(read_all(&fs, &mut fd), b"NewContent"); } + + #[test] + fn open_returns_symlink_resolved_path() { + use crate::fs::in_mem::InitialNode; + use alloc::string::String; + + const ALL_PERMS: Mode = Mode::RWXU.union(Mode::RWXG).union(Mode::RWXO); + let directory = || InitialNode::Directory { + mode: ALL_PERMS, + owner: ROOT, + }; + let symlink = |target: &str| InitialNode::Symlink { + mode: ALL_PERMS, + owner: ROOT, + target: target.into(), + }; + let fs: InMemFs = Resolver::new(InMem::new_initialized([ + ("/", directory()), + ("/dir", directory()), + ( + "/dir/file", + InitialNode::File { + mode: ALL_PERMS, + owner: ROOT, + data: (&[][..]).into(), + }, + ), + ("/dir/file-link", symlink("file")), + ("/dir-link", symlink("/dir")), + ("/dangling", symlink("dir/created")), + ])); + let path = |path: &str, flags: OFlags| -> String { + fs.open_with_path(USER, path, flags, Mode::RWXU) + .unwrap_or_else(|error| panic!("open {path:?} failed: {error:?}")) + .1 + }; + + assert_eq!(path("/", OFlags::RDONLY), "/"); + assert_eq!(path("/dir-link", OFlags::RDONLY), "/dir"); + assert_eq!(path("/dir-link/file-link", OFlags::RDONLY), "/dir/file"); + assert_eq!(path("/dir/../dir-link/./file", OFlags::RDONLY), "/dir/file"); + assert_eq!( + path("/dir-link/file-link", OFlags::PATH | OFlags::NOFOLLOW), + "/dir/file-link" + ); + assert_eq!( + path("/dangling", OFlags::CREAT | OFlags::WRONLY), + "/dir/created" + ); + } } mod tar_ro { diff --git a/litebox_broker_host/src/lib.rs b/litebox_broker_host/src/lib.rs index 0d73f9eb96..5a3dd47b80 100644 --- a/litebox_broker_host/src/lib.rs +++ b/litebox_broker_host/src/lib.rs @@ -33,12 +33,12 @@ use litebox_broker_protocol::error::ErrorCode; use litebox_broker_protocol::event::{AddEventResponse, CreateEventResponse}; use litebox_broker_protocol::fs::{ ChmodFileRequest, ChownFileRequest, DirectoryPayloadError, DirectoryTransferError, FileError, - FilePathResponse, HandleFileStatusRequest, IsTerminalFileRequest, MAX_FILE_TRANSFER_SIZE, - MkdirFileRequest, OpenFileRequest, OpenFileResponse, PathFileStatusRequest, - ReadDirectoryRequest, ReadDirectoryResponse, ReadFileRequest, ReadFileResponse, - ReadLinkFileRequest, ResolvePathFileRequest, RmdirFileRequest, SeekFileRequest, - SeekFileResponse, TerminalControlFileRequest, TruncateFileRequest, UnlinkFileRequest, - WriteFileRequest, WriteFileResponse, encode_directory_entries_chunk, + FilePathResponse, HandleFilePathRequest, HandleFileStatusRequest, IsTerminalFileRequest, + MAX_FILE_TRANSFER_SIZE, MkdirFileRequest, OpenFileRequest, OpenFileResponse, + PathFileStatusRequest, ReadDirectoryRequest, ReadDirectoryResponse, ReadFileRequest, + ReadFileResponse, ReadLinkFileRequest, ResolvePathFileRequest, RmdirFileRequest, + SeekFileRequest, SeekFileResponse, TerminalControlFileRequest, TruncateFileRequest, + UnlinkFileRequest, WriteFileRequest, WriteFileResponse, encode_directory_entries_chunk, }; use litebox_broker_protocol::message::{ BrokerHandshakeResponse, BrokerOperation, BrokerRequest, BrokerResponse, BrokerResult, @@ -833,6 +833,17 @@ fn handle_file_request( Err(error) => Ok(FileResponse::Failed(error)), } } + FileRequest::HandlePath(HandleFilePathRequest { handle, buffer }) => { + validate_shared_buffer(buffer, SHARED_BUFFER_SLOT_SIZE)?; + let path = litebox_broker_core::fs::handle_path(process, handle) + .map_err(RequestFailure::from)?; + Ok( + match write_file_path_response(shared_buffers, buffer, path)? { + Ok(response) => FileResponse::HandlePath(response), + Err(error) => FileResponse::Failed(error), + }, + ) + } FileRequest::IsTerminal(IsTerminalFileRequest { handle }) => { match litebox_broker_core::fs::is_terminal(process, handle) .map_err(RequestFailure::from)? @@ -2323,6 +2334,7 @@ mod tests { association_shared_buffer_sequence_stages_random_data(&broker); association_shared_buffer_sequences_stage_ready_events(&broker); active_request_queries_file_terminal(&broker); + active_request_reads_file_handle_path(&broker); active_requests_change_file_status_flags(&broker); association_shared_buffer_sequences_stage_file_data(&broker); shared_buffer_usage_rejects_invalid_sequences(); @@ -2716,6 +2728,56 @@ mod tests { ); } + fn active_request_reads_file_handle_path(broker: &BrokerCore) { + let process = broker + .create_process(CallerCredential::Unauthenticated, None) + .unwrap(); + let shared_buffers = test_shared_buffers(); + let path = b"/dev/./stdout"; + shared_buffers + .write(SharedBufferSlotIndex(0), path) + .unwrap(); + let BrokerResult::File(FileResponse::Open(opened)) = handle_test_request_with_buffers( + &process, + BrokerOperation::File(FileRequest::Open(OpenFileRequest { + path: single_slot_sequence(0, u32::try_from(path.len()).unwrap()), + user: ROOT, + access: FileAccessMode::WriteOnly, + flags: FileOpenFlags::NONE, + mode: FileMode::default(), + })), + &shared_buffers, + ) else { + panic!("expected successful file open"); + }; + let handle_path = |length| { + handle_test_request_with_buffers( + &process, + BrokerOperation::File(FileRequest::HandlePath(HandleFilePathRequest { + handle: opened.handle, + buffer: single_slot_sequence(1, length), + })), + &shared_buffers, + ) + }; + + assert_eq!( + handle_path(64), + BrokerResult::File(FileResponse::HandlePath(FilePathResponse { length: 11 })) + ); + let mut output = [0; 11]; + shared_buffers + .read(SharedBufferSlotIndex(1), &mut output) + .unwrap(); + assert_eq!(output, *b"/dev/stdout"); + assert_eq!( + handle_path(4), + BrokerResult::File(FileResponse::Failed(FileError::Io)) + ); + + process.close_object_reference(opened.handle).unwrap(); + } + fn active_requests_change_file_status_flags(broker: &BrokerCore) { let process = broker .create_process(CallerCredential::Unauthenticated, None) diff --git a/litebox_broker_local/src/fs.rs b/litebox_broker_local/src/fs.rs index 1719a5e27e..72ffd4abb5 100644 --- a/litebox_broker_local/src/fs.rs +++ b/litebox_broker_local/src/fs.rs @@ -9,11 +9,11 @@ use litebox_broker_protocol::error::ErrorCode; use litebox_broker_protocol::fs::{ ChmodFileRequest, ChownFileRequest, DirectoryPayloadError, DirectoryTransferError, FileAccessMode, FileDirectoryEntry, FileError, FileMode, FileOpenFlags, FilePathResponse, - FileSeekWhence, FileStatus, FileUser, HandleFileStatusRequest, IsTerminalFileRequest, - MAX_FILE_TRANSFER_SIZE, MkdirFileRequest, OpenFileRequest, PathFileStatusRequest, - ReadDirectoryRequest, ReadFileRequest, ReadLinkFileRequest, ResolvePathFileRequest, - RmdirFileRequest, SeekFileRequest, TerminalControlFileRequest, TruncateFileRequest, - UnlinkFileRequest, WriteFileRequest, try_decode_directory_entries, + FileSeekWhence, FileStatus, FileUser, HandleFilePathRequest, HandleFileStatusRequest, + IsTerminalFileRequest, MAX_FILE_TRANSFER_SIZE, MkdirFileRequest, OpenFileRequest, + PathFileStatusRequest, ReadDirectoryRequest, ReadFileRequest, ReadLinkFileRequest, + ResolvePathFileRequest, RmdirFileRequest, SeekFileRequest, TerminalControlFileRequest, + TruncateFileRequest, UnlinkFileRequest, WriteFileRequest, try_decode_directory_entries, }; use litebox_broker_protocol::message::{BrokerOperation, BrokerResult, FileRequest, FileResponse}; use litebox_broker_protocol::shared_buffer::{SHARED_BUFFER_SLOT_SIZE, SharedBufferSequence}; @@ -492,6 +492,35 @@ impl BrokerLocal { } } + /// Reads the absolute path of an open file, with symbolic links resolved, as of when it was + /// opened. + /// + /// # Panics + /// + /// Panics if the buffer sequence is inconsistent or the broker returns + /// a response for another file operation or an invalid path. + pub fn handle_file_path( + &self, + handle: ObjectHandle, + buffer: SharedBufferSequence, + ) -> Result, Channel::Error> { + if buffer.length() > SHARED_BUFFER_SLOT_SIZE { + return Err(BrokerLocalError::Broker(ErrorCode::ResourceExhausted)); + } + match self.request_file(FileRequest::HandlePath(HandleFilePathRequest { + handle, + buffer, + }))? { + FileResponse::HandlePath(response) => { + Ok(Ok(self.read_path_response_buffer(buffer, response)?)) + } + FileResponse::Failed(error) => Ok(Err(error)), + response => { + panic!("broker returned unexpected file handle-path response: {response:?}") + } + } + } + fn write_path_query_buffer( &self, buffer: SharedBufferSequence, diff --git a/litebox_broker_protocol/src/fs.rs b/litebox_broker_protocol/src/fs.rs index aa644965b9..73af1acdb5 100644 --- a/litebox_broker_protocol/src/fs.rs +++ b/litebox_broker_protocol/src/fs.rs @@ -487,6 +487,15 @@ pub struct HandleFileStatusRequest { pub handle: ObjectHandle, } +/// Reads the absolute path of an open file, with symbolic links resolved, as of when it was opened. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct HandleFilePathRequest { + /// Broker-owned file handle. + pub handle: ObjectHandle, + /// Shared-buffer region the broker writes the absolute path to. + pub buffer: SharedBufferSequence, +} + /// Determines whether an open file is connected to a terminal. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct IsTerminalFileRequest { diff --git a/litebox_broker_protocol/src/message.rs b/litebox_broker_protocol/src/message.rs index 816b7343ae..f258a5b0c0 100644 --- a/litebox_broker_protocol/src/message.rs +++ b/litebox_broker_protocol/src/message.rs @@ -8,12 +8,12 @@ use crate::event::{ }; use crate::fs::{ ChmodFileRequest, ChownFileRequest, FileError, FilePathResponse, FileStatus, FileStatusFlags, - HandleFileStatusRequest, IsTerminalFileRequest, MkdirFileRequest, OpenFileRequest, - OpenFileResponse, PathFileStatusRequest, ReadDirectoryRequest, ReadDirectoryResponse, - ReadFileRequest, ReadFileResponse, ReadLinkFileRequest, ResolvePathFileRequest, - RmdirFileRequest, SeekFileRequest, SeekFileResponse, SetStatusFlagsRequest, - TerminalControlFileRequest, TruncateFileRequest, UnlinkFileRequest, WriteFileRequest, - WriteFileResponse, + HandleFilePathRequest, HandleFileStatusRequest, IsTerminalFileRequest, MkdirFileRequest, + OpenFileRequest, OpenFileResponse, PathFileStatusRequest, ReadDirectoryRequest, + ReadDirectoryResponse, ReadFileRequest, ReadFileResponse, ReadLinkFileRequest, + ResolvePathFileRequest, RmdirFileRequest, SeekFileRequest, SeekFileResponse, + SetStatusFlagsRequest, TerminalControlFileRequest, TruncateFileRequest, UnlinkFileRequest, + WriteFileRequest, WriteFileResponse, }; use crate::pipe::{ CreatePipeRequest, CreatePipeResponse, ReadPipeRequest, ReadPipeResponse, WritePipeRequest, @@ -129,6 +129,7 @@ impl BrokerOperation { | FileRequest::Unlink(UnlinkFileRequest { path: buffer, .. }) | FileRequest::Mkdir(MkdirFileRequest { path: buffer, .. }) | FileRequest::Rmdir(RmdirFileRequest { path: buffer, .. }) + | FileRequest::HandlePath(HandleFilePathRequest { buffer, .. }) | FileRequest::ReadLink(ReadLinkFileRequest { buffer, .. }) | FileRequest::ResolvePath(ResolvePathFileRequest { buffer, .. }), ) @@ -733,6 +734,9 @@ pub enum FileRequest { PathStatus(PathFileStatusRequest), /// Read status by open handle. HandleStatus(HandleFileStatusRequest), + /// Read the absolute path of an open file, with symbolic links resolved, as of when it was + /// opened. + HandlePath(HandleFilePathRequest), /// Determine whether an open file is connected to a terminal. IsTerminal(IsTerminalFileRequest), /// Control the shared terminal backing an open file. @@ -772,6 +776,8 @@ pub enum FileResponse { PathStatus(FileStatus), /// Handle-status response. HandleStatus(FileStatus), + /// Handle-path response. + HandlePath(FilePathResponse), /// Terminal query response. IsTerminal(bool), /// Terminal-control response. diff --git a/litebox_broker_protocol/src/wire.rs b/litebox_broker_protocol/src/wire.rs index 9e8860ec65..cff28306a6 100644 --- a/litebox_broker_protocol/src/wire.rs +++ b/litebox_broker_protocol/src/wire.rs @@ -627,12 +627,12 @@ mod tests { use crate::fs::{ ChmodFileRequest, ChownFileRequest, FileAccessMode, FileError, FileMode, FileNodeInfo, FileOpenFlags, FilePathResponse, FileSeekWhence, FileStatus, FileStatusFlags, FileType, - FileUser, HandleFileStatusRequest, IsTerminalFileRequest, MkdirFileRequest, - OpenFileRequest, OpenFileResponse, PathFileStatusRequest, ReadDirectoryRequest, - ReadDirectoryResponse, ReadFileRequest, ReadFileResponse, ReadLinkFileRequest, - ResolvePathFileRequest, RmdirFileRequest, SeekFileRequest, SeekFileResponse, - SetStatusFlagsRequest, TerminalControlFileRequest, TruncateFileRequest, UnlinkFileRequest, - WriteFileRequest, WriteFileResponse, + FileUser, HandleFilePathRequest, HandleFileStatusRequest, IsTerminalFileRequest, + MkdirFileRequest, OpenFileRequest, OpenFileResponse, PathFileStatusRequest, + ReadDirectoryRequest, ReadDirectoryResponse, ReadFileRequest, ReadFileResponse, + ReadLinkFileRequest, ResolvePathFileRequest, RmdirFileRequest, SeekFileRequest, + SeekFileResponse, SetStatusFlagsRequest, TerminalControlFileRequest, TruncateFileRequest, + UnlinkFileRequest, WriteFileRequest, WriteFileResponse, }; use crate::message::{ EventRequest, EventResponse, FileRequest, FileResponse, PipeRequest, PipeResponse, @@ -988,6 +988,10 @@ mod tests { path_length: 9, user: FileUser { user: 2, group: 3 }, })), + BrokerOperation::File(FileRequest::HandlePath(HandleFilePathRequest { + handle, + buffer: sequence(13, 256), + })), BrokerOperation::Socket(SocketRequest::Create(CreateSocketRequest { address_family: AddressFamily::Ipv4, socket_type: SocketType::Stream, @@ -1704,6 +1708,7 @@ mod tests { BrokerResult::File(FileResponse::Rmdir), BrokerResult::File(FileResponse::ReadLink(FilePathResponse { length: 31 })), BrokerResult::File(FileResponse::ResolvePath(FilePathResponse { length: 37 })), + BrokerResult::File(FileResponse::HandlePath(FilePathResponse { length: 41 })), BrokerResult::File(FileResponse::Failed(FileError::Io)), BrokerResult::Process(ProcessResponse::AllocateChild(ProcessIdentity { process_id: process_id(19), diff --git a/litebox_broker_protocol/src/wire/fs.rs b/litebox_broker_protocol/src/wire/fs.rs index e86b82afbf..ad3985b542 100644 --- a/litebox_broker_protocol/src/wire/fs.rs +++ b/litebox_broker_protocol/src/wire/fs.rs @@ -6,12 +6,12 @@ use core::num::NonZeroU64; use crate::fs::{ ChmodFileRequest, ChownFileRequest, FileAccessMode, FileError, FileMode, FileNodeInfo, FileOpenFlags, FilePathResponse, FileSeekWhence, FileStatus, FileStatusFlags, FileUser, - HandleFileStatusRequest, IsTerminalFileRequest, MkdirFileRequest, OpenFileRequest, - OpenFileResponse, PathFileStatusRequest, ReadDirectoryRequest, ReadDirectoryResponse, - ReadFileRequest, ReadFileResponse, ReadLinkFileRequest, ResolvePathFileRequest, - RmdirFileRequest, SeekFileRequest, SeekFileResponse, SetStatusFlagsRequest, - TerminalControlFileRequest, TruncateFileRequest, UnlinkFileRequest, WriteFileRequest, - WriteFileResponse, file_type_from_raw, file_type_raw, + HandleFilePathRequest, HandleFileStatusRequest, IsTerminalFileRequest, MkdirFileRequest, + OpenFileRequest, OpenFileResponse, PathFileStatusRequest, ReadDirectoryRequest, + ReadDirectoryResponse, ReadFileRequest, ReadFileResponse, ReadLinkFileRequest, + ResolvePathFileRequest, RmdirFileRequest, SeekFileRequest, SeekFileResponse, + SetStatusFlagsRequest, TerminalControlFileRequest, TruncateFileRequest, UnlinkFileRequest, + WriteFileRequest, WriteFileResponse, file_type_from_raw, file_type_raw, }; use crate::message::{FileRequest, FileResponse}; @@ -37,6 +37,7 @@ const REQUEST_TAG_IS_TERMINAL: u8 = 13; const REQUEST_TAG_TERMINAL_CONTROL: u8 = 14; const REQUEST_TAG_READ_LINK: u8 = 15; const REQUEST_TAG_RESOLVE_PATH: u8 = 16; +const REQUEST_TAG_HANDLE_PATH: u8 = 17; const RESPONSE_TAG_OPEN: u8 = 0; const RESPONSE_TAG_READ: u8 = 1; @@ -55,6 +56,7 @@ const RESPONSE_TAG_IS_TERMINAL: u8 = 13; const RESPONSE_TAG_TERMINAL_CONTROL: u8 = 14; const RESPONSE_TAG_READ_LINK: u8 = 15; const RESPONSE_TAG_RESOLVE_PATH: u8 = 16; +const RESPONSE_TAG_HANDLE_PATH: u8 = 17; const RESPONSE_TAG_FAILED: u8 = u8::MAX; const _: () = { @@ -74,6 +76,7 @@ const _: () = { assert!(REQUEST_TAG_IS_TERMINAL == RESPONSE_TAG_IS_TERMINAL); assert!(REQUEST_TAG_READ_LINK == RESPONSE_TAG_READ_LINK); assert!(REQUEST_TAG_RESOLVE_PATH == RESPONSE_TAG_RESOLVE_PATH); + assert!(REQUEST_TAG_HANDLE_PATH == RESPONSE_TAG_HANDLE_PATH); }; pub(super) fn encode_fs_request(encoder: &mut Encoder, request: FileRequest) { @@ -176,6 +179,11 @@ pub(super) fn encode_fs_request(encoder: &mut Encoder, request: FileRequest) { encoder.u32(request.path_length); encode_user(encoder, request.user); } + FileRequest::HandlePath(request) => { + encoder.u8(REQUEST_TAG_HANDLE_PATH); + encoder.handle(request.handle); + encoder.shared_buffer_sequence(request.buffer); + } } } @@ -268,6 +276,10 @@ pub(super) fn decode_fs_request(decoder: &mut Decoder<'_>) -> Result Ok(FileRequest::HandlePath(HandleFilePathRequest { + handle: decoder.handle()?, + buffer: decoder.shared_buffer_sequence()?, + })), _ => Err(WireError::InvalidTag), } } @@ -325,6 +337,10 @@ pub(super) fn encode_fs_response(encoder: &mut Encoder, response: FileResponse) encoder.u8(RESPONSE_TAG_RESOLVE_PATH); encoder.u32(length); } + FileResponse::HandlePath(FilePathResponse { length }) => { + encoder.u8(RESPONSE_TAG_HANDLE_PATH); + encoder.u32(length); + } FileResponse::Failed(error) => { encoder.u8(RESPONSE_TAG_FAILED); encode_file_error(encoder, error); @@ -372,6 +388,9 @@ pub(super) fn decode_fs_response(decoder: &mut Decoder<'_>) -> Result Ok(FileResponse::ResolvePath(FilePathResponse { length: decoder.u32()?, })), + RESPONSE_TAG_HANDLE_PATH => Ok(FileResponse::HandlePath(FilePathResponse { + length: decoder.u32()?, + })), RESPONSE_TAG_FAILED => Ok(FileResponse::Failed(decode_file_error(decoder)?)), _ => Err(WireError::InvalidTag), } diff --git a/litebox_common_linux/src/errno/mod.rs b/litebox_common_linux/src/errno/mod.rs index 384b8de063..47879e0811 100644 --- a/litebox_common_linux/src/errno/mod.rs +++ b/litebox_common_linux/src/errno/mod.rs @@ -422,6 +422,16 @@ impl From for Errno { } } +impl From for Errno { + fn from(value: litebox::fs::errors::FilePathError) -> Self { + match value { + litebox::fs::errors::FilePathError::ClosedFd => Errno::EBADF, + litebox::fs::errors::FilePathError::Io => Errno::EIO, + _ => unimplemented!(), + } + } +} + impl From for Errno { fn from(value: litebox::fs::errors::IsTerminalError) -> Self { match value { diff --git a/litebox_runner_linux_userland/tests/tty.c b/litebox_runner_linux_userland/tests/tty.c index b178ecbf05..c9b1f5e913 100644 --- a/litebox_runner_linux_userland/tests/tty.c +++ b/litebox_runner_linux_userland/tests/tty.c @@ -135,6 +135,8 @@ int main(int argc, char **argv) { // The controlling terminal is the one behind the standard streams. int tty = open("/dev/tty", O_RDWR); assert(tty >= 0 && isatty(tty)); + const char *tty_name = ttyname(tty); + assert(tty_name != NULL && strcmp(tty_name, "/dev/tty") == 0); struct stat tty_status; assert(fstat(tty, &tty_status) == 0 && S_ISCHR(tty_status.st_mode)); assert(major(tty_status.st_rdev) == 5 && minor(tty_status.st_rdev) == 0); diff --git a/litebox_shim_linux/src/syscalls/file.rs b/litebox_shim_linux/src/syscalls/file.rs index 5e177106df..cde3f002ae 100644 --- a/litebox_shim_linux/src/syscalls/file.rs +++ b/litebox_shim_linux/src/syscalls/file.rs @@ -135,6 +135,11 @@ impl File { Ok(self.file.status()?) } + /// Absolute path of the file, with symbolic links resolved, as of when it was opened. + pub(crate) fn path(&self) -> Result { + Ok(self.file.path()?) + } + /// Controls the shared terminal, waiting interruptibly for output-draining operations. pub(crate) fn terminal_control( &self, @@ -1959,19 +1964,37 @@ impl Task { } } + /// The open descriptor named by a `/proc/self/fd/` path, or `None` for any other path. + /// + /// Like Linux, a name that is not an open descriptor fails with `ENOENT`. + // XXX(jayb): `/proc/self/fd/` should be backed by the file system rather than handled here. + fn proc_self_fd(&self, path: &str) -> Option, Errno>> { + let name = path.strip_prefix("/proc/self/fd/")?; + Some( + name.bytes() + .all(|byte| byte.is_ascii_digit()) + .then(|| name.parse::().ok()) + .flatten() + .and_then(|fd| self.typed_fd(fd).ok()) + .ok_or(Errno::ENOENT), + ) + } + /// Read the target of a symbolic link /// /// The caller must pass an absolute path. - // XXX(jayb): `/proc/self/fd/` should be backed by the file system rather than hardcoded. fn do_readlink(&self, fullpath: &str) -> Result { - if let Some(stripped) = fullpath.strip_prefix("/proc/self/fd/") { - let fd = stripped.parse::().map_err(|_| Errno::EINVAL)?; - match fd { - 0 => return Ok("/dev/stdin".to_string()), - 1 => return Ok("/dev/stdout".to_string()), - 2 => return Ok("/dev/stderr".to_string()), - _ => unimplemented!(), - } + if let Some(fd) = self.proc_self_fd(fullpath) { + // Like Linux, a non-file descriptor names its kind; the inode numbers match `fstat`. + return fd?.dispatch( + |fd| self.global.with_file(fd, File::path), + |_fd| Ok("socket:[0]".into()), + |_fd| Ok("pipe:[0]".into()), + |_fd| Ok("anon_inode:[eventfd]".into()), + |_fd| Ok("anon_inode:[eventpoll]".into()), + |_fd| Ok("socket:[0]".into()), + |_fd| Ok("anon_inode:[timerfd]".into()), + ); } let fs = self.fs.borrow(); @@ -2080,24 +2103,21 @@ impl Task { /// Get the file status of `pathname`. /// /// The `pathname` must be absolute. - fn do_path_stat>( - &self, - pathname: &str, - follow_symlink: bool, - ) -> Result { - // `/proc/self/fd/` is not backed by the file system, so it is followed here. - let path = if follow_symlink && pathname.starts_with("/proc/self/fd/") { - self.do_readlink(pathname) - .unwrap_or_else(|_| pathname.into()) - } else { - pathname.into() - }; + fn do_path_stat(&self, pathname: &str, follow_symlink: bool) -> Result + where + T: TryFrom + From, + { + // `/proc/self/fd/` is not backed by the file system; following it reaches the open + // descriptor itself. + if follow_symlink && let Some(fd) = self.proc_self_fd(pathname) { + return self.do_stat(&fd?); + } let status = { let fs = self.fs.borrow(); let context = fs.context.read(); self.global .litebox - .path_file_status(&context, &path, follow_symlink)? + .path_file_status(&context, pathname, follow_symlink)? }; T::try_from(status) } diff --git a/litebox_shim_linux/src/syscalls/tests.rs b/litebox_shim_linux/src/syscalls/tests.rs index c67662abbc..9b14c0f333 100644 --- a/litebox_shim_linux/src/syscalls/tests.rs +++ b/litebox_shim_linux/src/syscalls/tests.rs @@ -954,3 +954,61 @@ fn exec_inherits_only_transferable_descriptors() { .unwrap(); assert_eq!(inherited(), Err(Errno::EAGAIN)); } + +#[test] +fn proc_self_fd_names_open_descriptions() { + let task = init_platform(); + let readlink = |path: &str| { + let mut buffer = [0; 128]; + task.sys_readlink(path, &mut buffer) + .map(|length| std::string::String::from_utf8(buffer[..length].to_vec()).unwrap()) + }; + let link = |fd: u32| readlink(&std::format!("/proc/self/fd/{fd}")); + let stat = |fd: u32| task.sys_stat(&std::format!("/proc/self/fd/{fd}")); + let fstat = |fd: u32| task.sys_fstat(i32::try_from(fd).unwrap()); + + create_directory(&task, "/proc_self_fd"); + create_file(&task, "/proc_self_fd/file", b""); + let file = task + .sys_open( + "/proc_self_fd/../proc_self_fd/./file", + OFlags::RDONLY, + Mode::empty(), + ) + .unwrap(); + assert_eq!(link(file).as_deref(), Ok("/proc_self_fd/file")); + assert_eq!(stat(file), fstat(file)); + let tty = task + .sys_open("/dev/tty", OFlags::RDWR, Mode::empty()) + .unwrap(); + assert_eq!(link(tty).as_deref(), Ok("/dev/tty")); + + let (read_end, write_end) = task.sys_pipe2(OFlags::empty()).unwrap(); + let eventfd = task.sys_eventfd2(0, EfdFlags::empty()).unwrap(); + let epoll = task.sys_epoll_create(EpollCreateFlags::empty()).unwrap(); + for (fd, expected) in [ + (read_end, "pipe:[0]"), + (write_end, "pipe:[0]"), + (eventfd, "anon_inode:[eventfd]"), + (epoll, "anon_inode:[eventpoll]"), + ] { + assert_eq!(link(fd).as_deref(), Ok(expected)); + assert_eq!(stat(fd), fstat(fd)); + } + + // Links follow what each descriptor refers to, including the standard streams. + task.sys_dup(i32::try_from(read_end).unwrap(), Some(0), None) + .unwrap(); + assert_eq!(link(0).as_deref(), Ok("pipe:[0]")); + assert_eq!(stat(0), fstat(0)); + task.sys_dup(i32::try_from(file).unwrap(), Some(1), None) + .unwrap(); + assert_eq!(link(1).as_deref(), Ok("/proc_self_fd/file")); + + // Only open descriptors have links. + task.sys_close(i32::try_from(file).unwrap()).unwrap(); + assert_eq!(link(file), Err(Errno::ENOENT)); + assert_eq!(stat(file), Err(Errno::ENOENT)); + assert_eq!(readlink("/proc/self/fd/+1"), Err(Errno::ENOENT)); + assert_eq!(readlink("/proc/self/fd/x"), Err(Errno::ENOENT)); +} From f3b24dd25d701175e15613f64ed3496fef7384d0 Mon Sep 17 00:00:00 2001 From: Weidong Cui Date: Sun, 25 Oct 2026 07:37:30 -0700 Subject: [PATCH 2/4] Clarify that the shim serves /proc/self/fd Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1172b488-1351-40c1-a6d0-32f5277af568 --- litebox_shim_linux/src/syscalls/file.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litebox_shim_linux/src/syscalls/file.rs b/litebox_shim_linux/src/syscalls/file.rs index cde3f002ae..e2353fb788 100644 --- a/litebox_shim_linux/src/syscalls/file.rs +++ b/litebox_shim_linux/src/syscalls/file.rs @@ -2107,7 +2107,7 @@ impl Task { where T: TryFrom + From, { - // `/proc/self/fd/` is not backed by the file system; following it reaches the open + // The shim, not the file system, serves `/proc/self/fd/`; following it reaches the open // descriptor itself. if follow_symlink && let Some(fd) = self.proc_self_fd(pathname) { return self.do_stat(&fd?); From f844b94febd13a2be682cc90a553a215e354f429 Mon Sep 17 00:00:00 2001 From: Weidong Cui Date: Sun, 25 Oct 2026 07:47:31 -0700 Subject: [PATCH 3/4] Rename Resolver::open_with_path to open_with_path_returned Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1172b488-1351-40c1-a6d0-32f5277af568 --- litebox_broker_core/src/fs/resolver.rs | 4 ++-- litebox_broker_core/src/fs/service.rs | 15 ++++++++++----- litebox_broker_core/src/fs/tests.rs | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/litebox_broker_core/src/fs/resolver.rs b/litebox_broker_core/src/fs/resolver.rs index 13a0569894..e7f58142cc 100644 --- a/litebox_broker_core/src/fs/resolver.rs +++ b/litebox_broker_core/src/fs/resolver.rs @@ -582,13 +582,13 @@ impl Resolver Result, OpenError> { - self.open_with_path(user, path, flags, mode) + self.open_with_path_returned(user, path, flags, mode) .map(|(entry, _)| entry) } /// Opens a file like [`Self::open`], also returning the absolute path of the opened object, with /// symbolic links resolved. - pub(crate) fn open_with_path( + pub(crate) fn open_with_path_returned( &self, user: UserInfo, path: &str, diff --git a/litebox_broker_core/src/fs/service.rs b/litebox_broker_core/src/fs/service.rs index 0f2abe5533..226401dbc4 100644 --- a/litebox_broker_core/src/fs/service.rs +++ b/litebox_broker_core/src/fs/service.rs @@ -238,11 +238,16 @@ where mode: FileMode, ) -> ServiceResult { let flags = open_flags(access, flags)?; - let (entry, path) = - match Resolver::open_with_path(self, user, path, flags, mode & FileMode::SUPPORTED) { - Ok(opened) => opened, - Err(error) => return Ok(Err(file_open_error(error))), - }; + let (entry, path) = match Resolver::open_with_path_returned( + self, + user, + path, + flags, + mode & FileMode::SUPPORTED, + ) { + Ok(opened) => opened, + Err(error) => return Ok(Err(file_open_error(error))), + }; let readiness = Resolver::readiness_source(self, &entry); let terminal = Resolver::terminal(self, &entry); Ok(Ok(File { diff --git a/litebox_broker_core/src/fs/tests.rs b/litebox_broker_core/src/fs/tests.rs index 86427318f2..91d45d4140 100644 --- a/litebox_broker_core/src/fs/tests.rs +++ b/litebox_broker_core/src/fs/tests.rs @@ -965,7 +965,7 @@ mod in_mem { ("/dangling", symlink("dir/created")), ])); let path = |path: &str, flags: OFlags| -> String { - fs.open_with_path(USER, path, flags, Mode::RWXU) + fs.open_with_path_returned(USER, path, flags, Mode::RWXU) .unwrap_or_else(|error| panic!("open {path:?} failed: {error:?}")) .1 }; From dc7cd903c6b57d5944a62b8ae1e3e96e14ab08e2 Mon Sep 17 00:00:00 2001 From: Weidong Cui Date: Sun, 25 Oct 2026 14:21:00 -0700 Subject: [PATCH 4/4] Store the opened path on ResolverEntry Record the resolved absolute path on the resolver entry created by open and read it through a Service::handle_path method, like handle_status, instead of keeping a copy on File. This keeps Resolver::open's signature and drops open_with_path_returned. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1172b488-1351-40c1-a6d0-32f5277af568 --- litebox_broker_core/src/fs/resolver.rs | 49 +++++++++++--------------- litebox_broker_core/src/fs/service.rs | 30 ++++++++-------- litebox_broker_core/src/fs/tests.rs | 7 ++-- 3 files changed, 40 insertions(+), 46 deletions(-) diff --git a/litebox_broker_core/src/fs/resolver.rs b/litebox_broker_core/src/fs/resolver.rs index e7f58142cc..9fa3fa072f 100644 --- a/litebox_broker_core/src/fs/resolver.rs +++ b/litebox_broker_core/src/fs/resolver.rs @@ -576,25 +576,12 @@ impl Resolver Result, OpenError> { - self.open_with_path_returned(user, path, flags, mode) - .map(|(entry, _)| entry) - } - - /// Opens a file like [`Self::open`], also returning the absolute path of the opened object, with - /// symbolic links resolved. - pub(crate) fn open_with_path_returned( &self, user: UserInfo, path: &str, mut flags: OFlags, mode: Mode, - ) -> Result<(ResolverEntry, String), OpenError> { + ) -> Result, OpenError> { const CURRENTLY_SUPPORTED_OFLAGS: OFlags = OFlags::CREAT .union(OFlags::RDONLY) .union(OFlags::WRONLY) @@ -636,20 +623,17 @@ impl Resolver { nonblocking: bool, path_only: bool, seek_behavior: SeekBehavior, + /// Absolute path of the opened object, with symbolic links resolved, as of when it was opened. + /// + /// Later renames or removals of the object are not reflected. + path: String, } impl ResolverEntry { @@ -1269,6 +1257,11 @@ impl ResolverEntry { self.path_only } + /// Absolute path of the opened object, with symbolic links resolved, as of when it was opened. + pub(crate) fn path(&self) -> &str { + &self.path + } + pub(crate) const fn allows_read(&self) -> bool { self.read_allowed } diff --git a/litebox_broker_core/src/fs/service.rs b/litebox_broker_core/src/fs/service.rs index 226401dbc4..0dbc28ee98 100644 --- a/litebox_broker_core/src/fs/service.rs +++ b/litebox_broker_core/src/fs/service.rs @@ -33,7 +33,7 @@ type ServiceResult = Result>; /// Behaviorless type-erasure envelope for one broker-owned [`ResolverEntry`]. /// /// This does not introduce another open-file-description layer. The erased resolver entry remains -/// the only mutable per-open state. The surrounding lock serializes shared position and append +/// the only per-open semantic state. The surrounding lock serializes shared position and append /// updates across duplicated broker references, while operations that cannot update position use /// shared access so blocking device I/O does not prevent status and other stateless operations. #[derive(Clone)] @@ -42,10 +42,6 @@ pub struct File { /// Readiness of a file whose reads or writes can fail with `WouldBlock`. readiness: Option>, terminal: Option>, - /// Absolute path of the file, with symbolic links resolved, as of when it was opened. - /// - /// Later renames or removals of the file are not reflected. - path: Arc, } impl File { @@ -152,6 +148,10 @@ mod private { Err(BrokerError::UnsupportedOperation) } + fn handle_path(&self, _file: &File) -> ServiceResult { + Err(BrokerError::UnsupportedOperation) + } + fn get_status_flags(&self, _file: &File) -> Result { Err(BrokerError::UnsupportedOperation) } @@ -238,14 +238,8 @@ where mode: FileMode, ) -> ServiceResult { let flags = open_flags(access, flags)?; - let (entry, path) = match Resolver::open_with_path_returned( - self, - user, - path, - flags, - mode & FileMode::SUPPORTED, - ) { - Ok(opened) => opened, + let entry = match Resolver::open(self, user, path, flags, mode & FileMode::SUPPORTED) { + Ok(entry) => entry, Err(error) => return Ok(Err(file_open_error(error))), }; let readiness = Resolver::readiness_source(self, &entry); @@ -254,7 +248,6 @@ where state: Arc::new(RwLock::::new(entry)), readiness, terminal, - path: path.into(), })) } @@ -411,6 +404,13 @@ where Ok(Ok(status)) } + fn handle_path(&self, file: &File) -> ServiceResult { + let entry = file + .state::>>()? + .read(); + Ok(Ok(String::from(entry.path()))) + } + fn get_status_flags(&self, file: &File) -> Result { let entry = file .state::>>()? @@ -637,7 +637,7 @@ pub fn handle_status( /// when it was opened. pub fn handle_path(process: &BrokerProcess, handle: ObjectHandle) -> Result> { let file = file_with_any_rights(process, handle, ObjectRights::WAIT | ObjectRights::WRITE)?; - Ok(Ok(String::from(&*file.path))) + process.core.fs.handle_path(&file) } /// Returns whether a broker-owned open object refers to a terminal, which it does exactly when it diff --git a/litebox_broker_core/src/fs/tests.rs b/litebox_broker_core/src/fs/tests.rs index 91d45d4140..e5616b9be3 100644 --- a/litebox_broker_core/src/fs/tests.rs +++ b/litebox_broker_core/src/fs/tests.rs @@ -965,9 +965,10 @@ mod in_mem { ("/dangling", symlink("dir/created")), ])); let path = |path: &str, flags: OFlags| -> String { - fs.open_with_path_returned(USER, path, flags, Mode::RWXU) - .unwrap_or_else(|error| panic!("open {path:?} failed: {error:?}")) - .1 + let entry = fs + .open(USER, path, flags, Mode::RWXU) + .unwrap_or_else(|error| panic!("open {path:?} failed: {error:?}")); + String::from(entry.path()) }; assert_eq!(path("/", OFlags::RDONLY), "/");