flxzt/rnote · error
The filepath does not have a parent directory
Error message
The filepath does not have a parent directory
What it means
Thrown by atomic_save_to_file in rnote-engine utils when Path::parent() returns None for the given filepath. In Rust this happens only for paths with no parent component, i.e. the root path "/" (or effectively empty paths), for which no directory exists in which to create the atomic temporary file.
Solutions
- Validate the path has a parent directory before calling atomic_save_to_file
- Reject root/empty paths at the API boundary with a user-facing error
- Join the file name to a known directory instead of passing a bare path
- Use filepath.has_parent()-style checks via filepath.parent().is_some()
Example fix
// before
atomic_save_to_file("/", bytes)?;
// after
let path = Path::new("/tmp").join("file.dat");
assert!(path.parent().is_some());
atomic_save_to_file(&path, bytes)?; Defensive patterns
Strategy: validation
Validate before calling
if filepath.parent().is_none() {
anyhow::bail!("path {:?} has no parent directory", filepath);
}
if !filepath.parent().unwrap().is_dir() {
std::fs::create_dir_all(filepath.parent().unwrap())?;
} Type guard
fn has_parent(p: &std::path::Path) -> bool { p.parent().is_some() && p.file_name().is_some() } Try / catch
match atomic_save_to_file(&path, bytes) {
Ok(()) => {},
Err(e) if e.to_string().contains("parent directory") => eprintln!("invalid save path: {}", path.display()),
Err(e) => return Err(e),
} Prevention
- Never save to bare root or empty paths; always join a directory and file name
- Validate user-configured save paths at input time
- Create parent directories before atomic save if they may not exist
When it happens
Trigger: Passing a filepath that is the filesystem root "/" (or a bare path whose parent() is None) to atomic_save_to_file.
Common situations: Building a save path from a config value that is empty or reduced to "/", unvalidated user-supplied paths, or path-joining bugs that strip the directory component.
Related errors
- Failed to get file stem
- Failed to get file name from output-file
- Failed to get file stem from rnote file
- Failed to init audioplayer. file
- file of source path ' ' does not have a file stem.
AI-assisted analysis of flxzt/rnote@bbc5354502 (2026-09-08).
Data as JSON: /api/errors/33930fb7b90158b6.
Report an issue: GitHub.
Appendix: source
Thrown at crates/rnote-engine/src/utils.rs:135
/// Deserialize base64 encoded [glib::Bytes]
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<glib::Bytes, D::Error> {
rnote_compose::serialize::sliceu8_base64::deserialize(d).map(glib::Bytes::from_owned)
}
}
/// Attempts to atomically save data to a file.
/// Not asynchronous, wrap with `blocking::unblock()` or equivalent to avoid blocking.
pub fn atomic_save_to_file<Q, B>(filepath: Q, bytes: B) -> anyhow::Result<()>
where
Q: AsRef<std::path::Path>,
B: AsRef<[u8]>,
{
let filepath = filepath.as_ref();
let bytes = bytes.as_ref();
let parent_directory = filepath
.parent()
.ok_or_else(|| anyhow::anyhow!("The filepath does not have a parent directory"))?;
// We first create the named temporary file, specifically in the parent
// directory of the target filepath, as `.persist()` will not work
// if the temporary file is in a different filesystem than the target.
let mut temp_file = tempfile::NamedTempFile::new_in(parent_directory)
.with_context(|| "Failed to create a temporary file")?;
// We then write all of our bytes to the temporary file before syncing its contents.
temp_file
.write_all(bytes)
.with_context(|| "Failed to write to the temporary file")?;
temp_file
.as_file()
.sync_all()
.with_context(|| "Failed to sync the contents and metadata of the temporary file")?;
// Finally, we persist the temporary file to the target filepath, if a file
// preexists at this location, it will be atomically replaced by our new file.View on GitHub (pinned to bbc5354502)