| Fugu::File(3p) | Perl Library Manual | Fugu::File(3p) |
NAME
Fugu::File - file operations for a daemon and its tools
SYNOPSIS
use Fugu::File;
my $data = Fugu::File->read('/etc/mydaemon.conf');
Fugu::File->write_atomic('/var/db/myapp/key', $key, mode => 0600);
Fugu::File->write_json('/var/db/myapp/state.json', $state,
mode => 0600);
Fugu::File->ensure_dir('/var/db/myapp');
DESCRIPTION
Fugu::File holds the file operations that a daemon and its tools repeat. Every method is a class method over a path. The module keeps no state.
A recoverable failure returns "undef" and reports through Fugu::Log. A class-method module has no object to hold an error accessor, so the log is where the reason goes.
The write methods set the mode at the open, before the first byte. A chmod(2) after the write leaves a window in which a secret is world-readable, and that window is the whole point of the mode.
read
read($path) returns the whole content of the file, as bytes. The method returns "undef" when the file does not open. An empty file reads as the empty string, which is not the same answer.
write
"write($path, $data, %args)" writes the bytes to the file. An existing file goes away first, so the mode argument means what it says instead of yielding to the mode the old file had.
This is the argument:
- "mode"
- The file mode. The default is 0644.
write_atomic
"write_atomic($path, $data, %args)" writes through a temporary file in the same directory, then renames over the target. A reader sees the old content or the new content, and never a half-written file. The argument is the same as for "write".
The temporary file is a sibling of the target, because rename(2) is atomic only inside one filesystem.
read_json
read_json($path) reads and decodes a JSON file. The method returns "undef" when the file is absent, empty, or not valid JSON. A state file that a crash truncated is a recoverable condition: the caller starts over.
write_json
"write_json($path, $ref, %args)" encodes and writes a JSON file with "write_atomic". The argument is the same.
The encoding is canonical, so the same data always gives the same bytes. Thus a difference between two state files is a difference in the state.
ensure_dir
"ensure_dir($path, %args)" makes sure the directory exists.
The method refuses a symlink and refuses a path that exists as something else. Both are conditions that a daemon must not write through: a symlink puts the daemon's files where the owner of the link decided.
This is the argument:
- "mode"
- The mode for directories that the method creates. The default is 0755.
expand_tilde
expand_tilde($path) replaces a leading tilde with the home directory. The method leaves every other path unchanged, and leaves ~user alone: this module does not resolve other users.
share_path
"share_path($relative, %args)" resolves a file that ships with a distribution. The search tries, in order: an explicit root, the checkout that holds the caller's module file, the working directory, and the installed share tree of the named distribution under @INC. The method returns the first path that exists, or "undef".
These are the arguments:
- "root"
- A directory to search before the others.
- "from"
- The caller's module file, normally "__FILE__". The directory above the deepest lib component of its path is the root of the checkout that ships the data.
- "dist"
- The distribution name. An installed distribution keeps its share files under auto/share/dist/<name>, with the leading share/ component of $relative removed.
atomic_dir
"atomic_dir($target, $code)" builds a directory tree beside $target and publishes it with one rename(2). $code receives the temporary directory and returns true when the tree is complete. A false return, a die, or a signal discards the tree.
The method refuses to overwrite an existing target. A rename over a populated directory is not atomic, so a caller that wants a replacement removes the old tree itself.
A build can run for minutes over a large file. The method therefore installs handlers for "SIGINT" and "SIGTERM" for the length of the call, and an "END" block covers the rest.
sweep_temp
sweep_temp($parent) removes the leftover build directories under $parent. A kill that no handler can catch leaves them. The method returns the count it removed.
valid_name
valid_name($name) reports if the string is safe as one path component.
The check refuses an empty name, a name longer than 255 characters, a name that holds a path separator or a NUL byte, and the two directory entries . and ... Every one of them turns a name that came from a caller into a path that reaches outside the directory it was meant for.
RETURN VALUES
"read" returns the content of the file. "read_json" returns the decoded structure. Both return "undef" on failure.
"write", "write_atomic", "write_json" and "ensure_dir" return 1 on success and "undef" on failure.
"atomic_dir" returns $target on success and "undef" otherwise. "sweep_temp" returns a count. "valid_name" returns 1 or 0. "expand_tilde" and "share_path" return a path.
EXAMPLES
This example stores a key that only its owner may read:
Fugu::File->ensure_dir('/var/db/myapp', mode => 0700)
or die "cannot prepare the state directory\n";
Fugu::File->write_atomic('/var/db/myapp/ltsk', $secret,
mode => 0600)
or die "cannot store the key\n";
This example builds a cache entry that is never half-published:
Fugu::File->atomic_dir("$cache/$key", sub ($tmp) {
convert($disk, "$tmp/base") or return 0;
Fugu::File->write_json("$tmp/meta.json", $meta, mode => 0600)
or return 0;
return 1;
});
ERRORS
No method dies. Each one reports a failure through its return value and a message in the log.
SEE ALSO
chmod(2), rename(2), Fugu::Log, Fugu::StateFile, JSON::PP
AUTHORS
Dick Olsson <hi@senzilla.io>
CAVEATS
"read" holds the whole file in memory. Do not use it for a file without a bound.
"write_atomic" is atomic against a reader, not against another writer. Two processes that write the same path at the same time both succeed, and the last rename wins. Use Fugu::Pidfile or a lock of your own where that matters.
| 2026-10-16 | OpenBSD |