sxyazi/yazi · error · io::Error

invalid trash entry path

Error message

invalid trash entry path

What it means

`DsStore::join` (yazi-fs/src/trash/macos/ds_store.rs:38) rebuilds a put-back path for a macOS trashed item from the `.DS_Store` `ptbL`/`ptbN` records. The `rel` argument (the child path inside a trashed directory) must be a single relative component — not absolute and free of parent components. Anything else returns `ErrorKind::InvalidInput`, `invalid trash entry path`, preventing path traversal out of the trashed tree.

Source

Thrown at yazi-fs/src/trash/macos/ds_store.rs:38

			let Value::Ustr(value) = record.value else { continue };
			if value.is_empty() {
				continue;
			}

			let location = locations.entry_ref(OsStr::new(&record.name)).or_default();
			match &record.field.fourcc().bytes() {
				b"ptbL" => location.parent = Some(value.into()),
				b"ptbN" => location.name = Some(value.into()),
				_ => {}
			}
		}

		Ok(locations)
	}

	pub(super) fn join(&self, rel: &Path) -> io::Result<PathBuf> {
		if !rel.is_relative() || rel.has_parent_component() {
			return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid trash entry path"));
		}

		let parent = self.parent.as_deref().ok_or_else(|| {
			io::Error::new(io::ErrorKind::InvalidData, "trash item has no put-back location")
		})?;

		let name = self.name.as_deref().ok_or_else(|| {
			io::Error::new(io::ErrorKind::InvalidData, "trash item has no put-back name")
		})?;

		let mut components = Path::new(name).components();
		if !matches!(components.next(), Some(Component::Normal(_))) || components.next().is_some() {
			return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid trash put-back name"));
		}

		let top_path = Path::new("/").join(parent).join(name);
		Ok(if rel.as_os_str().is_empty() { top_path } else { top_path.join(rel) })
	}

View on GitHub (pinned to 94abcfa92f)

Solutions

  1. Only pass child names obtained directly from listing the trashed directory (single `Normal` component).
  2. Validate `rel.is_relative() && !rel.has_parent_component()` before calling `join`.
  3. Rebuild child ids from a fresh listing instead of reusing persisted ones.

Example fix

// before
let path = ds_store.join(Path::new("../escape"))?; // InvalidInput

// after
use yazi_shim::path::PathExt;
let rel = Path::new("photo.png");
assert!(rel.is_relative() && !rel.has_parent_component());
let path = ds_store.join(rel)?;
Defensive patterns

Strategy: validation

Validate before calling

use yazi_shim::path::PathExt;

if !rel.is_relative() || rel.has_parent_component() {
    // reject: rel must be a single plain component (a file name from read_dir)
}

Type guard

fn is_single_component(rel: &std::path::Path) -> bool {
    use yazi_shim::path::PathExt;
    rel.is_relative() && !rel.has_parent_component()
}

Try / catch

match ds_store.join(rel) {
    Ok(p) => { /* ... */ }
    Err(e) if e.kind() == io::ErrorKind::InvalidInput => { /* rel malformed: rebuild from a fresh listing */ }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `join` with a `rel` containing `..`, multiple segments (`a/b`), or an absolute path — typically from a `TrashId` whose rel was hand-built or persisted from bad state rather than derived from `read_dir` results.

Common situations: Programmatic child-id construction on macOS trash; stale expanded-trash state rehydrated with mangled rel paths.

Related errors


AI-assisted analysis of sxyazi/yazi@94abcfa92f (2026-08-16). Data as JSON: /api/errors/7e1398838fd392f7. Report an issue: GitHub.