vfs: implement hard links
This commit is contained in:
parent
ba00c97c66
commit
9b07dd7c6b
@ -50,7 +50,7 @@ impl DirectoryFile {
|
||||
if let Some((name, node)) = children.get(index) =>
|
||||
{
|
||||
pos = DirectoryCachePosition::Index(index + 1);
|
||||
Some((FixedString::from_str(name)?, node.clone()))
|
||||
Some((FixedString::from_str(name)?, node.flatten_hardlink()?))
|
||||
}
|
||||
DirectoryCachePosition::Index(_) => None,
|
||||
};
|
||||
|
@ -312,26 +312,6 @@ impl IoContext {
|
||||
self._find(at, path, follow_links)
|
||||
}
|
||||
|
||||
// fn _resolve_link(&self, at: &NodeRef) -> Result<NodeRef, Error> {
|
||||
// let token = self.check_access(at, AccessMode::READ)?;
|
||||
// // // let _path = link.imp.read_to_string()?;
|
||||
// // match at.read_symlink_node(token.clone()) {
|
||||
// // Ok(node) => Ok(node),
|
||||
// // // Need to read the link data and resolve it manually
|
||||
// // Err(Error::NotImplemented) => {
|
||||
// let path = at.read_symlink_path(token)?;
|
||||
// let target = if path.is_absolute() {
|
||||
// self._find(self.root.clone(), path.trim_start_separators(), false, true)?
|
||||
// } else {
|
||||
// self._find(at.parent(), &path, false, true)?
|
||||
// };
|
||||
// // TODO update node cache
|
||||
// Ok(target)
|
||||
// // }
|
||||
// // Err(e) => Err(e),
|
||||
// // }
|
||||
// }
|
||||
|
||||
fn _resolve(&self, mut at: NodeRef, follow_links: bool) -> Result<NodeRef, Error> {
|
||||
loop {
|
||||
if let Some(target) = at.mountpoint_target() {
|
||||
@ -415,8 +395,6 @@ impl IoContext {
|
||||
) -> Result<(PathBuf, NodeRef), Error> {
|
||||
let path = input.as_ref();
|
||||
|
||||
log::debug!("Resolve {path:?}");
|
||||
|
||||
let (mut buffer, mut node) = if path.is_absolute() {
|
||||
(PathBuf::from_str("/"), self.root.clone())
|
||||
} else {
|
||||
@ -447,18 +425,16 @@ impl IoContext {
|
||||
name => name,
|
||||
};
|
||||
|
||||
log::debug!("{buffer:?}: visit {name:?}");
|
||||
|
||||
if !node.is_directory() {
|
||||
todo!();
|
||||
return Err(Error::NotADirectory);
|
||||
}
|
||||
|
||||
let access = self.check_access(&node, AccessMode::EXEC)?;
|
||||
let child = node.lookup_or_load(name, access).unwrap();
|
||||
let child = node.lookup_or_load(name, access)?.flatten_hardlink()?;
|
||||
|
||||
let follow_links = follow_link || !tail.is_empty();
|
||||
|
||||
let (child, resolved) = self.visit(child, follow_links).unwrap();
|
||||
let (child, resolved) = self.visit(child, follow_links)?;
|
||||
if resolved.is_empty() {
|
||||
buffer.push(name);
|
||||
} else if resolved.is_absolute() {
|
||||
@ -470,9 +446,7 @@ impl IoContext {
|
||||
path = tail;
|
||||
}
|
||||
|
||||
log::debug!("-> {buffer:?}");
|
||||
|
||||
Ok((buffer, node))
|
||||
Ok((buffer, node.flatten_hardlink()?))
|
||||
}
|
||||
|
||||
fn _find(&self, mut at: NodeRef, path: &Path, follow_links: bool) -> Result<NodeRef, Error> {
|
||||
@ -509,11 +483,11 @@ impl IoContext {
|
||||
}
|
||||
|
||||
let access = self.check_access(&at, AccessMode::EXEC)?;
|
||||
let node = at.lookup_or_load(element, access)?;
|
||||
let node = at.lookup_or_load(element, access)?.flatten_hardlink()?;
|
||||
let node = self._resolve(node, follow_links)?;
|
||||
|
||||
if rest.is_empty() {
|
||||
Ok(node)
|
||||
Ok(node.flatten_hardlink()?)
|
||||
} else {
|
||||
self._find(node, rest, follow_links)
|
||||
}
|
||||
@ -530,9 +504,9 @@ mod tests {
|
||||
};
|
||||
|
||||
use crate::vfs::{
|
||||
impls::{const_value_node, fixed_path_symlink, mdir, value_node},
|
||||
impls::{const_value_node, fixed_hardlink, fixed_path_symlink, mdir, value_node},
|
||||
node::AccessToken,
|
||||
Read,
|
||||
Node, Read,
|
||||
};
|
||||
|
||||
use super::IoContext;
|
||||
@ -856,6 +830,46 @@ mod tests {
|
||||
assert_eq!(err, Error::ReadOnly);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hard_links() {
|
||||
// Hard link to file
|
||||
let f0 = const_value_node("test");
|
||||
let h0 = fixed_hardlink(f0.clone()).unwrap();
|
||||
let d0 = mdir([("f0", f0.clone()), ("h0", h0.clone())]);
|
||||
|
||||
let ioctx = IoContext::new(d0.clone());
|
||||
|
||||
let node = ioctx.find(None, "/f0", false).unwrap();
|
||||
let (path, n) = ioctx.normalize("/f0", false).unwrap();
|
||||
assert_eq!(path.as_str(), "/f0");
|
||||
assert!(Arc::ptr_eq(&n, &f0));
|
||||
assert!(Arc::ptr_eq(&node, &f0));
|
||||
let node = ioctx.find(None, "/h0", false).unwrap();
|
||||
let (path, n) = ioctx.normalize("/h0", false).unwrap();
|
||||
assert_eq!(path.as_str(), "/h0");
|
||||
assert!(Arc::ptr_eq(&n, &f0));
|
||||
assert!(Arc::ptr_eq(&node, &f0));
|
||||
|
||||
// Hard link to directory
|
||||
let d1f0 = const_value_node("test");
|
||||
let d1 = mdir([("f0", d1f0.clone())]);
|
||||
let h0 = fixed_hardlink(d1f0.clone()).unwrap();
|
||||
let d0 = mdir([("d1", d1.clone()), ("h0", h0.clone())]);
|
||||
|
||||
let ioctx = IoContext::new(d0.clone());
|
||||
|
||||
let node = ioctx.find(None, "/d1/f0", false).unwrap();
|
||||
let (path, n) = ioctx.normalize("/d1/f0", false).unwrap();
|
||||
assert_eq!(path.as_str(), "/d1/f0");
|
||||
assert!(Arc::ptr_eq(&n, &d1f0));
|
||||
assert!(Arc::ptr_eq(&node, &d1f0));
|
||||
let node = ioctx.find(None, "/h0", false).unwrap();
|
||||
let (path, n) = ioctx.normalize("/h0", false).unwrap();
|
||||
assert_eq!(path.as_str(), "/h0");
|
||||
assert!(Arc::ptr_eq(&n, &d1f0));
|
||||
assert!(Arc::ptr_eq(&node, &d1f0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_tests() {
|
||||
let root_d1_f1 = const_value_node("dir1/file1");
|
||||
|
@ -10,12 +10,15 @@ use alloc::{
|
||||
use libk_util::sync::IrqSafeSpinlock;
|
||||
use yggdrasil_abi::{
|
||||
error::Error,
|
||||
io::{FileMode, OpenOptions},
|
||||
io::{FileMode, FileType, OpenOptions},
|
||||
};
|
||||
|
||||
use crate::vfs::{DirectoryOpenPosition, InstanceData};
|
||||
|
||||
use super::{CommonImpl, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl, SymlinkImpl};
|
||||
use super::{
|
||||
traits::HardlinkImpl, CommonImpl, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl,
|
||||
SymlinkImpl,
|
||||
};
|
||||
|
||||
trait SliceRead {
|
||||
fn read_slice(&self, offset: usize, buf: &mut [u8]) -> usize;
|
||||
@ -100,6 +103,15 @@ pub struct FixedPathSymlink {
|
||||
target: String,
|
||||
}
|
||||
|
||||
/// In-memory hard link with a fixed target
|
||||
pub struct FixedHardlink {
|
||||
target: NodeRef,
|
||||
}
|
||||
/// In-memory hard link with a function-based target
|
||||
pub struct FnHardlink<R: Fn() -> Result<NodeRef, Error>> {
|
||||
read: R,
|
||||
}
|
||||
|
||||
impl<T, R> ReadOnlyFnValueNode<T, R>
|
||||
where
|
||||
T: ToString + Send + Sync + 'static,
|
||||
@ -114,10 +126,7 @@ where
|
||||
}
|
||||
|
||||
pub fn new_node(read: R) -> NodeRef {
|
||||
Node::regular(
|
||||
Self::new(read),
|
||||
NodeFlags::IN_MEMORY_PROPS | NodeFlags::IN_MEMORY_SIZE,
|
||||
)
|
||||
Node::regular(Self::new(read), NodeFlags::IN_MEMORY_PROPS)
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,10 +225,7 @@ where
|
||||
}
|
||||
|
||||
pub fn new_node(read: R, write: W) -> NodeRef {
|
||||
Node::regular(
|
||||
Self::new(read, write),
|
||||
NodeFlags::IN_MEMORY_PROPS | NodeFlags::IN_MEMORY_SIZE,
|
||||
)
|
||||
Node::regular(Self::new(read, write), NodeFlags::IN_MEMORY_PROPS)
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,7 +394,19 @@ impl SymlinkImpl for FixedPathSymlink {
|
||||
}
|
||||
}
|
||||
|
||||
// In-memory functional symlink
|
||||
// In-memory fixed hard link
|
||||
|
||||
impl HardlinkImpl for FixedHardlink {
|
||||
fn target(&self, _node: &NodeRef) -> Result<NodeRef, Error> {
|
||||
Ok(self.target.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Fn() -> Result<NodeRef, Error> + Sync + Send> HardlinkImpl for FnHardlink<R> {
|
||||
fn target(&self, _node: &NodeRef) -> Result<NodeRef, Error> {
|
||||
(self.read)()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a read-only value node with given `value`
|
||||
pub fn const_value_node<T>(value: T) -> NodeRef
|
||||
@ -454,6 +472,17 @@ pub fn fixed_path_symlink(target: impl Into<String>) -> NodeRef {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn fixed_hardlink(target: NodeRef) -> Result<NodeRef, Error> {
|
||||
if target.is_hard_link() || target.ty() != FileType::File {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
Ok(Node::hardlink(FixedHardlink { target }))
|
||||
}
|
||||
|
||||
pub fn fn_hardlink<R: Fn() -> Result<NodeRef, Error> + Send + Sync + 'static>(read: R) -> NodeRef {
|
||||
Node::hardlink(FnHardlink { read })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use yggdrasil_abi::io::OpenOptions;
|
||||
|
@ -10,6 +10,7 @@ use libk_util::{
|
||||
ext::OptionExt,
|
||||
sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock},
|
||||
};
|
||||
use traits::HardlinkImpl;
|
||||
use yggdrasil_abi::{
|
||||
bitflags,
|
||||
error::Error,
|
||||
@ -79,12 +80,17 @@ pub(crate) struct SymlinkData {
|
||||
pub(crate) imp: Box<dyn SymlinkImpl>,
|
||||
}
|
||||
|
||||
pub(crate) struct HardlinkData {
|
||||
pub(crate) imp: Box<dyn HardlinkImpl>,
|
||||
}
|
||||
|
||||
enum NodeImpl {
|
||||
Regular(Box<dyn RegularImpl>),
|
||||
Directory(DirectoryData),
|
||||
Block(BlockDeviceFile),
|
||||
Char(CharDeviceFile),
|
||||
Symlink(SymlinkData),
|
||||
Hardlink(HardlinkData),
|
||||
// These map transparently to other types of nodes
|
||||
// TODO: implement open operation on these
|
||||
#[allow(unused)]
|
||||
@ -175,6 +181,9 @@ impl Node {
|
||||
Metadata::default_file(),
|
||||
);
|
||||
|
||||
master.props.lock().size = Some(0);
|
||||
slave.props.lock().size = Some(0);
|
||||
|
||||
(master, slave)
|
||||
}
|
||||
|
||||
@ -261,6 +270,33 @@ impl Node {
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new hard link node pointing to another [Node]
|
||||
pub fn hardlink<T: HardlinkImpl + 'static>(data: T) -> NodeRef {
|
||||
Self::new(
|
||||
NodeImpl::Hardlink(HardlinkData {
|
||||
imp: Box::new(data),
|
||||
}),
|
||||
NodeFlags::IN_MEMORY_SIZE,
|
||||
Metadata::default_file(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Unwraps a hard link chain to its final target
|
||||
pub fn flatten_hardlink(self: &Arc<Self>) -> Result<Arc<Self>, Error> {
|
||||
let mut node = self.clone();
|
||||
while let Some(target) = node.read_hardlink()? {
|
||||
node = target;
|
||||
}
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
pub fn read_hardlink(self: &Arc<Self>) -> Result<Option<NodeRef>, Error> {
|
||||
match &self.data {
|
||||
NodeImpl::Hardlink(data) => data.target(self).map(Some),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [Filesystem] this node belongs to.
|
||||
pub fn filesystem(&self) -> Option<&dyn Filesystem> {
|
||||
self.data_as_common().filesystem()
|
||||
@ -274,6 +310,7 @@ impl Node {
|
||||
NodeImpl::Block(w) => w.as_any(),
|
||||
NodeImpl::Char(w) => w.as_any(),
|
||||
NodeImpl::Symlink(w) => w.imp.as_any(),
|
||||
NodeImpl::Hardlink(_) => unreachable!("BUG: Hard links cannot be referenced directly"),
|
||||
NodeImpl::PseudoTerminalSlave(_) | NodeImpl::PseudoTerminalMaster(_) => todo!(),
|
||||
}
|
||||
}
|
||||
@ -286,6 +323,7 @@ impl Node {
|
||||
NodeImpl::Block(w) => w,
|
||||
NodeImpl::Char(w) => w,
|
||||
NodeImpl::Symlink(w) => w.imp.as_ref(),
|
||||
NodeImpl::Hardlink(_) => unreachable!("BUG: Hard links cannot be referenced directly"),
|
||||
NodeImpl::PseudoTerminalSlave(_) | NodeImpl::PseudoTerminalMaster(_) => todo!(),
|
||||
}
|
||||
}
|
||||
@ -303,6 +341,7 @@ impl Node {
|
||||
NodeImpl::Block(_) => FileType::Block,
|
||||
NodeImpl::Char(_) => FileType::Char,
|
||||
NodeImpl::Symlink(_) => FileType::Symlink,
|
||||
NodeImpl::Hardlink(_) => unreachable!("BUG: Hard links cannot be referenced directly"),
|
||||
NodeImpl::PseudoTerminalSlave(_) | NodeImpl::PseudoTerminalMaster(_) => FileType::Char,
|
||||
}
|
||||
}
|
||||
@ -312,6 +351,11 @@ impl Node {
|
||||
matches!(&self.data, NodeImpl::Directory(_))
|
||||
}
|
||||
|
||||
/// Returns `true` if the node represents a hard link to another node
|
||||
pub fn is_hard_link(&self) -> bool {
|
||||
matches!(&self.data, NodeImpl::Hardlink(_))
|
||||
}
|
||||
|
||||
/// Returns `true` if the node represents a character device and the character device is a
|
||||
/// terminal
|
||||
pub fn is_terminal(&self) -> bool {
|
||||
@ -390,6 +434,19 @@ impl Node {
|
||||
}
|
||||
}
|
||||
|
||||
impl HardlinkData {
|
||||
fn target(&self, link: &NodeRef) -> Result<NodeRef, Error> {
|
||||
let target = self.imp.target(link)?;
|
||||
// Not allowed to point to other links or directories
|
||||
debug_assert!(!target.is_hard_link());
|
||||
debug_assert!(!matches!(
|
||||
target.ty(),
|
||||
FileType::Directory | FileType::Symlink
|
||||
));
|
||||
Ok(target)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Node {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.data {
|
||||
@ -398,6 +455,7 @@ impl fmt::Debug for Node {
|
||||
NodeImpl::Char(_) => f.debug_struct("CharNode").finish_non_exhaustive(),
|
||||
NodeImpl::Block(_) => f.debug_struct("BlockNode").finish_non_exhaustive(),
|
||||
NodeImpl::Symlink(_) => f.debug_struct("SymlinkNode").finish_non_exhaustive(),
|
||||
NodeImpl::Hardlink(_) => f.debug_struct("HardlinkNode").finish_non_exhaustive(),
|
||||
NodeImpl::PseudoTerminalSlave(_) => f
|
||||
.debug_struct("PseudoTerminalSlave")
|
||||
.finish_non_exhaustive(),
|
||||
|
@ -36,6 +36,7 @@ impl Node {
|
||||
}
|
||||
NodeImpl::Block(dev) => File::block(dev.clone(), self.clone(), opts),
|
||||
NodeImpl::Char(dev) => File::char(dev.clone(), self.clone(), opts),
|
||||
NodeImpl::Hardlink(_) => unreachable!("BUG: Hard links cannot be referenced directly"),
|
||||
// TODO: maybe merge open_directory and open?
|
||||
NodeImpl::Directory(_) => Err(Error::IsADirectory),
|
||||
NodeImpl::Symlink(_) => todo!(),
|
||||
@ -213,7 +214,10 @@ impl Node {
|
||||
return Ok(dir.children.lock().len() as _);
|
||||
}
|
||||
|
||||
log::warn!("Possible bug: node has IN_MEMORY_SIZE, but is not a directory");
|
||||
log::warn!(
|
||||
"Possible bug: node has IN_MEMORY_SIZE, but is not a directory: {:?}",
|
||||
self.ty()
|
||||
);
|
||||
Ok(0)
|
||||
// Err(Error::NotImplemented)
|
||||
} else {
|
||||
|
@ -151,3 +151,11 @@ pub trait SymlinkImpl: CommonImpl {
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
/// Hard link interface
|
||||
pub trait HardlinkImpl: Sync + Send {
|
||||
fn target(&self, node: &NodeRef) -> Result<NodeRef, Error> {
|
||||
let _ = node;
|
||||
Err(Error::NotImplemented)
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,10 @@
|
||||
use abi::error::Error;
|
||||
use libk::{
|
||||
device::display::console,
|
||||
fs::devfs,
|
||||
random,
|
||||
task::{binary::LoadOptions, process::Process, runtime},
|
||||
vfs::{IoContext, NodeRef},
|
||||
task::{binary::LoadOptions, process::Process, runtime, thread::Thread},
|
||||
vfs::{impls::fn_hardlink, IoContext, NodeRef},
|
||||
};
|
||||
use memfs::MemoryFilesystem;
|
||||
|
||||
@ -32,7 +33,17 @@ pub fn kinit() -> Result<(), Error> {
|
||||
runtime::spawn(ygg_driver_usb::bus::bus_handler())?;
|
||||
runtime::spawn(console::flush_consoles_task()).ok();
|
||||
|
||||
// TODO bring /dev/tty back
|
||||
devfs::root()
|
||||
.add_child(
|
||||
"tty",
|
||||
fn_hardlink(|| {
|
||||
let thread = Thread::current();
|
||||
let process = thread.process();
|
||||
|
||||
process.session_terminal().ok_or(Error::InvalidOperation)
|
||||
}),
|
||||
)
|
||||
.ok();
|
||||
|
||||
ygg_driver_net_loopback::init();
|
||||
ygg_driver_net_core::start_network_tasks()?;
|
||||
|
@ -52,10 +52,14 @@ fn exec_script<P: AsRef<Path>>(path: P, arg: &str) -> Result<(), Error> {
|
||||
}
|
||||
|
||||
fn get_scripts<P: AsRef<Path>>(dir: P) -> Result<Vec<String>, Error> {
|
||||
let mut items = read_dir(dir)?
|
||||
.map(|item| item.map(|item| item.file_name().to_str().unwrap().to_owned()))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
items.sort();
|
||||
let mut items = vec![];
|
||||
for item in read_dir(dir)? {
|
||||
let item = item?;
|
||||
if !item.file_type()?.is_file() {
|
||||
continue;
|
||||
}
|
||||
items.push(item.file_name().to_str().unwrap().to_owned());
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,7 @@
|
||||
#![feature(let_chains, decl_macro)]
|
||||
|
||||
use std::{
|
||||
fmt,
|
||||
fs::{read_dir, FileType, Metadata},
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
process::ExitCode,
|
||||
cmp::Ordering, fmt, fs::{read_dir, FileType, Metadata}, io, path::{Path, PathBuf}, process::ExitCode
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
@ -213,45 +209,6 @@ impl DisplayBit for Option<Metadata> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
// #[cfg(target_os = "yggdrasil")]
|
||||
// impl DisplayBit for Option<Metadata> {
|
||||
// fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// let Some(attrs) = self else {
|
||||
// write!(f, "--------- {:<12}", "???")?;
|
||||
// if opts.inodes {
|
||||
// write!(f, " {:<8}", "---")?;
|
||||
// }
|
||||
//
|
||||
// return Ok(());
|
||||
// };
|
||||
//
|
||||
// let mode = attrs.mode_ext();
|
||||
// write!(f, "{} {:>12}", mode, attrs.len().display_size_with(opts))?;
|
||||
// if opts.inodes {
|
||||
// if let Some(ino) = attrs.inode() {
|
||||
// write!(f, " {:>8}", ino)?;
|
||||
// } else {
|
||||
// write!(f, " {:>8}", "---")?;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[cfg(unix)]
|
||||
// impl DisplayBit for Option<Metadata> {
|
||||
// fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// use sysutils::unix::FileMode;
|
||||
//
|
||||
// let Some(attrs) = self else {
|
||||
// return write!(f, "--------- {:<8}", "???");
|
||||
// };
|
||||
//
|
||||
// let mode = FileMode::from(attrs.mode());
|
||||
// write!(f, "{} {:>8}", mode, attrs.len().display_size_with(opts))
|
||||
// }
|
||||
// }
|
||||
|
||||
impl DisplayBit for Entry {
|
||||
fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
@ -296,6 +253,16 @@ impl Entry {
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_dirs_first(a: &Entry, b: &Entry) -> Ordering {
|
||||
let a_is_dir = a.ty.map(|t| t.is_dir()).unwrap_or(false);
|
||||
let b_is_dir = b.ty.map(|t| t.is_dir()).unwrap_or(false);
|
||||
|
||||
let by_type = Ord::cmp(&(b_is_dir as usize), &(a_is_dir as usize));
|
||||
let by_name = Ord::cmp(&a.name, &b.name);
|
||||
|
||||
by_type.then(by_name)
|
||||
}
|
||||
|
||||
fn list_directory(path: &Path) -> io::Result<Vec<Entry>> {
|
||||
let mut entries = vec![];
|
||||
for entry in read_dir(path)? {
|
||||
@ -327,7 +294,7 @@ fn list_directory(path: &Path) -> io::Result<Vec<Entry>> {
|
||||
});
|
||||
}
|
||||
|
||||
entries.sort_by(|a, b| Ord::cmp(&a.name, &b.name));
|
||||
entries.sort_by(sort_dirs_first);
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user