The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

File::Valet - Utilities for file slurping, locking, and finding.

SYNOPSIS

    use File::Valet;

    # Simple slurp and unslurp with rd_f, wr_f, ap_f:

    my $text = rd_f('some/file.txt');
    die "slurp failure: $File::Valet::ERROR ($File::Valet::ERRNO)" unless ($File::Valet::OK eq 'OK');
    # or, equivalently:
    die "slurp failure: $File::Valet::ERROR ($File::Valet::ERRNO)" unless (defined($text));

    # Contents written will be same as that of "some/file.txt",
    # plus two lines appended at the end:

    wr_f('another/file.txt', $text);
    ap_f('another/file.txt', "Oh, and another thing:\n");
    ap_f('another/file.txt', "STOP BREATHING IN MY CUP\n");

    # Find a place suited to temporary files:
    my $tmp = find_temp();  # Likely /var/tmp or /tmp or C:\Windows\Temp

    # Find the full pathname of an executable:
    my $shell = find_bin('sh');  # Likely /bin/sh

    # Use a lockfile for exclusive access to a shared resource:
    lockafile("$tmp/shared.txt") or die "cannot obtain lock: $File::Valet::ERROR ($File::Valet::ERRNO)";
    my $text = rd_f("$tmp/shared.txt");
    unlockafile("$tmp/shared.txt") or die "unlock error: $File::Valet::ERROR ($File::Valet::ERRNO)";

    # Nested file locking:
    lockafile("shared.txt") or die "cannot obtain first lock";
    my $text = rd_f("$tmp/shared.txt");
    ...
    lockafile("shared.txt") or die "cannot obtain second lock";
    ap_f("$tmp/shared.txt", $data);
    unlockafile("$tmp/shared.txt");
    ...
    unlockafile("$tmp/shared.txt");

    # Your code has bugs, resulting in leaving lockfiles behind, but
    # instead of debugging you'd rather just remove all your locks:
    my ($n_errors, $n_locks, $n_files) = unlock_all_the_files();

DESCRIPTION

File::Valet contains a selection of easy-to-use subroutines for manipulating files and file content. Some effort has been made to establish cross-platform portability, and to make their behavior as unsurprising as possible.

FUNCTIONS

The following functions are available through this module for use in other applications. In keeping with the intent of minimizing user keystrokes, all of these functions are exported into the calling namespace.

rd_f
 my $string = rd_f($filename);

rd_f() is similar to the well-known Slurp::to_scalar(), in that it reads the entire contents of the named file and returns it as a string. Its principle differences are a slightly shorter name and the insertion of diagnostic information into $File::Valet::OK, $File::Valet::ERROR, $File::Valet::ERRNO when the operation failed to complete.

The return value is either the contents of the file as a string of bytes (an empty string if the file had no contents), or undef on any error.

wr_f
 my $success = wr_f($filename, $string);

wr_f() is conceptually the opposite of rd_f(), in that it overwrites the named file's contents with the given string of bytes.

If the specified file does not exist, wr_f() will attempt to create it.

Returns 1 on success, or 0 on any failure, and sets $File::Valet::OK, $File::Valet::ERROR, $File::Valet::ERRNO appropriately.

ap_f
 my $success = ap_f($filename, $string);

ap_f() is similar to wr_f(), differing in that the specified string of bytes is appended to the end of the file, rather than overwriting it.

If the specified file does not exist, ap_f() will attempt to create it.

Returns 1 on success, or 0 on any failure, and sets $File::Valet::OK, $File::Valet::ERROR, $File::Valet::ERRNO appropriately.

find_home
 my $path = find_home;
 my $path = find_temp("/var/home", "/tmp/home");

find_home() performs a best-effort search for the effective user's home, returning a path-string or undef if none is found.

If arguments are provided, it will return the first argument for which there is a directory for which the user has write permissions.

if $ENV{HOME} is set, find_home() will check there for a writable directory after checking any arguments.

Some effort has been made to make it cross-platform.

find_temp
 my $path = find_temp();
 my $path = find_temp("/home/tmp", "/usr/tmp", ...);

Intended for easy cross-platform programming, find_temp() checks in a number of likely, common filesystem locations for a valid directory for temporary files. It returns the first directory it finds for which the user has write permissions, or undef if none is found.

If parameters are passed to find_temp(), it will check those locations first.

If $ENV{TEMPDIR}, $ENV{TEMP} or $ENV{TMP} are defined, find_temp() will check those locations after checking the locations provided as parameters.

find_temp() is Windows-savvy enough to check such locations as "C:\Windows\Temp", but might try to open locations on network-mounted drives if it is unable to find a local alternative.

find_home
  my $path = find_home();
  my $path = find_home("/var/home/fred");

Another function intended for easy cross-platform programming, find_home() will first check $ENV{HOME} on *nix or $ENV{HOMEDIRVE} and $ENV{HOMEPATH (on Windows) if defined, then in the system's passwd database, and then in a number of other likely locations, for a writable home directory for the effective user.

It will return the full absolute path of the home directory on success, or undef on failure.

find_bin
 my $pathname = find_bin("ls");
 my $pathname = find_bin("ls", "/home/ttk/bin", "/opt/bin", ...);

Another function intended for easy cross-platform programming, find_bin() will first check all of the directories in $ENV{PATH} (if defined), and then in a number of other likely, common locations, for an executable file whose name matches the first parameter. It will return the full absolute pathname of the executable file on success, or undef on failure.

find_bin() is Windows-savvy, albeit does not search Windows systems as extensively as others.

If directory paths are given as additional parameters, find_bin() will check those locations first.

find_bin() is smart enough to only check any given directory once, even if it appears in the parameter list as well as in $ENV{PATH}, or appears multiple times in either.

find_bin() also sets $File::Valet::OK, $File::Valet::ERROR, $File::Valet::ERRNO appropriately.

lockafile
 my $success = lockafile("/tmp/foo", %options);
 my $success = lockafile("/tmp/foo",
     limit => 2.0,  # keep retrying for 2.0 seconds before giving up
     msg   => 'in-channel update',  # helpful message for troubleshooting
     nsec  => 0.5,  # we expect to hold the lock for less than 0.5 seconds
 );

lockafile() applies an advisory lock on the named file, and attempts to be somewhat clever about it, automatically invalidating existing locks set by processes which no longer exist. or set a very long time ago.

If the file is already locked by another process, lockafile() will linger and attempt to acquire the lock when the owner of the lock releases the file. This linger time defaults to thirty seconds, and may be overridden with the limit parameter.

The advisory lock takes the form of a file, which may be manually deleted to remove the lock, or may be inspected to learn something about the process which created the lock. To facilitate this, a message may be embedded in the lock file describing the reason the file is being locked. It defaults to "programmer is lazy", and may be set by passing the msg parameter.

Advisory locks will be respected by other invocations of lockafile() for up to some time before being assumed stale and forceably removed. This period may be increased by passing the nsec parameter (which becomes embedded in the lockfile).

lockafile() attempts to manage nested advisory locks via %File::Valet::LOCKS_HASH. lockafile() will keep track of which files the caller has locked, and how many times. Thus if the caller locks the same file two or more times, and unlocks it an equal number of times, the lock file will only be created on the first invocation of lockafile(), and removed only on the last invocation of unlockafile(). See FURTHER DEVELOPMENT for caveats regarding this.

Returns 1 on success, or 0 on any failure, and sets $File::Valet::OK, $File::Valet::ERROR, $File::Valet::ERRNO appropriately.

unlockafile
 my $success = unlockafile("/tmp/foo", %options);

unlockafile() reverses the action of lockafile(), removing an advisory lock on a file (or reducing the count of locks on a multiply-locked file).

unlockafile() will fail if invoked on a file which is not locked, or has been locked by a different process.

unlockafile() returns 1 on success, and 0 on any failure, and sets $File::Valet::OK, $File::Valet::ERROR, $File::Valet::ERRNO appropriately.

unlock_all_the_files

unlock_all_the_files() is a convenience wrapper for walking %File::Valet::LOCKS_HASH and safely removing all lockfiles.

If your code has bugs which cause it to leave lockfiles behind, then calling unlock_all_the_files() before exiting will help prevent that.

Really, though, you should fix your bugs.

Returns three values: A count of errors returned by unlockafile(), a count of locks removed, and a count of lock files removed.

The number of locks can differ from the number of lock files when locks are nested. A file which is locked twice counts as two locks but has only one lock file.

LOCKFILE FORMAT

The lockfiles contain useful bits of information which help lockafile figure out if it should override someone else's lock, and is also useful for gaining insight about the system's behavior, for troubleshooting purposes.

Its fields are tab-delimited, and the file terminates with a newline. They appear in this order:

    * Process identifier of the process which created the lockfile (per "$$"),
    * The number of seconds the lock should be considered valid (per "nsec" parameter),
    * The helpful message provided by the programmer (per "msg" parameter),
    * The name of the program which created the lockfile (per "$0")

Example:

    "4873\t2.0\tThe programmer is lame\t/opt/simon/bin/simond\n"

These fields may change in future versions of this module.

FURTHER DEVELOPMENT

A recursive descent function similar to File::Find is planned, since File::Find is pretty horrible and unusable.

The lockafile() implementation goes through considerable effort to avoid race conditions, but there is still a very short danger window where an overridden lock might get double-clobbered. If a contended lock expires just when two or more other processes call lockafile() on it, it is possible for one process to unlink the lock file, the other process to create a new lock file, and then the first process to overwrite that lock file with its own lock file, leaving both processes under the impression they have acquired the lock. Future implementations may remedy this. In the meantime the possibility can be avoided by setting a sufficiently large "nsec" value when acquiring a lock that it will not expire before the owning process is ready to release it.

The nested lock management lockafile() and unlockafile() implement is flawed, in that the lock on the file is only valid for as long specified by the first invocation of lockafile(). Thus if a file is locked for 3 seconds, and then subsequently locked for 30 seconds, other processes contending for the locked file will forceably acquire the lock after 3 seconds after the first lock, not 30 seconds after the second lock. Future implementations may overwrite the lockfile to reflect the parameters of subsequent (nested) locks.

The file-slurping functions handle data explicitly as bytes and never as codepoints. This is intentional and unlikely to change. If codepoint handling (utf-8, utf-16, etc) is desired, see File::Slurper.

SEE ALSO

File::Slurper is considered the likely successor to File::Slurp for CPAN's primary file-slurping implementation, with proper handling of multibyte-encoded characters (which is broken in File::Slurp). If File::Slurper implemented an appending method, the slurp functions would likely be absent from File::Valet. Until then, File::Valet's slurping functions provide a simple, robust alternative.

Path::Tiny - a different style for file handling, which some people might prefer.

File::Temp - returns name and handle of a temporary file.

File::SearchPath - searches an environment path for a file.

Lock::File - file locker with an automatic out-of-scope unlocking mechanism.

NL::File::Lock - file locker with timeout, but no lock expiration.

File::Lock::Multi - file locker with support for nested locks.

File::TinyLock - a very easy to use file locker.

Data::Munge - a gaggle of useful functions, including a simple slurp().

AUTHOR

TTK Ciar

LICENSE

You can use and distribute this module under the same terms as Perl itself.