views:

42

answers:

3

The following Perl script outputs "SUCCESS" as you'd expect:

use Fcntl qw(:DEFAULT :flock);
sysopen(LF, "test.txt", O_RDONLY | O_CREAT) or die "SYSOPEN FAIL: $!";
if(flock(LF, LOCK_EX)) { print "SUCCESS.\n"; }
else { print "FAIL: $!\n"; }

But now, replace that first line with

require "testlib.pl";

where testlib.pl contains

use Fcntl qw(:DEFAULT :flock);

1;

Now, strangely enough, the script fails, like so:

FAIL: Bad file descriptor

The question: Why?

ADDED:

And now that I know why -- thanks! -- I'm wondering what is the best way to deal with this:

  1. Just do the use Fcntl twice, once in the main script and once in the required library (both the main script and the library need it).
  2. Replace O_RDONLY with &O_RDONLY, etc.
  3. Replace O_RDONLY with O_RDONLY(), etc.
  4. Something else?
+1  A: 

The line use Fcntl qw(:DEFAULT :flock); is not just loading the Fcntl library for you, but also exporting some symbols into your script's namespace. If you move that to a different scope, then the constants O_RDONLY, O_CREAT, LF, and LOCK_EX are no longer available to you, and your code won't do the same thing [however you could still reach them, if you know what namespace they ended up in -- since it was a script that did the export, you could call &main::NAME or simply &NAME, but then you have to be aware of what another file is doing with its code, which is not very clean].

This is described in the documentation under EXPORTED SYMBOLS:

By default your system's F_* and O_* constants (eg, F_DUPFD and O_CREAT) and the FD_CLOEXEC constant are exported into your namespace.

You can request that the flock() constants (LOCK_SH, LOCK_EX, LOCK_NB and LOCK_UN) be provided by using the tag ":flock". See Exporter.

If you add the lines

use strict;
use warnings;

to the top of your script, you will get more informative error messages such as "Name "main::O_RDONLY" used only once: possible type at line ...", which would give you a clue that these constants definitions are no longer visible.

Edit: in response to your question, the best practice would be #1, to include the use statement in every file that needs it. See perldoc -f use -- the Fcntl library is only included once, but the import() call is made every time it is needed, which is what you want.

Ether
Thanks Ether! Mystery solved! In my actual application, I need to "use Fcntl" in both the main script and testlib.pl, which the main script `require`s. Is that fine to do "use Fcntl" twice or is there a better way? Php addresses this with the require_once command. I'm not sure if there's an equivalent in Perl. Maybe this should be a separate question...
dreeves
Just to clarify - the subs (LOCK_EX, etc.) *are* available in the `main::` namespace, it's just that the un-strict parser does not recognize their naked names as parameterless subs, as in @Sean's answer.
pilcrow
@dreeves: The best practice would be to include the use statement in every file that needs it. See `perldoc -f use` -- the Fcntl library is only included once, but the `import()` call is made every time it is needed, which is what you want.
Ether
@Ether: Ah, you just answered the question I appended. Thanks again, and to @Sean and @pilcrow as well!
dreeves
Ether
A: 

use is equivalent to:

BEGIN { require Module; Module->import( LIST ); }

guaranteeing that the import functions are available before the code starts executing. Whe you replace use with require, it simply reads the code in at the lexical point in the program where it exists.

ennuikiller
+3  A: 

By foregoing use, you deprive the Perl parser of the knowledge that O_RDONLY et al. are parameterless subroutines. You have to be a bit more verbose in that situation:

sysopen(LF, "test.txt", O_RDONLY() | O_CREAT()) or die "SYSOPEN FAIL: $!";
if(flock(LF, LOCK_EX())) { print "SUCCESS.\n"; }

EDIT: To elaborate a bit further, without the parentheses, the O_RDONLY and O_CREAT were being interpreted as barewords (strings), which don't behave as you'd expect when binary-or'ed together:

$ perl -le 'print O_RDONLY | O_CREAT'
O_SVOO\Y

(The individual characters are being bitwise or'ed togther.)

In this case, the string "O_SVOO\Y" (or whatever it is on your system) was being interpreted as the number 0 to sysopen, which would therefore still work as long as O_RDONLY is 0 (as is typical) and the file already existed (so the O_CREAT was superfluous). But fcntl is apparently not as forgiving with non-numeric arguments:

$ perl -e 'flock STDOUT, "LOCK_EX" or die "Failed: $!"'
Failed: Bad file descriptor at -e line 1.

Similarly:

$ perl -e 'flock STDOUT, LOCK_EX or die "Failed: $!"'
Failed: Bad file descriptor at -e line 1.

However:

$ perl -e 'use Fcntl qw(:flock); flock STDOUT, LOCK_EX or die "Failed: $!"'
(no output)

Finally, note that use strict provides many helpful clues.

Sean
pilcrow