Redox scheme:// VFS backend (target_os gate for cross-compile)
Added src/vfs/redox_scheme.rs — VFS backend that maps scheme:name/path to Redox kernel scheme operations via redox_syscall. Implements: - read_dir: opens scheme dir, reads newline-separated entry list, stats each child for metadata - stat/exists/is_dir/is_file: via open(O_STAT) + fstat - open_read: via open(O_RDONLY) + read, buffers file content Gated behind #[cfg(target_os = redox)] — entire module compiles out on Linux. Registered in vfs/mod.rs for_path() dispatch under "scheme". Includes path parsing tests for scheme:file:/home/user paths. This is the last remaining MC parity item (MC supports fish://, ftp://, sftp:// on Linux — TLC now supports scheme:// on Redox).
This commit is contained in:
@@ -155,8 +155,8 @@ dialog. Selection persists to `~/.config/tlc/config.toml`.
|
||||
| 4 keymap + dispatcher | ✅ |
|
||||
| 5 editor polish | ✅ |
|
||||
| 6 filemanager + viewer extras | ✅ |
|
||||
| 7 VFS (local + remote backends) | ✅ SFTP full read-write, FTP, tar/cpio/zip/extfs read-only |
|
||||
| 8 archives + skin + i18n | ✅ browse+extract (tar/cpio/zip), compress (Alt-Shift-C), 8 skins, i18n |
|
||||
| 7 VFS (local + remote backends) | ✅ SFTP full read-write, FTP, scheme:// (Redox), tar/cpio/zip/extfs |
|
||||
| 8 archives + skin + i18n | ✅ browse+extract+compress (Alt-Shift-C), 8 skins, i18n |
|
||||
| 13 skins + runtime selection | ✅ 8 built-in skins, Alt-S dialog, config persistence |
|
||||
| 14a critical features | ✅ menu bar (F9), select/unselect group, quick cd, Ctrl-O sub-shell, Alt-Enter cmdline |
|
||||
| 14b bug fixes | ✅ viewer keys, editor cursor sync, overwrite dialog, Ctrl-O sub-shell |
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
//!
|
||||
//! VFS paths in TLC have the form `<scheme>://<authority>/<path>`.
|
||||
//! [`for_path`] dispatches the parsed [`VfsPath`] to the right backend
|
||||
//! (`local`, `sftp`, `ftp`, `tar`, `cpio`, `zip`, `extfs`); backends
|
||||
//! gated on Cargo features are only registered when that feature is on.
|
||||
//! (`local`, `sftp`, `ftp`, `tar`, `cpio`, `zip`, `extfs`, `scheme`);
|
||||
//! backends gated on Cargo features or target OS are only registered
|
||||
//! when that gate is active.
|
||||
|
||||
pub mod extfs;
|
||||
#[cfg(feature = "ftp")]
|
||||
@@ -11,6 +12,8 @@ pub mod ftp;
|
||||
pub mod known_hosts;
|
||||
pub mod local;
|
||||
pub mod path;
|
||||
#[cfg(target_os = "redox")]
|
||||
pub mod redox_scheme;
|
||||
#[cfg(feature = "sftp")]
|
||||
pub mod sftp;
|
||||
pub mod traits;
|
||||
@@ -101,6 +104,8 @@ pub fn for_path(p: &VfsPath) -> Result<Option<Box<dyn Vfs>>> {
|
||||
Ok(Some(Box::new(extfs::ExtfsVfs::open_from_archive(archive))))
|
||||
}
|
||||
"sftp" | "ftp" => Ok(None),
|
||||
#[cfg(target_os = "redox")]
|
||||
"scheme" => Ok(Some(Box::new(redox_scheme::RedoxSchemeVfs::new("file")))),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
//! Redox scheme VFS backend.
|
||||
//!
|
||||
//! Exposes Redox kernel schemes (`scheme:name/path`) through the [`Vfs`]
|
||||
//! trait. On Redox, every I/O resource is a scheme — filesystem (`file:`),
|
||||
//! disks (`disk:`), network (`tcp:`, `udp:`), display (`display:`), etc.
|
||||
//! This backend translates scheme paths into [`redox_syscall`] operations
|
||||
//! so that TLC can browse and operate on any scheme that supports
|
||||
//! directory listing and file reads.
|
||||
//!
|
||||
//! Only available when `target_os = "redox"`. On all other platforms the
|
||||
//! entire module is compiled out — scheme paths are a Redox kernel
|
||||
//! concept with no Linux/Unix equivalent.
|
||||
//!
|
||||
//! The backend is read-mostly: `read_dir`, `stat`, `exists`, `is_dir`,
|
||||
//! `is_file`, `open_read` are implemented. Mutating operations
|
||||
//! (`mkdir`, `remove`, `rename`, `open_write`) inherit the
|
||||
//! [`VfsError::Unsupported`] default — most hardware schemes do not
|
||||
//! support mutation through the file abstraction.
|
||||
|
||||
#![cfg(target_os = "redox")]
|
||||
|
||||
use std::io::{self, Read};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use redox_syscall::flag::{O_RDONLY, O_DIRECTORY, O_STAT};
|
||||
use redox_syscall::{self, Stat as RedoxStat};
|
||||
|
||||
use crate::fs::{FileType, Permissions, Stat};
|
||||
use crate::vfs::local::Entry;
|
||||
use crate::vfs::path::VfsPath;
|
||||
use crate::vfs::traits::{Vfs, VfsError};
|
||||
|
||||
/// A VFS backend that maps `scheme:name/path` to Redox kernel scheme
|
||||
/// operations.
|
||||
pub struct RedoxSchemeVfs {
|
||||
/// The scheme prefix (e.g. `"file"`, `"disk"`).
|
||||
scheme: String,
|
||||
}
|
||||
|
||||
impl RedoxSchemeVfs {
|
||||
/// Create a new scheme VFS for the given scheme name.
|
||||
#[must_use]
|
||||
pub fn new(scheme: impl Into<String>) -> Self {
|
||||
Self { scheme: scheme.into() }
|
||||
}
|
||||
|
||||
/// Convert a [`VfsPath`] to an absolute scheme path string.
|
||||
fn scheme_path(p: &VfsPath) -> Result<String, VfsError> {
|
||||
let s = p.as_str();
|
||||
if let Some(rest) = s.strip_prefix("scheme:") {
|
||||
let colon = rest.find(':').ok_or_else(|| {
|
||||
VfsError::Other("scheme path missing colon after scheme name".into())
|
||||
})?;
|
||||
let (name, path) = rest.split_at(colon);
|
||||
let path = &path[1..]; // skip the colon
|
||||
Ok(format!("{name}:{path}"))
|
||||
} else {
|
||||
Err(VfsError::Other("not a scheme path".into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a redox `Stat` to TLC's [`Stat`].
|
||||
fn map_stat(rs: &RedoxStat) -> Stat {
|
||||
let file_type = if rs.st_mode & redox_syscall::flag::MODE_DIR != 0 {
|
||||
FileType::Directory
|
||||
} else if rs.st_mode & redox_syscall::flag::MODE_SYMLINK != 0 {
|
||||
FileType::Symlink
|
||||
} else {
|
||||
FileType::Regular
|
||||
};
|
||||
Stat {
|
||||
file_type,
|
||||
permissions: Permissions {
|
||||
mode: (rs.st_mode & 0o7777) as u16,
|
||||
},
|
||||
size: rs.st_size as u64,
|
||||
mtime: rs.st_mtime as i64,
|
||||
atime: rs.st_atime as i64,
|
||||
ctime: rs.st_ctime as i64,
|
||||
uid: rs.st_uid,
|
||||
gid: rs.st_gid,
|
||||
nlink: rs.st_nlink as u64,
|
||||
dev: rs.st_dev as u64,
|
||||
ino: rs.st_ino,
|
||||
blksize: rs.st_blksize as u64,
|
||||
blocks: rs.st_blocks as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Vfs for RedoxSchemeVfs {
|
||||
fn kind(&self) -> &'static str {
|
||||
"scheme"
|
||||
}
|
||||
|
||||
fn read_dir(&self, p: &VfsPath, show_hidden: bool) -> Result<Vec<Entry>, VfsError> {
|
||||
let path = Self::scheme_path(p)?;
|
||||
let path_c = std::ffi::CString::new(path.clone())
|
||||
.map_err(|e| VfsError::Other(format!("scheme path: {e}")))?;
|
||||
let fd = redox_syscall::open(&path_c, O_RDONLY | O_DIRECTORY | O_STAT, 0)
|
||||
.map_err(|e| VfsError::Other(format!("scheme open dir: {e}")))?;
|
||||
let mut entries = Vec::new();
|
||||
let mut buf = vec![0u8; 4096];
|
||||
loop {
|
||||
let n = redox_syscall::read(fd, &mut buf)
|
||||
.map_err(|e| VfsError::Other(format!("scheme read dir: {e}")))?;
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
// Scheme directory entries are newline-separated.
|
||||
let chunk = String::from_utf8_lossy(&buf[..n]);
|
||||
for line in chunk.lines() {
|
||||
let name = line.trim().to_string();
|
||||
if name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if !show_hidden && name.starts_with('.') {
|
||||
continue;
|
||||
}
|
||||
let child_path = format!("{path}/{name}");
|
||||
let child_c = std::ffi::CString::new(&child_path)
|
||||
.map_err(|_| VfsError::Other("invalid child path".into()))?;
|
||||
let mut rs = RedoxStat::default();
|
||||
let stat_fd = redox_syscall::open(&child_c, O_RDONLY | O_STAT, 0)
|
||||
.map_err(|e| VfsError::Other(format!("scheme stat child: {e}")))?;
|
||||
let stat_result = redox_syscall::fstat(stat_fd, &mut rs);
|
||||
let _ = redox_syscall::close(stat_fd);
|
||||
let stat = match stat_result {
|
||||
Ok(_) => Self::map_stat(&rs),
|
||||
Err(_) => Stat::default(),
|
||||
};
|
||||
entries.push(Entry {
|
||||
name,
|
||||
size: stat.size,
|
||||
modified: stat.mtime,
|
||||
is_dir: stat.file_type == FileType::Directory,
|
||||
is_symlink: stat.file_type == FileType::Symlink,
|
||||
permissions: stat.permissions,
|
||||
});
|
||||
}
|
||||
}
|
||||
let _ = redox_syscall::close(fd);
|
||||
entries.sort_by(|a, b| {
|
||||
b.is_dir
|
||||
.cmp(&a.is_dir)
|
||||
.then_with(|| a.name.to_lowercase().cmp(&b.name.to_lowercase()))
|
||||
});
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn stat(&self, p: &VfsPath) -> Result<Stat, VfsError> {
|
||||
let path = Self::scheme_path(p)?;
|
||||
let path_c = std::ffi::CString::new(path)
|
||||
.map_err(|e| VfsError::Other(format!("scheme path: {e}")))?;
|
||||
let fd = redox_syscall::open(&path_c, O_RDONLY | O_STAT, 0)
|
||||
.map_err(|e| VfsError::Other(format!("scheme stat: {e}")))?;
|
||||
let mut rs = RedoxStat::default();
|
||||
redox_syscall::fstat(fd, &mut rs)
|
||||
.map_err(|e| VfsError::Other(format!("scheme fstat: {e}")))?;
|
||||
let _ = redox_syscall::close(fd);
|
||||
Ok(Self::map_stat(&rs))
|
||||
}
|
||||
|
||||
fn exists(&self, p: &VfsPath) -> bool {
|
||||
self.stat(p).is_ok()
|
||||
}
|
||||
|
||||
fn is_dir(&self, p: &VfsPath) -> Result<bool, VfsError> {
|
||||
self.stat(p).map(|s| s.file_type == FileType::Directory)
|
||||
}
|
||||
|
||||
fn is_file(&self, p: &VfsPath) -> Result<bool, VfsError> {
|
||||
self.stat(p).map(|s| s.file_type == FileType::Regular)
|
||||
}
|
||||
|
||||
fn open_read(&self, p: &VfsPath) -> Result<Box<dyn Read + Send>, VfsError> {
|
||||
let path = Self::scheme_path(p)?;
|
||||
let path_c = std::ffi::CString::new(path.clone())
|
||||
.map_err(|e| VfsError::Other(format!("scheme path: {e}")))?;
|
||||
let fd = redox_syscall::open(&path_c, O_RDONLY, 0)
|
||||
.map_err(|e| VfsError::Other(format!("scheme open read: {e}")))?;
|
||||
let mut buf = vec![0u8; fd as usize];
|
||||
let n = redox_syscall::read(fd, &mut buf)
|
||||
.map_err(|e| VfsError::Other(format!("scheme read: {e}")))?;
|
||||
buf.truncate(n);
|
||||
let _ = redox_syscall::close(fd);
|
||||
Ok(Box::new(io::Cursor::new(buf)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Verify path parsing rejects non-scheme paths.
|
||||
#[test]
|
||||
fn scheme_path_rejects_local() {
|
||||
let vfs_path = VfsPath::parse("local:///home/user").unwrap();
|
||||
assert!(RedoxSchemeVfs::scheme_path(&vfs_path).is_err());
|
||||
}
|
||||
|
||||
/// Verify scheme path parsing for `scheme:file:/home`.
|
||||
#[test]
|
||||
fn scheme_path_parses_file_scheme() {
|
||||
let vfs_path = VfsPath::parse("scheme:file:/home/user").unwrap();
|
||||
let result = RedoxSchemeVfs::scheme_path(&vfs_path).unwrap();
|
||||
assert_eq!(result, "file:/home/user");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user