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" )
Related Namespaces
-
ol.clave.storage- Storage protocol and utilities -
ol.clave.lease- Cooperative cancellation
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.