googleworkspace/cli · error · std::io::Error

InvalidInput

InvalidInput

Error message

path has no parent directory

What it means

atomic_write() rejects its target path with io::ErrorKind::InvalidInput when Path::parent() returns None. On Unix that only happens for the empty path "" and the filesystem root "/" (on Windows also for drive prefixes like "C:\"). The guard exists because tempfile::NamedTempFile::new_in(parent) needs a real directory to host the temporary file that is later atomically renamed over the target.

Source

Thrown at crates/google-workspace-cli/src/fs_util.rs:32

//! File-system utilities.

use std::io::{self, Write};
use std::path::Path;

/// Write `data` to `path` atomically.
///
/// This implementation uses `tempfile::NamedTempFile` to create a temporary
/// file with a random name, `O_EXCL` flags (preventing symlink attacks),
/// and secure 0600 permissions from the moment of creation.
///
/// # Errors
///
/// Returns an `io::Error` if the temporary file cannot be created/written or if the
/// final rename fails.
pub fn atomic_write(path: &Path, data: &[u8]) -> io::Result<()> {
    let parent = path.parent().ok_or_else(|| {
        io::Error::new(io::ErrorKind::InvalidInput, "path has no parent directory")
    })?;

    let mut tmp = tempfile::NamedTempFile::new_in(parent)?;
    tmp.write_all(data)?;
    tmp.as_file().sync_all()?;
    tmp.persist(path)
        .map_err(|e| io::Error::new(e.error.kind(), e.error))?;

    Ok(())
}

/// Async variant of [`atomic_write`] for use with tokio.
///
/// This implementation uses `create_new(true)` (O_EXCL) and `mode(0o600)` to
/// prevent TOCTOU/symlink race conditions.
pub async fn atomic_write_async(path: &Path, data: &[u8]) -> io::Result<()> {
    use rand::Rng;
    use tokio::io::AsyncWriteExt;

View on GitHub (pinned to a3768d0e82)

Solutions

  1. Ensure the target is a concrete file path: build it as dir.join("filename") where dir comes from a validated config directory (e.g., dirs::config_dir() with a fallback), never pass a bare root or empty string
  2. Trace where the empty/root path originates — usually an env var (HOME, XDG_CONFIG_HOME) that is unset or empty — and default it explicitly
  3. Add a debug_assert!/early return in callers when path.as_os_str().is_empty() to catch regressions during development

Example fix

// before
let p = std::path::Path::new(""); // degenerate path from empty cfg dir
fs_util::atomic_write(p, &bytes)?;

// after
let dir = std::env::var_os("CFG_DIR").unwrap_or_else(|| ".".into());
let p = std::path::Path::new(&dir).join("credentials.enc");
fs_util::atomic_write(&p, &bytes)?;
Defensive patterns

Strategy: validation

Validate before calling

use std::path::Path;

fn ensure_writable_file_path(p: &Path) -> std::io::Result<()> {
    if p.as_os_str().is_empty() {
        return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "empty target path"));
    }
    if p.parent().is_none() {
        return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "target must be a file under a directory, not a root"));
    }
    Ok(())
}

ensure_writable_file_path(&path)?;
fs_util::atomic_write(&path, &bytes)?;

Try / catch

if let Err(e) = fs_util::atomic_write(&path, &bytes) {
    if e.kind() == std::io::ErrorKind::InvalidInput {
        eprintln!("misconfigured target path '{path:?}': pass dir.join(\"file\") instead of a root/empty path");
    }
}

Prevention

When it happens

Trigger: Calling atomic_write(Path::new(""), ...) after a config-dir variable came back empty; passing Path::new("/") because a join chain collapsed (e.g., an empty filename was joined onto the root); on Windows, passing a drive root like "C:\\"; any code that builds a path from user input without checking it is a full file path.

Common situations: HOME or GOOGLE_WORKSPACE_CLI_CONFIG_DIR resolution returned an empty string and the code joined it into a path that degenerated to the root; a path built as base.join("") where base is already "/"; unit tests passing a placeholder empty Path.

Related errors


AI-assisted analysis of googleworkspace/cli@a3768d0e82 (2026-08-16). Data as JSON: /api/errors/a607fb749c1bdad3. Report an issue: GitHub.