ol.clave.storage.file

Filesystem-based storage implementation.

Provides a FileStorage record that implements ol.clave.storage/Storage and ol.clave.storage/TryLocker using the local filesystem. The presence of a lock file for a given name indicates a lock is held.

Key-to-Path Mapping

Storage keys map directly to filesystem paths relative to a root directory. Key normalization converts backslashes to forward slashes and strips leading and trailing slashes. Path traversal attempts (keys containing .. that escape the root) throw IllegalArgumentException.

Atomic Writes

All writes use atomic move semantics: data is written to a temporary file and renamed into place. This ensures readers never see partial writes. On filesystems that do not support atomic moves, a best-effort rename is used.

Locking

Advisory locking is implemented with lock files in a locks/ subdirectory. Locks are created atomically by relying on the filesystem to enforce exclusive file creation.

Processes that terminate abnormally will not have a chance to clean up their lock files. To handle this, while a lock is actively held, a background virtual thread periodically updates a timestamp in the lock file (every 5 seconds). If another process tries to acquire the lock but fails, it checks whether the timestamp is still fresh. If so, it waits by polling (every 1 second). Otherwise, the stale lock file is deleted, effectively forcing an unlock.

While lock acquisition is atomic, unlocking is not perfectly atomic. Filesystems offer atomic file creation but not necessarily atomic deletion. It is theoretically possible for two processes to discover the same stale lock and both attempt to delete it. If one process deletes the lock file and creates a new one before the other calls delete, the new lock may be deleted by mistake. This means mutual exclusion is not guaranteed to be perfectly enforced in the presence of stale locks. However, these cases are rare, and we prefer the simpler solution over alternatives that risk infinite loops.

Filesystem Considerations

This implementation is designed for local filesystems and relies on specific filesystem semantics:

  • Exclusive file creation via O_CREAT | O_EXCL. Lock acquisition depends on the filesystem atomically failing when creating a file that already exists.

  • Durable writes via fsync. Lock file timestamps must survive crashes to enable stale lock detection.

Network filesystems (NFS, CIFS/SMB, AWS EFS) may not reliably support these semantics. In particular, some network filesystems do not honor O_EXCL across nodes or do not guarantee that data is persisted after fsync, which can leave lock files empty or corrupt after a crash or network interruption.

Permissions

On POSIX systems, files are created with rw------- and directories with rwx------. On non-POSIX systems (Windows), default permissions apply.

Usage

(require '[ol.clave.storage.file :as fs]
         '[ol.clave.storage :as s])

;; Use platform-appropriate directories for certificate storage
(def storage (fs/file-storage (fs/data-dir "myapp")))

;; Or specify a custom path
(def storage (fs/file-storage "/var/lib/myapp"))

;; Store and retrieve data
(s/store-string! storage nil "certs/example.com/cert.pem" cert)
(s/load-string storage nil "certs/example.com/cert.pem")

→LockMeta

(->LockMeta created-ms updated-ms)

LockMeta

map→LockMeta

(map->LockMeta m)

→FileStorage

(->FileStorage root)

FileStorage

map→FileStorage

(map->FileStorage m)

home-dir

(home-dir)

Returns the user’s home directory.

Uses the user.home system property, which the JVM resolves appropriately for each platform. Returns nil if the home directory cannot be determined.


state-dir

(state-dir)
(state-dir app-name)

Returns the directory for persistent application state.

With no arguments, returns the base directory (caller appends app name). With app-name, appends it as a subdirectory (unless systemd provides one).

Checks environment variables in order: 1. $STATE_DIRECTORY - set by systemd for system units (already app-specific) 2. $XDG_STATE_HOME - XDG base directory spec / systemd user units 3. Platform default

When systemd sets $STATE_DIRECTORY, it may contain multiple colon-separated paths if the unit configures multiple directories; this function returns the first path.

Platform defaults when no environment variable is set:

| platform | path | |----------|-------------------------------------| | Linux | $HOME/.local/state | | macOS | $HOME/Library/Application Support | | Windows | %LOCALAPPDATA% |

Returns nil if a suitable directory cannot be determined.

(state-dir)           ; => "/home/user/.local/state"
(state-dir "myapp")  ; => "/home/user/.local/state/myapp"

;; Under systemd system unit with StateDirectory=myapp:
(state-dir)           ; => "/var/lib/myapp"
(state-dir "myapp")  ; => "/var/lib/myapp" (no double append)

(file-storage (state-dir "myapp"))

config-dir

(config-dir)
(config-dir app-name)

Returns the directory for application configuration files.

With no arguments, returns the base directory (caller appends app name). With app-name, appends it as a subdirectory (unless systemd provides one).

Checks environment variables in order: 1. $CONFIGURATION_DIRECTORY - set by systemd for system units (already app-specific) 2. $XDG_CONFIG_HOME - XDG base directory spec / systemd user units 3. Platform default

When systemd sets $CONFIGURATION_DIRECTORY, it may contain multiple colon-separated paths if the unit configures multiple directories; this function returns the first path.

Platform defaults when no environment variable is set:

| platform | path | |----------|-----------------------------| | Linux | $HOME/.config | | macOS | $HOME/Library/Preferences | | Windows | %APPDATA% |

Returns nil if a suitable directory cannot be determined.

(config-dir)           ; => "/home/user/.config"
(config-dir "myapp")  ; => "/home/user/.config/myapp"

;; Under systemd system unit with ConfigurationDirectory=myapp:
(config-dir)           ; => "/etc/myapp"
(config-dir "myapp")  ; => "/etc/myapp" (no double append)

cache-dir

(cache-dir)
(cache-dir app-name)

Returns the directory for application cache files.

With no arguments, returns the base directory (caller appends app name). With app-name, appends it as a subdirectory (unless systemd provides one).

Checks environment variables in order: 1. $CACHE_DIRECTORY - set by systemd for system units (already app-specific) 2. $XDG_CACHE_HOME - XDG base directory spec / systemd user units 3. Platform default

When systemd sets $CACHE_DIRECTORY, it may contain multiple colon-separated paths if the unit configures multiple directories; this function returns the first path.

Platform defaults when no environment variable is set:

| platform | path | |----------|------------------------| | Linux | $HOME/.cache | | macOS | $HOME/Library/Caches | | Windows | %LOCALAPPDATA% |

Returns nil if a suitable directory cannot be determined.

(cache-dir)           ; => "/home/user/.cache"
(cache-dir "myapp")  ; => "/home/user/.cache/myapp"

;; Under systemd system unit with CacheDirectory=myapp:
(cache-dir)           ; => "/var/cache/myapp"
(cache-dir "myapp")  ; => "/var/cache/myapp" (no double append)

data-dir

(data-dir)
(data-dir app-name)

Returns the directory for persistent application data.

This is the recommended directory for ACME certificates and keys because they are valuable cryptographic material with rate limits on reissuance.

With no arguments, returns the base directory (caller appends app name). With app-name, appends it as a subdirectory (unless systemd provides one).

Checks environment variables in order: 1. $STATE_DIRECTORY - set by systemd (StateDirectory= maps to /var/lib/ which is semantically correct) 2. $XDG_DATA_HOME - XDG base directory spec 3. Platform default

When systemd sets $STATE_DIRECTORY, it may contain multiple colon-separated paths if the unit configures multiple directories; this function returns the first path.

Platform defaults when no environment variable is set:

| platform | path | |----------|-------------------------------------| | Linux | $HOME/.local/share | | macOS | $HOME/Library/Application Support | | Windows | %LOCALAPPDATA% |

Returns nil if a suitable directory cannot be determined.

(data-dir)           ; => "/home/user/.local/share"
(data-dir "myapp")  ; => "/home/user/.local/share/myapp"

;; Under systemd system unit with StateDirectory=myapp:
(data-dir)           ; => "/var/lib/myapp"
(data-dir "myapp")  ; => "/var/lib/myapp" (no double append)

(file-storage (data-dir "myapp"))

file-storage

(file-storage)
(file-storage root)

Creates a FileStorage rooted at `root.

root may be a string or java.nio.file.Path. The directory is created if it does not exist.

Default: "ol.clave" subdir inside data-dir

Returns a record implementing ol.clave.storage/Storage and ol.clave.storage/TryLocker.