NAME

IO::Die - Namespaced, error-checked I/O

VERSION

Version 0.057

SYNOPSIS

use IO::Die;

#Will throw on error:
IO::Die->open( my $fh, '<', '/path/to/file' );
IO::Die->print( $fh, 'Some output...' );
IO::Die->close( $fh );

#----------------------------------------------
#...or, perhaps more usefully:

package MyIO;

use parent 'IO::Die';

sub _CREATE_ERROR {
    my ( $NS, $type, %args ) = @_;

    return MyErrorClass->new( $type, %args );
}

sub _DO_WITH_ERROR {
    my ( $NS, $err ) = @_;  #$err is the result of _CREATE_ERROR() above

    return warn $err;
}

MyIO->open( .. );   #will warn() a MyErrorClass object on error

#----------------------------------------------
# You can also do:

package MyDynamicIO;

use parent 'IO::Die' qw(:preload);  #load everything at once

sub new {
    #...something that sets an internal coderef “_create_err_cr”
}

sub _CREATE_ERROR {
    my ( $self, $type, %args ) = @_;
    return $self->{'_create_err_cr'}->($type, %args);
}

MyDynamicIO->sysopen( .. );     #uses “_create_err_cr” above

DETAILS

This module wraps most of Perl’s built-in I/O functions with code that throws exceptions if the requested operation fails. It confers many of autodie’s benefits but with some distinctions from that module that you may appreciate:

* IO::Die does not overwrite Perl built-ins.

* IO::Die does not clobber globals like $! and $^E.

* IO::Die does not use function prototypes and does not export: all calls into IO::Die (or subclasses) “look like” what they are.

* IO::Die does not try to impose its own error handling; you can customize both how to represent errors and what to do with them.

* IO::Die seems lighter than autodie in simple memory usage checks. YMMV.

For the most part, you can simply replace:

read( ... );

...with:

IO::Die->read( ... );

This module, though, explicitly rejects certain “unsafe” practices that Perl built-ins still support. Neither bareword file handles nor one-/two-arg open(), for example, are supported here--partly because it’s more complicated to implement, but also because those patterns seem best avoided anyway. Damian Conway’s “Perl Best Practices” and the present author’s experience largely inform discernment of the above, which is admittedly subjective by nature.

This module also rejects use of a single Perl command to operate on multiple files, as e.g. Perl’s chmod() allows. This is because that is the only way to have reliable error checking, which is the whole point of this module.

Finally, since this doesn’t use function prototypes, some of the syntaxes that Perl’s built-ins support won’t work here. You’ll likely find yourself needing more parentheses here.

The intent, though, is that no actual functionality of Perl’s built-ins is unimplemented; you may just need to rewrite your calls a bit to have this module perform a given operation. For example:

open( GLOBALS_R_BAD, '>somefile' );
IO::Die->open( my $good_fh, '>', 'somefile');

chown( $uid, $gid, qw( file1 file2 file 3 ) );
IO::Die->chown( $uid, $gid, $_ ) for ( qw( file1 file2 file3 ) );

print { $wfh } 'Haha';
IO::Die->print( $wfh, 'Haha' );

(And, yes, unlike autodie, IO::Die has a print() function!)

Most Perl built-ins that autodie overrides have corresponding functions in this module. Some functions, however, are not implemented here by design:

* readline() and readdir(): Perl’s built-ins do lots of "magic" (e.g., considering '0' as true in a while()) that would be hard to implement.

* system(): This one does a lot “under the hood”, and it’s not feasible to avoid clobbering global $? if you use it.

* tell() doesn’t write to $! and so can’t be error-checked.

* printf() seems not to have much advantage over combining print() and sprintf(). (?)

Some functions are thus far not implemented, including write(), ioctl(), syscall(), semaphore functions, etc. These can be implemented as needed.

PRELOADING

As of version 0.057, this module only loads in a stub at first; each I/O method that you call from this module is then lazy-loaded. This is meant to make this module use the least amount of memory possible.

If you want to load everything all at once (as 0.056 and prior versions did), pass the :preload tag when you use() this module, e.g.:

use IO::Die qw(:preload);

FUNCTIONS THAT DIFFER SIGNIFICANTLY FROM THEIR PERL BUILT-INS

The following are not complete descriptions of each function; rather, this describes the differences between the relevant Perl built-in and IO::Die’s wrapper of it. It is assumed that the reader is familiar with the built-in form.

Note that NONE of the following functions support bareword filehandles.

open()

This supports all built-in forms of 3 or more arguments. It ONLY supports the two-argument form when the second argument (i.e., the MODE) is “|-” or “-|”.

select()

Only the four-argument form is permitted.

chmod()

chown()

kill()

unlink()

utime()

Unlike Perl’s built-ins, these will only operate on one filesystem node at a time. This restriction is necessary for reliable error reporting because Perl’s built-ins have no way of telling us which of multiple filesystem nodes produced the error.

exec()

This always treats the first argument as the program name, so if you do:

IO::Die->exec('/bin/echo haha');

… that will actually attempt to execute a program named echo haha in the directory /bin, which probably isn’t what you wanted and will thus fail. (In the above case, what was likely desired was:

IO::Die->exec('/bin/echo', 'haha');

CONVENIENCE FUNCTIONS

systell( FILEHANDLE )

This function returns the unbuffered file pointer position.

FUNCTIONS THAT LARGELY MATCH THEIR RELEVANT PERL BUILT-INS

The remaining functions intend to match their corresponding Perl built-ins; differences should be regarded as bugs to be fixed!

CUSTOM ERROR HANDLING

IO::Die’s default error format is a rather primitive one that just consists of the error parameters in a string. By default, this error is thrown via Perl’s die() built-in. If you need more robust/flexible error handling, subclass this module, and override the _CREATE_ERROR() and/or _DO_WITH_ERROR methods.

_DO_WITH_ERROR() receives these parameters:

* The namespace

* The error (i.e., the error as returned by _CREATE_ERROR())

_CREATE_ERROR() receives these parameters:

* The namespace.

* A name for the error type, e.g., “FileOpen”.

* A list of key/value pairs that describe the error.

The error types are proprietary to this module and listed below.

PROPRIETARY ERROR TYPES

Each error type always has the following attributes, which are the same values as their corresponding variables as described in “perldoc perlvar”.

* OS_ERROR

* EXTENDED_OS_ERROR

Additional attributes for each type are listed below.

Binmode layer
Chdir OPTIONAL: path
Chmod permissions, path
Chown uid, gid, path
Chroot filename
Close
DirectoryClose
DirectoryCreate path, mask
DirectoryDelete path
DirectoryOpen path
DirectoryRewind
Exec path, arguments
Fcntl function, scalar
FileOpen mode, path; OPTIONAL: mask
Fileno
FileSeek whence, position
FileTruncate
Flock operation
Fork
Kill signal, process
Pipe
Read length
Rename oldpath, newpath
ScalarOpen
Select
SocketAccept
SocketBind name
SocketConnect name
SocketGetOpt level, optname
SocketListen queuesize
SocketOpen domain, type, protocol
SocketPair domain, type, protocol
SocketReceive length, flags
SocketSend length, flags
SocketSetOpt level, optname, optval
SocketShutdown how
Stat path
SymlinkCreate oldpath, newpath
SymlinkRead path
Utime atime, mtime; OPTIONAL: path
Write length

AUTHOR

Felipe Gasper, working for cPanel, Inc.

REPOSITORY

https://github.com/FGasper/io-die

REPORTING BUGS

Open an issue at the GitHub URL above. Patches are welcome!

TODO

  • More tests.

  • Reduce testing dependencies.

  • Right now this kind of works on Windows, but the tests use fork(), so there all kinds of weird failures that, while they can happen in real code, don’t really stem from this module.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc IO::Die

LICENSE AND COPYRIGHT

Copyright 2015 Felipe Gasper.

This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at:

http://www.perlfoundation.org/artistic_license_2_0

Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license.

If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license.

This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder.

This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed.

Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.