Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions litebox/src/broker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,11 @@ pub(crate) trait BrokerControl: Send + Sync {
handle: ObjectHandle,
) -> core::result::Result<core::result::Result<FileStatus, FileError>, BrokerControlError>;

fn handle_file_path(
&self,
handle: ObjectHandle,
) -> core::result::Result<core::result::Result<String, FileError>, BrokerControlError>;

fn is_terminal_file(
&self,
handle: ObjectHandle,
Expand Down Expand Up @@ -1431,6 +1436,14 @@ where
self.request(|local| local.handle_file_status(handle))
}

fn handle_file_path(
&self,
handle: ObjectHandle,
) -> core::result::Result<core::result::Result<String, FileError>, 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,
Expand Down
10 changes: 10 additions & 0 deletions litebox/src/fs/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
22 changes: 19 additions & 3 deletions litebox/src/fs/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Platform: sync::RawSyncPrimitivesProvider + TimeProvider> LiteBox<Platform> {
Expand Down Expand Up @@ -421,6 +421,16 @@ impl<Platform: sync::RawSyncPrimitivesProvider + TimeProvider> File<Platform> {
.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<String, FilePathError> {
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<bool, IsTerminalError> {
self.broker
Expand Down Expand Up @@ -637,6 +647,12 @@ impl From<BrokerControlError> for FileStatusError {
}
}

impl From<BrokerControlError> for FilePathError {
fn from(error: BrokerControlError) -> Self {
broker_fd_error(error, Self::ClosedFd, Self::Io, Self::Io)
}
}

impl From<BrokerControlError> for IsTerminalError {
fn from(error: BrokerControlError) -> Self {
broker_fd_error(error, Self::ClosedFd, Self::Io, Self::Io)
Expand Down
6 changes: 3 additions & 3 deletions litebox_broker_core/src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand Down
31 changes: 23 additions & 8 deletions litebox_broker_core/src/fs/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand Down Expand Up @@ -130,14 +140,9 @@ impl<Platform, Backend: super::backend::Backend + 'static> Resolver<Platform, Ba
user: UserInfo,
path: &str,
) -> Result<String, WalkError> {
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
Expand Down Expand Up @@ -628,6 +633,7 @@ impl<Platform, Backend: super::backend::Backend + 'static> Resolver<Platform, Ba
nonblocking,
path_only,
seek_behavior,
path: path.to_absolute(),
};

if path.components.is_empty() {
Expand Down Expand Up @@ -1240,13 +1246,22 @@ pub struct ResolverEntry<Backend: super::backend::Backend> {
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<Backend: super::backend::Backend> ResolverEntry<Backend> {
pub(crate) const fn is_path_only(&self) -> bool {
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
}
Expand Down
18 changes: 18 additions & 0 deletions litebox_broker_core/src/fs/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ mod private {
Err(BrokerError::UnsupportedOperation)
}

fn handle_path(&self, _file: &File) -> ServiceResult<String> {
Err(BrokerError::UnsupportedOperation)
}

fn get_status_flags(&self, _file: &File) -> Result<FileStatusFlags> {
Err(BrokerError::UnsupportedOperation)
}
Expand Down Expand Up @@ -400,6 +404,13 @@ where
Ok(Ok(status))
}

fn handle_path(&self, file: &File) -> ServiceResult<String> {
let entry = file
.state::<RwLock<Platform, ResolverEntry<Backend>>>()?
.read();
Ok(Ok(String::from(entry.path())))
}

fn get_status_flags(&self, file: &File) -> Result<FileStatusFlags> {
let entry = file
.state::<RwLock<Platform, ResolverEntry<Backend>>>()?
Expand Down Expand Up @@ -622,6 +633,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<FileResult<String>> {
let file = file_with_any_rights(process, handle, ObjectRights::WAIT | ObjectRights::WRITE)?;
process.core.fs.handle_path(&file)
}

/// 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<FileResult<bool>> {
Expand Down
51 changes: 51 additions & 0 deletions litebox_broker_core/src/fs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,57 @@ 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 {
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), "/");
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 {
Expand Down
74 changes: 68 additions & 6 deletions litebox_broker_host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -833,6 +833,17 @@ fn handle_file_request<Memory: SharedMemory>(
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)?
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading