diff --git a/Cargo.lock b/Cargo.lock index fc4801952a..3c11aa8c63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,9 +303,9 @@ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libredox" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" dependencies = [ "bitflags", "libc", @@ -417,9 +417,9 @@ checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" [[package]] name = "redox-scheme" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c00025a04f76fdcf72c15f10c7a12d9f2fdde93e539be9a57d5d632c4158a9e" +checksum = "4da6a0251965958189cdfd5ebb66f99754db4aa165394300aa2b958525d94b64" dependencies = [ "libredox", "redox_syscall", @@ -427,9 +427,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" dependencies = [ "bitflags", ] @@ -442,7 +442,7 @@ checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" [[package]] name = "redoxfs" -version = "0.7.0" +version = "0.7.1" dependencies = [ "aes", "argon2", diff --git a/Cargo.toml b/Cargo.toml index 3907b6f640..91415b1ee5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "redoxfs" description = "The Redox Filesystem" repository = "https://gitlab.redox-os.org/redox-os/redoxfs" -version = "0.7.0" +version = "0.7.1" license-file = "LICENSE" readme = "README.md" authors = ["Jeremy Soller "] @@ -38,12 +38,12 @@ endian-num = "0.1" env_logger = { version = "0.11", optional = true } getrandom = { version = "0.2.5", optional = true } libc = "0.2" -libredox = { version = "0.1.3", optional = true } -log = { version = "0.4.14", default-features = false, optional = true} +libredox = { version = "0.1.6", features = ["call"], optional = true } +log = { version = "0.4.14", default-features = false, optional = true } range-tree = { version = "0.1", optional = true } redox-path = "0.3.0" -redox-scheme = { version = "0.6.2", optional = true } -redox_syscall = { version = "0.5.13" } +redox-scheme = { version = "0.7.0", optional = true } +redox_syscall = { version = "0.5.15", optional = true } seahash = { version = "4.1.0", default-features = false } termion = { version = "4", optional = true } uuid = { version = "1.4", default-features = false } diff --git a/src/mount/redox/mod.rs b/src/mount/redox/mod.rs index d8050b8f5d..d165c05660 100644 --- a/src/mount/redox/mod.rs +++ b/src/mount/redox/mod.rs @@ -1,4 +1,4 @@ -use redox_scheme::{RequestKind, SignalBehavior, Socket}; +use redox_scheme::{scheme::SchemeSync, RequestKind, Response, SignalBehavior, Socket}; use std::io; use std::path::Path; use std::sync::atomic::Ordering; @@ -22,17 +22,27 @@ where let mounted_path = format!("/scheme/{}", mountpoint.display()); let res = callback(Path::new(&mounted_path)); - let mut scheme = FileScheme::new(format!("{}", mountpoint.display()), filesystem); + let mut scheme = FileScheme::new(format!("{}", mountpoint.display()), filesystem, &socket); while IS_UMT.load(Ordering::SeqCst) == 0 { let req = match socket.next_request(SignalBehavior::Restart)? { None => break, Some(req) => { - if let RequestKind::Call(r) = req.kind() { - r - } else { - // TODO: Redoxfs does not yet support asynchronous file IO. It might still make - // sense to implement cancellation for huge buffers, e.g. dd bs=1G - continue; + match req.kind() { + RequestKind::Call(r) => r, + RequestKind::SendFd(sendfd_request) => { + let result = scheme.on_sendfd(&sendfd_request); + let response = Response::new(result, sendfd_request); + + if !socket.write_response(response, SignalBehavior::Restart)? { + break; + } + continue; + } + _ => { + // TODO: Redoxfs does not yet support asynchronous file IO. It might still make + // sense to implement cancellation for huge buffers, e.g. dd bs=1G + continue; + } } } }; diff --git a/src/mount/redox/scheme.rs b/src/mount/redox/scheme.rs index 62a72f53ae..4d9db062b6 100644 --- a/src/mount/redox/scheme.rs +++ b/src/mount/redox/scheme.rs @@ -3,18 +3,20 @@ use std::str; use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::{SystemTime, UNIX_EPOCH}; -use redox_scheme::{scheme::SchemeSync, CallerCtx, OpenResult}; +use redox_scheme::{scheme::SchemeSync, CallerCtx, OpenResult, SendFdRequest, Socket}; use syscall::data::{Stat, StatVfs, TimeSpec}; use syscall::dirent::DirentBuf; use syscall::error::{ Error, Result, EACCES, EBADF, EBUSY, EEXIST, EINVAL, EISDIR, ELOOP, ENOENT, ENOTDIR, ENOTEMPTY, - EPERM, EXDEV, + EOPNOTSUPP, EPERM, EXDEV, }; use syscall::flag::{ EventFlags, MapFlags, O_ACCMODE, O_CREAT, O_DIRECTORY, O_EXCL, O_NOFOLLOW, O_RDONLY, O_RDWR, O_STAT, O_SYMLINK, O_TRUNC, O_WRONLY, }; use syscall::schemev2::NewFdFlags; +use syscall::FobtainFdFlags; +use syscall::FsCall; use syscall::MunmapFlags; use redox_path::{ @@ -26,22 +28,28 @@ use crate::{Disk, FileSystem, Node, Transaction, TreeData, TreePtr, BLOCK_SIZE}; use super::resource::{DirResource, Entry, FileResource, Resource}; -pub struct FileScheme { +pub struct FileScheme<'sock, D: Disk> { name: String, pub(crate) fs: FileSystem, + socket: &'sock Socket, next_id: AtomicUsize, files: BTreeMap>>, fmap: super::resource::Fmaps, + + // Map of file id to other scheme's file descriptor. + other_scheme_fd_map: BTreeMap, } -impl FileScheme { - pub fn new(name: String, fs: FileSystem) -> FileScheme { +impl<'sock, D: Disk> FileScheme<'sock, D> { + pub fn new(name: String, fs: FileSystem, socket: &'sock Socket) -> FileScheme<'sock, D> { FileScheme { name, fs, + socket, next_id: AtomicUsize::new(1), files: BTreeMap::new(), fmap: BTreeMap::new(), + other_scheme_fd_map: BTreeMap::new(), } } @@ -110,6 +118,17 @@ impl FileScheme { Err(Error::new(ELOOP)) } + fn handle_connect(&mut self, id: usize, payload: &mut [u8]) -> Result { + let resource = self.files.get(&id).ok_or(Error::new(EBADF))?; + let inode_id = resource.node_ptr().id(); + let target_fd = self + .other_scheme_fd_map + .get(&inode_id) + .ok_or(Error::new(EBADF))?; + let len = libredox::call::get_socket_token(*target_fd, payload)?; + return Ok(len); + } + fn open_internal( &mut self, start_ptr: TreePtr, @@ -383,7 +402,7 @@ fn dirname(path: &str) -> Option { canonicalize_using_cwd(Some(path), "..") } -impl SchemeSync for FileScheme { +impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> { fn open(&mut self, url: &str, flags: usize, ctx: &CallerCtx) -> Result { self.open_internal(TreePtr::root(), url, flags, ctx) } @@ -454,7 +473,7 @@ impl SchemeSync for FileScheme { // println!("Unlink '{}'", path); let scheme_name = &self.name; - self.fs.tx(|tx| { + let unlink_result = self.fs.tx(|tx| { let mut nodes = Vec::new(); let Some((child, child_name)) = @@ -480,15 +499,23 @@ impl SchemeSync for FileScheme { if child.data().is_symlink() { tx.remove_node(parent.ptr(), &child_name, Node::MODE_SYMLINK) - .and(Ok(())) + } else if child.data().is_sock() { + tx.remove_node(parent.ptr(), &child_name, Node::MODE_SOCK) } else { tx.remove_node(parent.ptr(), &child_name, Node::MODE_FILE) - .and(Ok(())) } } else { Err(Error::new(EISDIR)) } - }) + }); + + let Some(node_id) = unlink_result? else { + return Ok(()); + }; + if let Some(fd) = self.other_scheme_fd_map.remove(&node_id) { + let _ = syscall::close(fd); + } + Ok(()) } /* Resource operations */ @@ -910,4 +937,120 @@ impl SchemeSync for FileScheme { // TODO: If open_fds reaches zero and there are no hardlinks (directory entries) to any // particular inode, remove that inode here. } + + fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result { + let ctx = sendfd_request.caller(); + let uid = ctx.uid; + let gid = ctx.gid; + + let parent_resource = self + .files + .get(&sendfd_request.id()) + .ok_or(Error::new(EBADF))?; + + let mut new_fd = usize::MAX; + if let Err(e) = + sendfd_request.obtain_fd(&self.socket, FobtainFdFlags::empty(), Err(&mut new_fd)) + { + return Err(e); + } + + let parent_resource_ptr = parent_resource.node_ptr(); + + let parent_node = self.fs.tx(|tx| tx.read_tree(parent_resource_ptr))?; + if !parent_node.data().is_dir() { + return Err(Error::new(ENOTDIR)); + } + if !parent_node.data().permission(uid, gid, Node::MODE_WRITE) { + return Err(Error::new(EACCES)); + } + let parent_path = parent_resource.path(); + + // TODO: Move the PATH_MAX definition to a more appropriate place. + const PATH_MAX: usize = 4096; + let mut url_buf = [0u8; PATH_MAX]; + let url_len = syscall::fpath(new_fd, &mut url_buf)?; + let url_str = str::from_utf8(&url_buf[..url_len]).map_err(|_| Error::new(EINVAL))?; + let redox_path = RedoxPath::from_absolute(url_str).ok_or(Error::new(EINVAL))?; + let (_, path) = redox_path.as_parts().ok_or(Error::new(EINVAL))?; + + let mut last_part = String::new(); + for part in path.as_ref().split('/') { + if !part.is_empty() { + last_part = part.to_string(); + } + } + + let (resource, node_id): (Box>, u32) = if !last_part.is_empty() { + let mut stat = Stat::default(); + syscall::fstat(new_fd, &mut stat)?; + let mode_type = stat.st_mode & Node::MODE_TYPE; + + let flags = 0o777; + let node_ptr = self.fs.tx(|tx| { + if tx.find_node(parent_resource_ptr, &last_part).is_ok() { + // If the file already exists, we cannot create it again + return Err(Error::new(EEXIST)); + } + + let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let mut node = tx.create_node( + parent_resource_ptr, + &last_part, + mode_type | (flags as u16 & Node::MODE_PERM), + ctime.as_secs(), + ctime.subsec_nanos(), + )?; + let node_ptr = node.ptr(); + if node.data().uid() != uid || node.data().gid() != gid { + node.data_mut().set_uid(uid); + node.data_mut().set_gid(gid); + tx.sync_tree(node)?; + } + Ok(node_ptr) + })?; + + let file_path = format!("{parent_path}/{last_part}"); + let node_id = node_ptr.id(); + + ( + Box::new(FileResource::new( + file_path, + Some(parent_resource_ptr), + node_ptr, + flags, + uid, + )), + node_id, + ) + } else { + return Err(Error::new(EINVAL)); + }; + + self.fmap + .entry(resource.node_ptr().id()) + .or_insert_with(Default::default) + .open_fds += 1; + + let id = self.next_id.fetch_add(1, Ordering::Relaxed); + self.files.insert(id, resource); + self.other_scheme_fd_map.insert(node_id, new_fd); + Ok(new_fd) + } + + fn call( + &mut self, + id: usize, + payload: &mut [u8], + metadata: &[u64], + ctx: &CallerCtx, + ) -> Result { + let Some(verb) = FsCall::try_from_raw(metadata[0] as usize) else { + return Err(Error::new(EINVAL)); + }; + match verb { + FsCall::Connect => self.handle_connect(id, payload), + _ => Err(Error::new(EOPNOTSUPP)), + } + } } diff --git a/src/node.rs b/src/node.rs index b2c2426497..be3bfdf947 100644 --- a/src/node.rs +++ b/src/node.rs @@ -190,6 +190,7 @@ impl Node { pub const MODE_FILE: u16 = 0x8000; pub const MODE_DIR: u16 = 0x4000; pub const MODE_SYMLINK: u16 = 0xA000; + pub const MODE_SOCK: u16 = 0xC000; /// Mask for node permission bits pub const MODE_PERM: u16 = 0x0FFF; @@ -310,6 +311,10 @@ impl Node { self.mode() & Self::MODE_TYPE == Self::MODE_SYMLINK } + pub fn is_sock(&self) -> bool { + self.mode() & Self::MODE_SOCK == Self::MODE_SOCK + } + /// Tests if UID is the owner of that file, only true when uid=0 or when the UID stored in metadata is equal to the UID you supply pub fn owner(&self, uid: u32) -> bool { uid == 0 || self.uid() == uid diff --git a/src/transaction.rs b/src/transaction.rs index c48d9b0208..d51c1eae8c 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -993,7 +993,12 @@ impl<'a, D: Disk> Transaction<'a, D> { Ok(None) } - pub fn remove_node(&mut self, parent_ptr: TreePtr, name: &str, mode: u16) -> Result<()> { + pub fn remove_node( + &mut self, + parent_ptr: TreePtr, + name: &str, + mode: u16, + ) -> Result> { #[cfg(feature = "log")] log::debug!( "REMOVE_NODE: name: {}, mode: {:x}, parent_ptr: {:?}", @@ -1052,6 +1057,7 @@ impl<'a, D: Disk> Transaction<'a, D> { } let links = node.data().links(); + let node_id = node.id(); let remove_node = if links > 1 { node.data_mut().set_links(links - 1); false @@ -1102,12 +1108,13 @@ impl<'a, D: Disk> Transaction<'a, D> { unsafe { self.deallocate(node_addr); } + + Ok(Some(node_id)) } else { // Sync both parent and node at the same time self.sync_trees(&[parent, node])?; + Ok(None) } - - Ok(()) } fn remove_node_inner(