mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
123 lines
3.1 KiB
Rust
123 lines
3.1 KiB
Rust
use std::{io, path::Path};
|
|
|
|
use yazi_shared::url::{Component, UrlBuf, UrlLike};
|
|
|
|
#[inline]
|
|
pub fn ok_or_not_found<T: Default>(result: io::Result<T>) -> io::Result<T> {
|
|
match result {
|
|
Ok(t) => Ok(t),
|
|
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(T::default()),
|
|
Err(_) => result,
|
|
}
|
|
}
|
|
|
|
// Find the max common root in a list of urls
|
|
// e.g. /a/b/c, /a/b/d -> /a/b
|
|
// /aa/bb/cc, /aa/dd/ee -> /aa
|
|
pub fn max_common_root(urls: &[UrlBuf]) -> usize {
|
|
if urls.is_empty() {
|
|
return 0;
|
|
} else if urls.len() == 1 {
|
|
return urls[0].components().count() - 1;
|
|
}
|
|
|
|
let mut it = urls.iter().map(|u| u.parent());
|
|
let Some(first) = it.next().unwrap() else {
|
|
return 0; // The first URL has no parent
|
|
};
|
|
|
|
let mut common = first.components().count();
|
|
for parent in it {
|
|
let Some(parent) = parent else {
|
|
return 0; // One of the URLs has no parent
|
|
};
|
|
|
|
common = first
|
|
.components()
|
|
.zip(parent.components())
|
|
.take_while(|(a, b)| match (a, b) {
|
|
(Component::Auth(a), Component::Auth(b)) => a.covariant(b),
|
|
(a, b) => a == b,
|
|
})
|
|
.count()
|
|
.min(common);
|
|
|
|
if common == 0 {
|
|
break; // No common root found
|
|
}
|
|
}
|
|
|
|
common
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
fn test_max_common_root() {
|
|
yazi_shared::init_tests();
|
|
|
|
fn assert(input: &[&str], expected: &str) {
|
|
use std::{ffi::OsStr, str::FromStr};
|
|
let urls: Vec<_> =
|
|
input.iter().copied().map(UrlBuf::from_str).collect::<Result<_, _>>().unwrap();
|
|
|
|
let mut comp = urls[0].components();
|
|
for _ in 0..comp.clone().count() - max_common_root(&urls) {
|
|
comp.next_back();
|
|
}
|
|
assert_eq!(comp.os_str(), OsStr::new(expected));
|
|
}
|
|
|
|
assert_eq!(max_common_root(&[]), 0);
|
|
assert(&[""], "");
|
|
assert(&["a"], "");
|
|
|
|
assert(&["/a"], "/");
|
|
assert(&["/a/b"], "/a");
|
|
assert(&["/a/b/c", "/a/b/d"], "/a/b");
|
|
assert(&["/aa/bb/cc", "/aa/dd/ee"], "/aa");
|
|
assert(&["/aa/bb/cc", "/aa/bb/cc/dd/ee", "/aa/bb/cc/ff"], "/aa/bb");
|
|
}
|
|
|
|
pub async fn create_owned_dir(p: &Path) -> io::Result<()> {
|
|
let p = p.to_owned();
|
|
tokio::task::spawn_blocking(move || create_owned_dir_blocking(&p)).await?
|
|
}
|
|
|
|
pub fn create_owned_dir_blocking(p: &Path) -> io::Result<()> {
|
|
#[cfg(unix)]
|
|
{
|
|
use std::{fs::{DirBuilder, OpenOptions}, mem, os::unix::{fs::{DirBuilderExt, OpenOptionsExt}, io::AsRawFd}};
|
|
|
|
use libc::{O_DIRECTORY, O_NOFOLLOW};
|
|
use uzers::Users;
|
|
use yazi_shared::USERS_CACHE;
|
|
|
|
DirBuilder::new().mode(0o700).recursive(true).create(p)?;
|
|
let dir = OpenOptions::new().read(true).custom_flags(O_DIRECTORY | O_NOFOLLOW).open(p)?;
|
|
|
|
let mut stat: libc::stat = unsafe { mem::zeroed() };
|
|
if unsafe { libc::fstat(dir.as_raw_fd(), &mut stat) } != 0 {
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
|
|
// Reject directories not owned by the current user.
|
|
let uid = USERS_CACHE.get_current_uid();
|
|
if stat.st_uid != uid {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::PermissionDenied,
|
|
format!("directory {:?} is owned by uid {} but current uid is {}", p, stat.st_uid, uid),
|
|
));
|
|
}
|
|
|
|
// Enforce mode 0o700 via the fd.
|
|
if unsafe { libc::fchmod(dir.as_raw_fd(), 0o700) } != 0 {
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
#[cfg(not(unix))]
|
|
{
|
|
std::fs::DirBuilder::new().recursive(true).create(p)
|
|
}
|
|
}
|