Skip to content
Back to skills

1206 File Io A3b40f5f

ASecurity

Patterns for durable writes, concurrency, and file watching.

  • 9 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 31, 2026
testingrust

Security analysis

A100/100

Scanned May 31, 2026

npx -y skills add tools-only/X-Skills --skill 1206-file-io_a3b40f5f --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 1206 File Io A3b40f5f?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 1206 File Io A3b40f5f
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-1206-file-io-a3b40f5f/badge)](https://www.skillsdirectory.com/skills/tools-only-1206-file-io-a3b40f5f)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# File I/O & Persistence

Patterns for durable writes, concurrency, and file watching.

## Table of Contents

- [Atomic Writes](#atomic-writes)
- [Concurrency Control](#concurrency-control)
- [File Watching](#file-watching)
- [When NOT to Use Atomic Writes](#when-not-to-use-atomic-writes)

---

## Atomic Writes

Write to temp file, sync to disk, then rename:

```rust
use std::{io::Write, path::Path};
use tempfile::NamedTempFile;

fn atomic_write(path: &Path, contents: &str) -> std::io::Result<()> {
    // Create temp file in SAME directory as target.
    // Why: rename() fails across filesystem boundaries.
    let dir = path.parent().unwrap_or_else(|| Path::new("."));
    let mut tmp = NamedTempFile::new_in(dir)?;

    tmp.write_all(contents.as_bytes())?;
    tmp.flush()?;

    // Critical: ensure OS flushes to disk BEFORE rename.
    // Without this, power loss after rename could leave truncated file.
    tmp.as_file().sync_all()?;

    tmp.persist(path).map(|_| ()).map_err(|e| e.error)
}
```

**Durable-First Invariant**: Never gate persistence on validation or subprocess output:

1. Write raw data to storage (e.g., `status: pending`)
2. Return control immediately
3. Async enrichment updates later (or not at all)

---

## Concurrency Control

### In-Process: Mutex Across Full Operation

A `Mutex` protects the in-memory state, but the lock must span the **entire** read-modify-write cycle:

```rust
use std::sync::Mutex;
use std::path::Path;

struct FileState {
    data: MyData,
}

static STATE: Mutex<FileState> = Mutex::new(FileState { data: MyData::default() });

fn update_file(path: &Path, change: Change) -> std::io::Result<()> {
    // Lock held from read through write
    let mut state = STATE.lock().unwrap();

    // Re-read file to get latest (another process may have changed it)
    let current = std::fs::read_to_string(path)?;
    let mut data: MyData = serde_json::from_str(&current)?;

    // Apply change
    data.apply(change);

    // Write back atomically
    atomic_write(path, &serde_json::to_string_pretty(&data)?)?;

    // Update in-memory state
    state.data = data;

    Ok(())
    // Lock released here
}
```

**Bug this prevents**: Without holding the lock across the full operation:

1. Thread A reads file, releases lock
2. Thread B reads file, releases lock
3. Thread A writes changes
4. Thread B writes changes → **Thread A's changes lost**

### Out-of-Process: Advisory Locking

For coordination between separate processes, use advisory locks via `fs2`:

```rust
use fs2::FileExt;
use std::fs::File;

fn with_file_lock<T>(path: &Path, f: impl FnOnce() -> T) -> std::io::Result<T> {
    let lock_file = File::create(path.with_extension("lock"))?;
    lock_file.lock_exclusive()?;  // Blocks until lock acquired

    let result = f();

    lock_file.unlock()?;
    Ok(result)
}
```

**Platform caveats** (document if using advisory locks):

| Platform | Caveat |
|----------|--------|
| NFS | Advisory locks unreliable or unsupported depending on configuration |
| macOS | `flock` locks are per-process, not per-file-descriptor |
| Windows | Different locking semantics; consider `LockFileEx` |

If advisory locking is too complex, prefer merge-friendly designs:

1. Re-read file immediately before write
2. Apply patch to latest parsed model
3. Write back atomically
4. Accept that last-writer-wins on conflicts

---

## File Watching

Use debounced file watchers for external edit detection:

```rust
use notify::{Watcher, RecursiveMode, watcher};
use std::sync::mpsc::channel;
use std::time::Duration;

fn watch_file(path: &Path) -> notify::Result<()> {
    let (tx, rx) = channel();

    // Debounce: editors save via multiple quick writes (temp + rename)
    let mut watcher = watcher(tx, Duration::from_millis(200))?;
    watcher.watch(path, RecursiveMode::NonRecursive)?;

    loop {
        match rx.recv() {
            Ok(event) => {
                // Re-parse file and compute diff
                // Send "data changed" notification (avoid huge payloads)
            }
            Err(e) => break,
        }
    }
    Ok(())
}
```

**Threading model**: The watcher thread should only push events. A separate model thread owns the data and processes events via channel. This avoids shared mutable state.

---

## When NOT to Use Atomic Writes

Atomic writes aren't always the right choice:

| Scenario | Better Approach |
|----------|-----------------|
| Append-only logs | Direct append with `fsync` |
| Large files, small updates | In-place modification with record locking |
| Write-ahead logs | Append + checkpoint |
| Streaming data | Buffered writes with periodic sync |

For append-only patterns:

```rust
use std::fs::OpenOptions;
use std::io::Write;

fn append_log(path: &Path, entry: &str) -> std::io::Result<()> {
    let mut file = OpenOptions::new()
        .create(true)
        .append(true)
        .open(path)?;

    writeln!(file, "{}", entry)?;
    file.sync_all()?;  // Ensure durability
    Ok(())
}
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.