oldj/SwitchHosts · error · StorageError
PermissionDenied
PermissionDenied
Error message
directory is not writable
What it means
V5Paths::ensure_usable probes each v5 storage directory (root, entries, internal, histories) by actually writing a '.swh-write-probe.tmp' file via fs_copy::is_writable_dir (src-tauri/src/storage/fs_copy.rs:161). If any probe write fails, the dir is treated as unusable and the function returns StorageError::io wrapping io::ErrorKind::PermissionDenied with message 'directory is not writable'. This is deliberate: create_dir_all returns Ok on an existing read-only directory, so a real write probe is the only reliable check before later data writes would crash.
Source
Thrown at src-tauri/src/storage/paths.rs:83
/// Like `ensure_dirs`, but also verifies every v5 directory is actually
/// writable (root, entries, internal, histories). `ensure_dirs` is a
/// no-op on already-existing dirs, so it can't tell a read-only sub-dir
/// from a usable one; this probes each with a temp file. Use before
/// committing to a data root (apply pre-check and startup), so an
/// unwritable target can't be saved and later crash data writes.
pub fn ensure_usable(&self) -> Result<(), StorageError> {
self.ensure_dirs()?;
for dir in [
&self.root,
&self.entries_dir,
&self.internal,
&self.histories_dir,
] {
if !super::fs_copy::is_writable_dir(dir) {
return Err(StorageError::io(
dir.display().to_string(),
std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
"directory is not writable",
),
));
}
}
Ok(())
}
/// Remove leftover `.tmp` files from `atomic_write` that survived
/// a crash or force-kill. Each v5 directory is scanned for files
/// ending in `.tmp`; matches are deleted silently. This is safe
/// because `atomic_write` writes to `<target>.tmp` then renames
/// to `<target>` — a leftover `.tmp` is always a partial write
/// that never became the real file.
pub fn cleanup_tmp_files(&self) {
let dirs = [
&self.root,View on GitHub (pinned to 6ecea88d92)
Solutions
- Check mount status and permissions of the data root: run `mount | grep <dir>` (macOS/Linux) or verify the drive isn't a read-only DMG/network share; remount read-write or pick a writable location.
- Fix ownership/permissions: chmod u+w (or on Windows, grant Modify on the folder to the current user) on the data dir and its entries/internal/histories subdirs.
- Point SwitchHosts at a different data directory (the app's recovery flow / 'Choose New Folder') on a local writable volume, then restart.
- If this happens at every launch and blocks startup (see error 2), move the offending data dir aside and let the app recreate the v5 layout fresh.
Example fix
# before data-root -> /Volumes/SwitchHosts-DMG/data (read-only mount) # after # remount writable or relocate the data dir mount -u rw /Volumes/SwitchHosts-DMG # or in-app: choose a new data folder on the local disk
Defensive patterns
Strategy: validation
Validate before calling
use crate::storage::fs_copy::is_writable_dir;
// Before relying on a chosen data root:
let candidate = std::path::PathBuf::from("/chosen/data-dir");
if !is_writable_dir(&candidate) {
eprintln!("data dir {} is not writable; pick another", candidate.display());
// prompt user for a different location instead of proceeding
} Type guard
null
Try / catch
// Rust: handle the StorageError from ensure_usable and inspect the path
match paths.ensure_usable() {
Ok(()) => {}
Err(StorageError::Io { path, .. }) if path.contains("not writable") || true => {
// degrade gracefully: fall back to default root / show chooser dialog
log::warn!("storage root {path} unusable");
}
Err(e) => return Err(e),
} Prevention
- Pre-validate custom data directories with is_writable_dir before accepting them in settings, not after.
- Avoid data roots on removable or network volumes unless write access is confirmed at selection time.
- Re-run ensure_usable when a volume remounts or the app regains focus after sleep, since writability can change under you.
- On Windows, verify ACL Modify rights rather than just folder existence when validating a user-picked path.
When it happens
Trigger: Calling V5Paths::ensure_usable (directly, or transitively through AppState::bootstrap at startup) when any of root/entries_dir/internal/histories_dir exists but rejects writes: read-only volume (DMG mount, ro exFAT/network share), dir mode 555 owned by another user, Windows ACL deny, macOS SIP-protected location, or a dir the process lacks write perms for. The probe std::fs::write of dir.join(".swh-write-probe.tmp") fails for any of these and the error is emitted.
Common situations: User configured a custom SwitchHosts data directory on an external/USB/network drive mounted read-only or currently unavailable in ro mode; app data dir was chmod'ed or chown'ed away; running the app from a read-only installer image or sandboxed environment without write grants to the chosen data location; disk full or quota exceeded can also make the probe write fail.
Related errors
AI-assisted analysis of oldj/SwitchHosts@6ecea88d92 (2026-08-16).
Data as JSON: /api/errors/957f30790012d1cf.
Report an issue: GitHub.