diff --git a/yazi-actor/src/lives/behavior.rs b/yazi-actor/src/lives/behavior.rs new file mode 100644 index 00000000..c97f3c1e --- /dev/null +++ b/yazi-actor/src/lives/behavior.rs @@ -0,0 +1,29 @@ +use std::ops::Deref; + +use mlua::{AnyUserData, UserData, UserDataMethods}; + +use super::{Lives, PtrCell}; + +pub(super) struct Behavior { + inner: PtrCell, +} + +impl Deref for Behavior { + type Target = yazi_scheduler::Behavior; + + fn deref(&self) -> &Self::Target { &self.inner } +} + +impl Behavior { + pub(super) fn make(inner: &yazi_scheduler::Behavior) -> mlua::Result { + let inner = PtrCell::from(inner); + + Lives::scoped_userdata(Self { inner }) + } +} + +impl UserData for Behavior { + fn add_methods>(methods: &mut M) { + methods.add_method("reset", |_, me, ()| Ok(me.reset())); + } +} diff --git a/yazi-actor/src/lives/mod.rs b/yazi-actor/src/lives/mod.rs index d6c82311..594dc7fd 100644 --- a/yazi-actor/src/lives/mod.rs +++ b/yazi-actor/src/lives/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(core file files filter finder folder lives mode preference preview ptr selected tab tabs task tasks which yanked); +yazi_macro::mod_flat!(behavior core file files filter finder folder lives mode preference preview ptr selected tab tabs task tasks which yanked); diff --git a/yazi-actor/src/lives/tasks.rs b/yazi-actor/src/lives/tasks.rs index e547c138..b19da6cb 100644 --- a/yazi-actor/src/lives/tasks.rs +++ b/yazi-actor/src/lives/tasks.rs @@ -4,13 +4,14 @@ use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value}; use yazi_binding::{SER_OPT, cached_field}; use super::{Lives, PtrCell}; -use crate::lives::TaskSnap; +use crate::lives::{Behavior, TaskSnap}; pub(super) struct Tasks { inner: PtrCell, - v_snaps: Option, - v_summary: Option, + v_behavior: Option, + v_snaps: Option, + v_summary: Option, } impl Deref for Tasks { @@ -24,8 +25,9 @@ impl Tasks { Lives::scoped_userdata(Self { inner: inner.into(), - v_snaps: None, - v_summary: None, + v_behavior: None, + v_snaps: None, + v_summary: None, }) } } @@ -34,6 +36,8 @@ impl UserData for Tasks { fn add_fields>(fields: &mut F) { fields.add_field_method_get("cursor", |_, me| Ok(me.cursor)); + cached_field!(fields, behavior, |_, me| Behavior::make(&me.scheduler.behavior)); + cached_field!(fields, snaps, |lua, me| { let tbl = lua.create_table_with_capacity(me.snaps.len(), 0)?; for snap in &me.snaps { diff --git a/yazi-actor/src/tasks/spawn.rs b/yazi-actor/src/tasks/spawn.rs index 963bd757..efa4b535 100644 --- a/yazi-actor/src/tasks/spawn.rs +++ b/yazi-actor/src/tasks/spawn.rs @@ -16,6 +16,7 @@ impl Actor for Spawn { fn act(cx: &mut Ctx, form: Self::Form) -> Result { succ!(match form.opt { TaskOpt::Cut(r#in) => cx.tasks.scheduler.file_cut(r#in), + TaskOpt::Plugin(r#in) => cx.tasks.scheduler.plugin_entry(r#in), }) } diff --git a/yazi-config/src/preview/preview.rs b/yazi-config/src/preview/preview.rs index 4556a110..18dfcd41 100644 --- a/yazi-config/src/preview/preview.rs +++ b/yazi-config/src/preview/preview.rs @@ -1,11 +1,9 @@ -#[cfg(unix)] -use std::os::unix::fs::DirBuilderExt; -use std::{fs::DirBuilder, path::PathBuf}; +use std::path::PathBuf; use anyhow::{Context, Result}; use serde::{Deserialize, Deserializer, Serialize}; use yazi_codegen::DeserializeOver2; -use yazi_fs::Xdg; +use yazi_fs::{Xdg, create_owned_dir_blocking}; use yazi_shared::{SStr, timestamp_us}; use yazi_shim::toml::DeserializeOverHook; @@ -51,12 +49,7 @@ impl Preview { impl DeserializeOverHook for Preview { fn deserialize_over_hook(self) -> Result { - #[cfg(unix)] - let result = DirBuilder::new().mode(0o700).recursive(true).create(&self.cache_dir); - #[cfg(not(unix))] - let result = DirBuilder::new().recursive(true).create(&self.cache_dir); - - result + create_owned_dir_blocking(&self.cache_dir) .context(format!("Failed to create cache directory: {}", self.cache_dir.display())) .map_err(serde::de::Error::custom)?; diff --git a/yazi-dds/src/stream.rs b/yazi-dds/src/stream.rs index 4b2915da..b09a6a0f 100644 --- a/yazi-dds/src/stream.rs +++ b/yazi-dds/src/stream.rs @@ -53,18 +53,14 @@ impl Stream { #[cfg(unix)] async fn socket_file() -> io::Result<&'static std::path::PathBuf> { - use tokio::{fs::DirBuilder, sync::OnceCell}; - use yazi_fs::Xdg; + use tokio::sync::OnceCell; + use yazi_fs::{Xdg, create_owned_dir}; - static ONCE: tokio::sync::OnceCell = OnceCell::const_new(); + static ONCE: OnceCell = OnceCell::const_new(); ONCE .get_or_try_init(|| async move { let p = Xdg::runtime_dir(); - - #[cfg(unix)] - DirBuilder::new().mode(0o700).recursive(true).create(p).await?; - #[cfg(not(unix))] - DirBuilder::new().recursive(true).create(p).await?; + create_owned_dir(p).await?; Ok(p.join(".dds.sock")) }) diff --git a/yazi-fs/src/fns.rs b/yazi-fs/src/fns.rs index 4718d1f0..4772769f 100644 --- a/yazi-fs/src/fns.rs +++ b/yazi-fs/src/fns.rs @@ -1,4 +1,5 @@ -use tokio::io; +use std::{io, path::Path}; + use yazi_shared::url::{Component, UrlBuf, UrlLike}; #[inline] @@ -74,3 +75,47 @@ fn test_max_common_root() { 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) + } +} diff --git a/yazi-plugin/preset/components/tip.lua b/yazi-plugin/preset/components/tip.lua index e4c0ed36..8012e4ce 100644 --- a/yazi-plugin/preset/components/tip.lua +++ b/yazi-plugin/preset/components/tip.lua @@ -13,7 +13,7 @@ function Tip:layout() :direction(ui.Layout.VERTICAL) :constraints({ ui.Constraint.Fill(1), - ui.Constraint.Length(1), + ui.Constraint.Length(3), ui.Constraint.Fill(1), }) :split(self._area) @@ -24,6 +24,7 @@ function Tip:reflow() return {} end function Tip:redraw() return { ui.Clear(self._chunks[2]), - ui.Text(self._text):area(self._chunks[2]):align(ui.Align.CENTER):fg("black"):bg("yellow"):bold(), + ui.Text(""):area(self._chunks[2]):align(ui.Align.CENTER):bg("yellow"), + ui.Text(self._text):area(self._chunks[2]:pad(ui.Pad.xy(1, 1))):align(ui.Align.CENTER):fg("black"):bold(), } end diff --git a/yazi-plugin/preset/plugins/dnd.lua b/yazi-plugin/preset/plugins/dnd.lua index 039cf90b..fae4c6ea 100644 --- a/yazi-plugin/preset/plugins/dnd.lua +++ b/yazi-plugin/preset/plugins/dnd.lua @@ -12,6 +12,7 @@ function M.selected_uri_list() end function M.cut_uri_list(list) + cx.tasks.behavior:reset() for line in list:gmatch("[^\r\n]+") do if line:sub(1, 7) ~= "file://" then goto continue