NAME
Win32::SharedFileOpen - Open a file for shared reading and/or writing
SYNOPSIS
# Open files a la C fopen()/Perl open(), but with mandatory file locking:
use Win32::SharedFileOpen qw(:DEFAULT $ErrStr);
fsopen(FH1, 'readme', 'r', SH_DENYWR) or
die "Can't read 'readme' and take write-lock: $ErrStr\n";
fsopen(FH2, 'writeme', 'w', SH_DENYRW) or
die "Can't write 'writeme' and take read/write-lock: $ErrStr\n";
# Open files a la C open()/Perl sysopen(), but with mandatory file locking:
use Win32::SharedFileOpen qw(:DEFAULT $ErrStr);
sopen(FH1, 'readme', O_RDONLY, SH_DENYWR) or
die "Can't read 'readme' and take write-lock: $ErrStr\n";
sopen(FH2, 'writeme', O_WRONLY | O_CREAT | O_TRUNC, SH_DENYRW, S_IWRITE) or
die "Can't write 'writeme' and take read/write-lock: $ErrStr\n";
# Retry opening the file if it fails due to a sharing violation:
use Win32::SharedFileOpen qw(:DEFAULT :retry $ErrStr);
$Max_Time = 10; # Try opening the file for up to 10 seconds
$Retry_Timeout = 500; # Wait 500 milliseconds between each try
fsopen(FH, 'readme', 'r', SH_DENYNO) or
die "Can't read 'readme' after retrying for $Max_Time secs: $ErrStr\n";
# Use a lexical indirect filehandle that closes itself when destroyed:
use Win32::SharedFileOpen qw(:DEFAULT new_fh $ErrStr);
{
my $fh = new_fh();
fsopen($fh, 'readme', 'r', SH_DENYNO) or
die "Can't read 'readme': $ErrStr\n";
while (<$fh>) {
# ... Do some stuff ...
}
} # ... $fh is automatically closed here
DESCRIPTION
This module provides a Perl interface to the Microsoft C library functions _fsopen()
and _sopen()
. These functions are counterparts to the standard C library functions fopen(3)
and open(2)
respectively (which are already effectively available in Perl as open()
and sysopen()
respectively), but are intended for use when opening a file for subsequent shared reading and/or writing.
The _fsopen()
function, like fopen(3)
, takes a file and a "mode string" (e.g. 'r'
and 'w'
) as arguments and opens the file as a stream, returning a pointer to a FILE
structure, while _sopen()
, like open(2)
, takes an "oflag" (e.g. O_RDONLY
and O_WRONLY
) instead of the "mode string" and returns an int
file descriptor (which the Microsoft documentation confusingly refers to as a C run-time "file handle", not to be confused here with a Perl "filehandle" (or indeed with the operating-system "file handle" returned by the Win32 API function CreateFile()
!)). The _sopen()
and open(2)
functions also take another, optional, "pmode" argument (e.g. S_IREAD
and S_IWRITE
) specifying the permission settings of the file if it has just been created.
The difference between the Microsoft-specific functions and their standard counterparts is that the Microsoft-specific functions also take an extra "shflag" argument (e.g. SH_DENYRD
and SH_DENYWR
) that specifies how to prepare the file for subsequent shared reading and/or writing. This flag can be used to specify that either, both or neither of read access and write access will be denied to other processes sharing the file.
This share access control is thus effectively a form a file-locking, which, unlike flock(3)
and lockf(3)
and their corresponding Perl function flock()
, is mandatory rather than just advisory. This means that if, for example, you "deny read access" for the file that you have opened then no other process will be able to read that file while you still have it open, whether or not they are playing the same ball game as you. They cannot gain read access to it simply by not honouring the same file opening/locking scheme as you.
This module provides straightforward Perl "wrapper" functions, fsopen()
and sopen()
, for both of these Microsoft C library functions (with the leading "_" character removed from their names). These Perl functions maintain the same formal parameters as the original C functions, except for the addition of an initial filehandle parameter like the Perl built-in open()
and sysopen()
functions have. This is used to make the Perl filehandle opened available to the caller (rather than using the functions' return values, which are now simple Booleans to indicate success or failure).
The value passed to the functions in this first parameter can be a straightforward filehandle (FH
) or any of the following:
a typeglob (either a named typeglob like
*FH
, or an anonymous typeglob (e.g. fromgensym()
ornew_fh()
in this module) in a scalar variable);a reference to a typeglob (either a hard reference like
\*FH
, or a name like'FH'
to be used as a symbolic reference to a typeglob in the caller's package);a suitable IO object (e.g. an instance of IO::Handle, IO::File or FileHandle).
These functions, however, do not have the ability of open()
and sysopen()
to auto-vivify the undefined scalar value into something that can be used as a filehandle, so calls like "fsopen(my $fh, ...)
" will croak()
with a message to this effect.
The "oflags" and "shflags", as well as the "pmode" flags used by _sopen()
, are all made available to Perl by this module, and are all exported by default. Clearly this module will only build on "native" (i.e. non-Cygwin) Microsoft Windows platforms, so only the flags found on such systems are exported, rather than re-exporting all of the O_*
and S_I*
flags from the Fcntl module like, for example, IO::File does. In any case, Fcntl does not know about the Microsoft-specific _O_SHORT_LIVED
and SH_*
flags. (The _O_SHORT_LIVED
flag is exported (like the _fsopen()
and _sopen()
functions themselves) without the leading "_" character.)
Both functions can be made to automatically retry opening a file (indefinitely, or up to a specified maximum time or number of times, and at a specified frequency) if the file could not be opened due to a sharing violation, via the "Variables" $Max_Time, $Max_Tries and $Retry_Timeout and the INFINITE
flag. (This functionality is not available for perls built with the Borland C++ compiler, due to limitations in that compiler's C run-time library. A warning will be issued when setting these variables using such a perl, and no retries will be attempted. See "LIMITATIONS" for more details.)
Functions
fsopen($fh, $file, $mode, $shflag)
-
Opens the file $file using the filehandle (or indirect filehandle) $fh in the access mode specified by $mode and prepares the file for subsequent shared reading and/or writing as specified by $shflag.
Returns a non-zero value if the file was successfully opened, or returns
undef
and sets $ErrStr if the file could not be opened. sopen($fh, $file, $oflag, $shflag[, $pmode])
-
Opens the file $file using the filehandle (or indirect filehandle) $fh in the access mode specified by $oflag and prepares the file for subsequent shared reading and/or writing as specified by $shflag. The optional $pmode argument specifies the file's permission settings if the file has just been created; it is required if (and only if) the access mode includes
O_CREAT
.Returns a non-zero value if the file was successfully opened, or returns
undef
and sets $ErrStr if the file could not be opened. gensym()
-
Returns a new, anonymous, typeglob that can be used as an indirect filehandle in the first parameter of
fsopen()
andsopen()
.This function is not actually implemented by this module itself: it is simply imported from the Symbol module and then re-exported. See "Indirect Filehandles" for more details.
new_fh()
-
Returns a new, anonymous, typeglob that can be used as an indirect filehandle in the first parameter of
fsopen()
andsopen()
.This function is an implementation of the "First-Class Filehandle Trick". See "The First-Class Filehandle Trick" for more details.
Mode Strings
The $mode argument in fsopen()
specifies the type of access requested for the file, as follows:
'r'
-
Opens the file for reading only. Fails if the file does not already exist.
'w'
-
Opens the file for writing only. Creates the file if it does not already exist; destroys the contents of the file if it does already exist.
'a'
-
Opens the file for appending only. Creates the file if it does not already exist.
'r+'
-
Opens the file for both reading and writing. Fails if the file does not already exist.
'w+'
-
Opens the file for both reading and writing. Creates the file if it does not already exist; destroys the contents of the file if it does already exist.
'a+'
-
Opens the file for both reading and appending. Creates the file if it does not already exist.
When the file is opened for appending the file pointer is always forced to the end of the file before any write operation is performed.
The following table shows the equivalent combination of "O_* Flags" for each mode string:
+------+-------------------------------+
| 'r' | O_RDONLY |
+------+-------------------------------+
| 'w' | O_WRONLY | O_CREAT | O_TRUNC |
+------+-------------------------------+
| 'a' | O_WRONLY | O_CREAT | O_APPEND |
+------+-------------------------------+
| 'r+' | O_RDWR |
+------+-------------------------------+
| 'w+' | O_RDWR | O_CREAT | O_TRUNC |
+------+-------------------------------+
| 'a+' | O_RDWR | O_CREAT | O_APPEND |
+------+-------------------------------+
See also "Text and Binary Modes".
O_* Flags
The $oflag argument in sopen()
specifies the type of access requested for the file, as follows:
O_RDONLY
-
Opens the file for reading only.
O_WRONLY
-
Opens the file for writing only.
O_RDWR
-
Opens the file for both reading and writing.
Exactly one of the above flags must be used to specify the file access mode: there is no default value. In addition, the following flags may also be used in bitwise-OR combination:
O_APPEND
-
The file pointer is always forced to the end of the file before any write operation is performed.
O_CREAT
-
Creates the file if it does not already exist. (Has no effect if the file does already exist.) The $pmode argument is required if (and only if) this flag is used.
O_EXCL
-
Fails if the file already exists. Only applies when used with
O_CREAT
. O_NOINHERIT
-
Prevents creation of a shared file descriptor.
O_RANDOM
-
Specifies the disk access will be primarily random. (Not supported for perls built with the Borland C++ compiler.)
O_SEQUENTIAL
-
Specifies the disk access will be primarily sequential. (Not supported for perls built with the Borland C++ compiler.)
O_SHORT_LIVED
-
Used with
O_CREAT
, creates the file such that, if possible, it does not flush the file to disk. (Not supported for perls built with the Borland C++ compiler.) O_TEMPORARY
-
Used with
O_CREAT
, creates the file as temporary. The file will be deleted when the last file descriptor attached to it is closed. (Not supported for perls built with the Borland C++ compiler.) O_TRUNC
-
Truncates the file to zero length, destroying the contents of the file, if it already exists. Cannot be specified with
O_RDONLY
, and the file must have write permission.
See also "Text and Binary Modes".
Text and Binary Modes
Both fsopen()
and sopen()
calls can specify whether the file should be opened in text (translated) mode or binary (untranslated) mode.
If the file is opened in text mode then on input carriage return-linefeed (CR-LF) pairs are translated to single linefeed (LF) characters and Ctrl+Z is interpreted as end-of-file (EOF), while on output linefeed (LF) characters are translated to carriage return-linefeed (CR-LF) pairs.
These translations are not performed if the file is opened in binary mode.
If neither mode is specified then text mode is assumed by default, as is usual for Perl filehandles. Binary mode can still be enabled after the file has been opened (but only before any I/O has been performed on it) by calling binmode()
in the usual way.
't'
'b'
-
Text/binary modes are specified for
fsopen()
by inserting a't'
or a'b'
respectively into the $mode string, immediately following the'r'
,'w'
or'a'
, for example:my $fh = fsopen($file, 'wt', SH_DENYNO);
O_TEXT
O_BINARY
O_RAW
-
Text/binary modes are specified for
sopen()
by usingO_TEXT
orO_BINARY
(orO_RAW
, which is an alias forO_BINARY
) respectively in bitwise-OR combination with otherO_*
flags in the $oflag argument, for example:my $fh = sopen($file, O_WRONLY | O_CREAT | O_TRUNC | O_TEXT, SH_DENYNO, S_IWRITE);
SH_* Flags
The $shflag argument in both fsopen()
and sopen()
specifies the type of sharing access permitted for the file, as follows:
SH_DENYNO
-
Permits both read and write access to the file.
SH_DENYRD
-
Denies read access to the file; write access is permitted.
SH_DENYWR
-
Denies write access to the file; read access is permitted.
SH_DENYRW
-
Denies both read and write access to the file.
S_I* Flags
The $pmode argument in sopen()
is required if (and only if) the access mode, $oflag, includes O_CREAT
. If the file does not already exist then $pmode specifies the file's permission settings, which are set the first time the file is closed. (It has no effect if the file already exists.) The value is specified as follows:
S_IREAD
-
Permits reading.
S_IWRITE
-
Permits writing.
Note that it is evidently not possible to deny read permission, so S_IWRITE
and S_IREAD | S_IWRITE
are equivalent.
Other Flags
INFINITE
-
This flag can be assigned to $Max_Time and/or $Max_Tries (see below) in order to have
fsopen()
orsopen()
indefinitely retry opening a file until it is either opened successfully or it cannot be opened for some reason other than a sharing violation.
Variables
- $ErrStr
-
Last error message.
If either function fails then a description of the last error will be set in this variable for use in reporting the cause of the failure, much like the use of the Perl Special Variables
$!
and$^E
after failed system calls and Win32 API calls. Note that$!
and/or$^E
may also be set on failure, but this is not always the case so it is better to check $ErrStr instead. Any relevant messages from$!
or$^E
will form part of the message in $ErrStr anyway. See "Error Values" for a listing of the possible values of $ErrStr.If a function succeeds then this variable will be set to the null string.
- $Trace
-
Trace mode setting.
Boolean value.
Setting this variable to a true value will cause trace information to be emitted (via
warn()
, so that it can be captured with a $SIG{__WARN__} handler if required) showing a summary of whatfsopen()
orsopen()
did just before it returns.The default value is 0, i.e. trace mode is "off".
- $Max_Time
- $Max_Tries
-
These variables specify respectively the maximum time for which to try, and the maximum number of times to try, opening a file on a single call to
fsopen()
orsopen()
while the file cannot be opened due to a sharing violation (specifically, while "$^E == ERROR_SHARING_VIOLATION
").The $Max_Time variable is generally more useful than $Max_Tries because even with a common value of $Retry_Timeout (see below) two processes may retry opening a shared file at significantly different rates. For example, if $Retry_Timeout is 0 then a process that can access the file in question on a local disk may retry thousands of times per second, while a process on another machine trying to open the same file across a network connection may only retry once or twice per second. Clearly, specifying the maximum time that a process is prepared to wait is preferable to specifying the maximum number of times to try.
For this reason, if both variables are specified then only $Max_Time is used; $Max_Tries is ignored in that case. Use the undefined value to explicitly have one or the other variable ignored. No retries are attempted if both variables are undefined.
Otherwise, the values must be natural numbers (i.e. non-negative integers); an exception is raised on any attempt to specify an invalid value.
The
INFINITE
flag (see above) indicates that the retries should be continued ad infinitum if necessary. The value zero has the same meaning for backwards compatibility with previous versions of this module.The default values are both
undef
, i.e. no retries are attempted.Note: These variables are not supported for perls built with the Borland C++ compiler, due to limitations in that compiler's C run-time library. (See "LIMITATIONS" for more details.) A warning will be issued when setting these variables using such a perl, and no retries will be attempted.
- $Retry_Timeout
-
Specifies the time to wait (in milliseconds) between tries at opening a file (see $Max_Time and $Max_Tries above).
The value must be a natural number (i.e. a non-negative integer); an exception is raised on any attempt to specify an invalid value.
The default value is 250, i.e. wait for one quarter of a second between tries.
Note: As with $Max_Time and $Max_Tries above, this variable is not supported for perls built with the Borland C++ compiler. (See "LIMITATIONS" for more details.) A warning will be issued when setting it using such a perl, and no retries will be attempted.
DIAGNOSTICS
Warnings and Error Messages
This module may produce the following diagnostic messages. They are classified as follows (a la perldiag):
(W) A warning (optional).
(F) A fatal error (trappable).
(I) An internal error that you should never see (trappable).
- %s() can't use the undefined value as an indirect filehandle
-
(F) The specified function was passed the undefined value as the first argument. That is not a filehandle, cannot be used as an indirect filehandle, and (unlike the Perl built-in
open()
andsysopen()
functions) the function is unable to auto-vivify something that can be used as an indirect filehandle in such a case. - '%s' is not supported with Borland builds
-
(W) You attempted to set the specified variable, but it is not supported for perls built with the Borland C++ compiler. See "LIMITATIONS" for more details.
- Invalid value for '%s': '%s' is not a natural number
-
(F) You attempted to set the specified variable to something other than a natural number (i.e. a non-negative integer). This is not allowed.
-
(F) You attempted to lookup the value of the specified constant in the Win32::SharedFileOpen module, but that constant is unknown to this module.
- Unexpected error in AUTOLOAD(): constant() is not defined
-
(I) There was an unexpected error looking up the value of the specified constant: the constant-lookup function itself is apparently not defined.
-
(I) There was an unexpected error looking up the value of the specified constant: the C component of the constant-lookup function returned an unknown type.
- Unknown IoTYPE '%s'
-
(I) The PerlIO stream associated with the C file stream opened by one of the Microsoft C library functions
_fsopen()
or_sopen()
is of an unknown type. - Unknown mode '%s'
-
(I) The PerlIO stream associated with the C file stream opened by one of the Microsoft C library functions
_fsopen()
or_sopen()
is in an unknown mode. - Usage: tie SCALAR, '%s', SCALARVALUE, SCALARNAME
-
(I) The class used internally to
tie()
the $Max_Time, $Max_Tries and $Retry_Timeout variables to has been used incorrectly. -
(I) You attempted to lookup the value of the specified constant in the Win32::SharedFileOpen module, but that constant is apparently not defined.
Error Values
Both functions set $ErrStr to a value indicating the cause of the error when they fail. The possible values are as follows:
- Can't get PerlIO file stream from C file %s for file '%s': %s
-
A PerlIO file stream could not be obtained from the C file descriptor or stream for the specified file. The system error message corresponding to the standard C library
errno
variable may also be given. - Can't open C file %s for file '%s': %s
-
A C file descriptor or stream could not be opened for the specified file. The system error message corresponding to the standard C library
errno
variable is also given. - Can't set binary mode on C file descriptor for file '%s': %s
-
The C file descriptor for the specified file could not be set in "binary" mode. The system error message corresponding to the standard C library
errno
variable is also given.
In some cases, the functions may also leave the Perl Special Variables $!
and/or $^E
set to values indicating the cause of the error when they fail; $!
will be incorporated into the $ErrStr message in such cases as indicated above. The possible values of each are as follows ($!
shown first, $^E
underneath):
- EACCES (Permission denied) [1]
- ERROR_ACCESS_DENIED (Access is denied)
-
The $file is a directory, or is a read-only file and an attempt was made to open it for writing.
- EACCES (Permission denied) [2]
- ERROR_SHARING_VIOLATION (The process cannot access the file because it is being used by another process.)
-
The $file cannot be opened because another process already has it open and is denying the requested access mode.
This is, of course, the error that other processes will get when trying to open a file in a certain access mode when we have already opened the same file with a sharing mode that denies other processes that access mode.
- EEXIST (File exists)
- ERROR_FILE_EXISTS (The file exists)
-
[
sopen()
only.] The $oflag includedO_CREAT | O_EXCL
, and the $file already exists. - EINVAL (Invalid argument)
- ERROR_ENVVAR_NOT_FOUND (The system could not find the environment option that was entered)
-
The $oflag or $shflag argument was invalid.
- EMFILE (Too many open files)
- ERROR_TOO_MANY_OPEN_FILES (The system cannot open the file)
-
The maximum number of file descriptors has been reached.
- ENOENT (No such file or directory)
- ERROR_FILE_NOT_FOUND (The system cannot find the file specified)
-
The filename or path in $file was not found.
Other values may also be produced by various functions that are used within this module whose possible error codes are not documented.
See $!
, %!
, $^E
and Error Indicators in perlvar, Win32::GetLastError()
and Win32::FormatMessage()
in Win32, and Errno and Win32::WinError for details on how to check these values.
EXAMPLES
- Open a file for reading, denying write access to other processes:
-
fsopen(FH, $file, 'r', SH_DENYWR) or die "Can't read '$file' and take write-lock: $ErrStr\n";
This example could be used for sharing a file amongst several processes for reading, but protecting the reads from interference by other processes trying to write the file.
- Open a file for reading, denying write access to other processes, with automatic open-retrying:
-
$Win32::SharedFileOpen::Max_Time = 10; fsopen(FH, $file, 'r', SH_DENYWR) or die "Can't read '$file' and take write-lock: $ErrStr\n";
This example could be used in the same scenario as above, but when we actually expect there to be other processes trying to write to the file, e.g. we are reading a file that is being regularly updated. In this situation we expect to get sharing violation errors from time to time, so we use $Max_Time to automatically have another go at reading the file (for up to 10 seconds at the most) when that happens.
We may also want to increase $Win32::SharedFileOpen::Retry_Timeout from its default value of 250 milliseconds if the file is fairly large and we expect the writer updating the file to take very long to do so.
- Open a file for "update", denying read and write access to other processes:
-
fsopen(FH, $file, 'r+', SH_DENYRW) or die "Can't update '$file' and take read/write-lock: $ErrStr\n";
This example could be used by a process to both read and write a file (e.g. a simple database) and guard against other processes interfering with the reads or being interfered with by the writes.
- Open a file for writing if and only if it does not already exist, denying write access to other processes:
-
sopen(FH, $file, O_WRONLY | O_CREAT | O_EXCL, SH_DENYWR, S_IWRITE) or die "Can't create '$file' and take write-lock: $ErrStr\n";
This example could be used by a process wishing to take an "advisory lock" on some non-file resource that cannot be explicitly locked itself by dropping a "sentinel" file somewhere. The test for the non-existence of the file and the creation of the file is atomic to avoid a "race condition". The file can be written by the process taking the lock and can be read by other processes to facilitate a "lock discovery" mechanism.
- Open a temporary file for "update", denying read and write access to other processes:
-
sopen(FH, $file, O_RDWR | O_CREAT | O_TRUNC | O_TEMPORARY, SH_DENYRW, S_IWRITE) or die "Can't update '$file' and take write-lock: $ErrStr\n";
This example could be used by a process wishing to use a file as a temporary "scratch space" for both reading and writing. The space is protected from the prying eyes of, and interference by, other processes, and is deleted when the process that opened it exits, even when dying abnormally.
BACKGROUND REFERENCE
This section gives some useful background reference on filehandles and indirect filehandles.
Filehandles
The fsopen()
and sopen()
functions both expect either a filehandle or an indirect filehandle as their first argument.
Using a filehandle:
fsopen(FH, $file, 'r', SH_DENYWR) or
die "Can't read '$file': $ErrStr\n";
is the simplest approach, but filehandles have a big drawback: they are global in scope so they are always in danger of clobbering a filehandle of the same name being used elsewhere. For example, consider this:
fsopen(FH, $file1, 'r', SH_DENYWR) or
die "Can't read '$file1': $ErrStr\n";
while (<FH>) {
chomp($line = $_);
my_sub($line);
}
...
close FH;
sub my_sub($) {
fsopen(FH, $file2, 'r', SH_DENYWR) or
die "Can't read '$file2': $ErrStr\n";
...
close FH;
}
The problem here is that when you open a filehandle that is already open it is closed first, so calling "fsopen(FH, ...)
" in my_sub()
causes the filehandle FH
that is already open in the caller to be closed first so that my_sub()
can use it. When my_sub()
returns the caller will now find that FH
is closed, causing the next read in the while { ... }
loop to fail. (Or even worse, the caller would end up mistakenly reading from the wrong file if my_sub()
had not closed FH
before returning!)
Localized Typeglobs and the *foo{THING}
Notation
One solution to this problem is to localize the typeglob of the filehandle in question within my_sub()
:
sub my_sub($) {
local *FH;
fsopen(FH, $file2, 'r', SH_DENYWR) or
die "Can't read '$file2': $ErrStr\n";
...
close FH;
}
but this has the unfortunate side-effect of localizing all the other members of that typeglob as well, so if the caller had global variables $FH, @FH or %FH, or even a subroutine FH()
, that my_sub()
needed then it no longer has access to them either. (It does, on the other hand, have the rather nicer side-effect that the filehandle is automatically closed when the localized typeglob goes out of scope, so the "close FH;
" above is no longer necessary.)
This problem can also be addressed by using the so-called *foo{THING}
syntax. *foo{THING}
returns a reference to the THING member of the *foo
typeglob. For example, *foo{SCALAR}
is equivalent to \$foo
, and *foo{CODE}
is equivalent to \&foo
. *foo{IO}
(or the older, now out-of-fashion notation *foo{FILEHANDLE}
) yields the actual internal IO::Handle object that the *foo
typeglob contains, so with this we can localize just the IO object, not the whole typeglob, so that we do not accidentally hide more than we meant to:
sub my_sub($) {
local *FH{IO};
fsopen(FH, $file2, 'r', SH_DENYWR) or
die "Can't read '$file2': $ErrStr\n";
...
close FH; # As in the example above, this is also not necessary
}
However, this has a drawback as well: *FH{IO}
only works if FH
has already been used as a filehandle (or some other IO handle), because *foo{THING}
returns undef
if that particular THING has not been seen by the compiler yet (with the exception of when THING is SCALAR
, which is treated differently). This is fine in the example above, but would not necessarily have been if the caller of my_sub()
had not used the filehandle FH
first, so this approach would be no good if my_sub()
was to be put in a module to be used by other callers too.
Indirect Filehandles
The answer to all of these problems is to use so-called indirect filehandles instead of "normal" filehandles. An indirect filehandle is anything other than a symbol being used in a place where a filehandle is expected, i.e. an expression that evaluates to something that can be used as a filehandle, namely:
- A string
-
A name like
'FH'
to be used as a symbolic reference to the typeglob whose IO member is to be used as the filehandle. - A typeglob
-
Either a named typeglob like
*FH
, or an anonymous typeglob in a scalar variable (e.g. fromSymbol::gensym()
), whose IO member is to be used as the filehandle. - A reference to a typeglob
-
Either a hard reference like
\*FH
, or a symbolic reference as in the first case above, to a typeglob whose IO member is to be used as the filehandle. - A suitable IO object
-
Either the IO member of a typeglob obtained via the
*foo{IO}
syntax, or an instance of IO::Handle, or of IO::File or FileHandle (which are both just sub-classes of IO::Handle).
Of course, typeglobs are global in scope just like filehandles are, so if a named typeglob, a reference (hard or symbolic) to a named typeglob or the IO member of a named typeglob is used then we run into the same scoping problems that we saw above with filehandles. The remainder of the above, however, (namely, an anonymous typeglob in a scalar variable, or a suitable IO object) finally give us the answer that we have been looking for.
Therefore, we can now write my_sub()
like this:
sub my_sub($) {
# Create "my $fh" here: see below
fsopen($fh, $file2, 'r', SH_DENYWR) or
die "Can't read '$file2': $ErrStr\n";
...
close $fh; # Not necessary again
}
where any of the following may be used to create "my $fh
":
use Symbol;
my $fh = gensym();
use IO::Handle;
my $fh = IO::Handle->new();
use IO::File;
my $fh = IO::File->new();
use FileHandle;
my $fh = FileHandle->new();
As we have noted in the code segment above, the "close $fh;
" is once again not necessary: the filehandle is closed automatically when the lexical variable $fh is destroyed, i.e. when it goes out of scope (assuming there are no other references to it).
However, there is still another point to bear in mind regarding the four solutions shown above: they all load a good number of extra lines of code into your script that might not otherwise be made use of. Note that FileHandle is a sub-class of IO::File, which is in turn a sub-class of IO::Handle, so using either of those sub-classes is particularly wasteful in this respect unless the methods provided by them are going to be put to use. Even the IO::Handle class still loads a number of other modules, including Symbol, so using the Symbol module is certainly the best bet here if none of the IO::Handle methods are required.
In our case, there is no additional overhead at all in loading the Symbol module for this purpose because it is already loaded by this module itself anyway. In fact, Symbol::gensym()
, imported by this module, is made available for export from this module, so one can write:
use Win32::SharedFileOpen qw(:DEFAULT gensym);
my $fh = gensym();
to create "my $fh
" above.
The First-Class Filehandle Trick
Finally, there is another way to get an anonymous typeglob in a scalar variable that is even leaner and meaner than using Symbol::gensym()
: the "First-Class Filehandle Trick". It is described in an article by Mark-Jason Dominus called "Seven Useful Uses of Local", which first appeared in The Perl Journal and can also be found (at the time of writing) on his website at the URL http://perl.plover.com/local.html. It consists simply of the following:
my $fh = do { local *FH };
It works like this: the do { ... }
block simply executes the commands within the block and returns the value of the last one. Therefore, in this case, the global *FH
typeglob is temporarily replaced with a new glob that is local()
to the do { ... }
block. The new, local()
, *FH
typeglob then goes out of scope (i.e. is no longer accessible by that name) but is not destroyed because it gets returned from the do { ... }
block. It is this, now anonymous, typeglob that gets assigned to "my $fh
", exactly as we wanted.
Note that it is important that the typeglob itself, not a reference to it, is returned from the do { ... }
block. This is because references to localized typeglobs cannot be returned from their local scopes, one of the few places in which typeglobs and references to typeglobs cannot be used interchangeably. If we were to try to return a reference to the typeglob, as in:
my $fh = do { \local *FH };
then $fh would actually be a reference to the original *FH
itself, not the temporary, localized, copy of it that existed within the do { ... }
block. This means that if we were to use that technique twice to obtain two typeglob references to use as two indirect filehandles then we would end up with them both being references to the same typeglob (namely, *FH
) so that the two filehandles would then clash.
If this trick is used only once within a script running under "use warnings;
" that does not mention *FH
or any of its members anywhere else then a warning like the following will be produced:
Name "main::FH" used only once: possible typo at ...
This can be easily avoided by turning off that warning within the do { ... }
block:
my $fh = do { no warnings 'once'; local *FH };
For convenience, this solution is implemented by this module itself in the function new_fh()
, so that one can now simply write:
use Win32::SharedFileOpen qw(:DEFAULT new_fh);
my $fh = new_fh();
The only downside to this solution is that any subsequent error messages involving this filehandle will refer to Win32::SharedFileOpen::FH
, the IO member of the typeglob that a temporary, localized, copy of was used. For example, the script:
use strict;
use warnings;
use Win32::SharedFileOpen qw(:DEFAULT new_fh);
my $fh = new_fh();
print $fh "Hello, world.\n";
outputs the warning:
print() on unopened filehandle Win32::SharedFileOpen::FH at test.pl line 5.
If several filehandles are being used in this way then it can be confusing to have them all referred to by the same name. (Do not be alarmed by this, though: they are completely different anonymous filehandles, which just happen to be referred to by their original, but actually now out-of-scope, names.) If this is a problem then consider using Symbol::gensym()
instead (see above): that function generates each anonymous typeglob from a different name (namely, 'GEN0', 'GEN1', 'GEN2', ...).
Auto-Vivified Filehandles
Note that all of the above discussion of filehandles and indirect filehandles applies equally to Perl's built-in open()
and sysopen()
functions. It should also be noted that those two functions both support the use of one other value in the first argument that this module's fsopen()
and sopen()
functions do not: the undefined value. The calls
open my $fh, $file;
sysopen my $fh, $file, O_RDONLY;
each auto-vivify "my $fh
" into something that can be used as an indirect filehandle. (In fact, they currently [as of Perl 5.8.0] auto-vivify an entire typeglob, but this may change in a future version of Perl to only auto-vivify the IO member.) Any attempt to do likewise with this module's functions:
fsopen(my $fh, $file, 'r', SH_DENYNO);
sopen(my $fh, $file, O_RDONLY, SH_DENYNO);
causes the functions to croak()
.
EXPORTS
The following symbols are, or can be, exported by this module:
- Default Exports
-
fsopen
,sopen
;O_APPEND
,O_BINARY
,O_CREAT
,O_EXCL
,O_NOINHERIT
,O_RANDOM
,O_RAW
,O_RDONLY
,O_RDWR
,O_SEQUENTIAL
,O_SHORT_LIVED
,O_TEMPORARY
,O_TEXT
,O_TRUNC
,O_WRONLY
;S_IREAD
,S_IWRITE
;SH_DENYNO
,SH_DENYRD
,SH_DENYRW
,SH_DENYWR
. - Optional Exports
-
gensym
,new_fh
;INFINITE
;$ErrStr
,$Max_Time
,$Max_Tries
,$Retry_Timeout
. - Export Tags
COMPATIBILITY
Before version 2.00 of this module, fsopen()
and sopen()
both created a filehandle and returned it to the caller. (undef
was returned instead on failure.)
As of version 2.00 of this module, the arguments and return values of these two functions now more closely resemble those of the Perl built-in open()
and sysopen()
functions. Specifically, they now both expect a filehandle or an indirect filehandle as their first argument and they both return a Boolean value to indicate success or failure.
THIS IS AN INCOMPATIBLE CHANGE. EXISTING SOFTWARE THAT USES THESE FUNCTIONS WILL NEED TO BE MODIFIED.
KNOWN BUGS
The following test failures are expected with perls built in the default configuration with the Borland C++ compiler:
t\06_fsopen_access.t tests 25 or 26, 30 or 31, 35 or 36
(Exactly which tests fail varies according to which version of Perl is being used.)
These failures are the result of outstanding problems with large file support in perls built with the Borland C++ compiler, and can be rectified by rebuilding perl with large file support disabled (by commenting out the line:
USE_LARGE_FILES *= define
in win32/makefile.mk) or by using a perl built with a different compiler.
LIMITATIONS
As noted in several places above, the functionality to automatically retry opening a file if it could not be opened due to a sharing violation is not available for perls built with the Borland C++ compiler, due to limitations in that compiler's C run-time library.
Specifically, the problem lies with the particular C run-time library, cw32mti.lib, used when building perl. A simple test program written in C shows that (as of Borland C++ 5.5.1) it does not set the Win32 last error code (as returned by the Win32 API function
GetLastError()
) after a failed CRT function call. There is actually no guarantee that it should be set because it is only really intended to be used after a failed Win32 API function call, but it does generally seem to be set by the Microsoft C run-time libraries and this module uses that fact to detect when a sharing violation has occurred, namely, when the last error code is ERROR_SHARING_VIOLATION.At least one other Borland C run-time library, cw32i.lib, does set the Win32 last error code after a failed CRT function call, so this may be a bug in the Borland C run-time library used by perl. If so, and if it is fixed in some later version of Borland C++, then this functionality will, of course, be enabled for those versions.
See the exchanges between myself and Jan Dubois on the "perl5-porters" mailing list, 30 Jan-01 Feb 2006, for more details on all of this.
FEEDBACK
Patches, bug reports, suggestions or any other feedback is welcome.
Bugs can be reported on the CPAN Request Tracker at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Win32-SharedFileOpen.
Open bugs on the CPAN Request Tracker can be viewed at http://rt.cpan.org/NoAuth/Bugs.html?Dist=Win32-SharedFileOpen.
Please test this distribution. See CPAN Testers at http://testers.cpan.org/ for details of how to get involved.
Previous test results on CPAN Testers can be viewed at http://testers.cpan.org/search?request=dist&dist=Win32-SharedFileOpen.
Please rate this distribution on CPAN Ratings at http://cpanratings.perl.org/rate/?distribution=Win32-SharedFileOpen.
SEE ALSO
"open" in perlfunc, "sysopen" in perlfunc, perlopentut;
Fcntl, FileHandle, IO::File, IO::Handle, Symbol, Win32API::File.
In particular, the Win32API::File module (part of the "libwin32" distribution) contains an interface to a (lower level) Win32 API function, CreateFile()
, which provides similar (and more) capabilities but using a completely different set of arguments that are unfamiliar to unseasoned Microsoft developers. A more Perl-friendly wrapper function, createFile()
, is also provided but does not entirely alleviate the pain.
ACKNOWLEDGEMENTS
Some of the XS code used in the re-write for version 3.00 is based on that in the standard library module VMS::Stdio (version 2.3), written by Charles Bailey.
Thanks to Nick Ing-Simmons for help in getting this XS to build under different flavours of Perl (all "stable" Perls from 5.6.0 onwards, both with and without PERL_IMPLICIT_CONTEXT and/or PERL_IMPLICIT_SYS enabled, are now supported), and for help in fixing a text mode/binary mode bug in the fsopen() function.
AVAILABILITY
The latest version of this module is available from CPAN (see "CPAN" in perlmodlib for details) at
http://www.cpan.org/authors/id/S/SH/SHAY/ or
http://www.cpan.org/modules/by-module/Win32/.
INSTALLATION
See the INSTALL file.
AUTHOR
Steve Hay <shay@cpan.org>
COPYRIGHT
Copyright (C) 2001-2008 Steve Hay. All rights reserved.
LICENCE
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself, i.e. under the terms of either the GNU General Public License or the Artistic License, as specified in the LICENCE file.
VERSION
Version 3.42
DATE
28 Feb 2012
HISTORY
See the Changes file.