Xiong has asked for the wisdom of the Perl Monks concerning the following question:

I want to accept a filehandle given in the use line of my module:

use My::Module $fh;

This seems straightforward enough:

package My::Module; our $fh; sub import { shift; # throw away my own package name $fh = shift; if ( not -w $fh ) # check to see it's a good filehandle { die 'Bad filehandle in use My::Module' }; } sub later { print $fh 'Arbitrary text.'; } 1;

Should I allow for the possibility that caller may decide to supply the filehandle using another syntax? If so, what should I allow?

use My::Module STDOUT;
use My::Module *STDOUT;
use My::Module 'STDOUT';
--?

Updates:

2010-05-30

The code I've actually written works fine for:

use My::Module *STDOUT;

It works okay, too, for:

use My::Module $fh;

... but with that, there's a fundamental complication which I did not consider while hurrying off in the other direction: The use line executes as a BEGIN block so, even though caller may appear to open $fh before, he doesn't, unless he does so in a previous BEGIN block:

BEGIN { open $::fh, '>>', '/var/foo.log'; } use My::Module $::fh;

... and of course there are other approaches caller might employ. None of this, strictly speaking, is my worry (as author of My::Module); I just have to deal with the passed-in argument.

This is probably lunacy:

use My::Module STDOUT;

... or at least I can hope that nobody will be go down that road.

Is it worthwhile to support:

use My::Module 'STDOUT';

or perhaps:

use My::Module '/var/foo.log';

... or is that just too much gingerbread? Do you have a preferred syntax you'd like to see implemented?

2010-05-30 (never trust the .0 release)

rm ',' per bart.

Marginally more legitimate example code per almut.

2010-05-31

Agree with tye that it's ugly to accept:

use My::Module "STDOUT";

... I never even considered it. I don't really want to accept either of:

use My::Module STDOUT;
use My::Module 'STDOUT';

I think I'll continue to support, because so commonly used:

use My::Module *STDOUT;

...but I'll add support for:

use My::Module \*STDOUT;

I see that almut has a good word for:

use My::Module '/var/foo.log';

This may be just a bent feeler gauge off from the paygrade of the basic module under consideration but the small project as a whole contemplates opening filenames passed in as literal strings.

ikegami speaks wisely that opening $fh in a BEGIN block before the use line is icky. There isn't really an alternative, this being a source filter -- not without way too much hackery on existing code. Again, a helper/wrapper module is intended for open and pass in one line. I'm going to escalate the priority of this.

- the lyf so short, the craft so long to lerne -

Replies are listed 'Best First'.
Re: Passing a Filehandle that Might be a Bareword
by BrowserUk (Patriarch) on May 29, 2010 at 23:49 UTC

    You could try using Win32::Fmode or FileHandle::Fmode to determine if the handle passed is open for the appropriate type of operations.

    Note: Both will fail to deal with ramfiles or IO::String handles.

Re: Passing a Filehandle that Might be a Bareword
by almut (Canon) on May 30, 2010 at 08:24 UTC
    package My::Module; shift; # throw away my own package name our $fh = shift; {...} # check to see it's a good filehandle sub later { print $fh, 'Arbitrary text.'; }; 1;

    I think you rather meant:

    package My::Module; our $fh; sub import { shift; # throw away my own package name $fh = shift; #{...} # check to see it's a good filehandle } sub later { print $fh 'Arbitrary text.'; } 1;

    As for what arguments to allow, I would not go to the trouble of allowing bareword filehandles (STDOUT) or filehandle names as strings ('STDOUT'), but a file name passed as a plain string (use My::Module '/var/foo.log';) might make sense, IMHO, in which case the module would open the corresponding handle.  (You could use ref \$fh in the module's import routine to tell apart...)

      I would encourage passing in via \*STDOUT over *STDOUT. Globs are a type from Perl 4 and look too much like strings. Checking !ref($arg) is much more common than checking "GLOB" eq ref(\$arg) and !ref($arg) needs to be checked anyway in order to deal with lexical file handles and \*STDOUT and *STDOUT{IO}.

      Supporting passing in "STDOUT" gets rather tricky. Consider:

      package Whatever; use My::Logger qw< setLogLevel >; setLogLevel( STDOUT => 1 ); open LOG, '>>', 'some.log' or die; setLogLevel( LOG => 2 );

      My::Logger::setLogLevel() needs to treat 'STDOUT' as *STDOUT (or *main::STDOUT) but must treat 'LOG' as *Whatever::LOG.

      my( $arg )= @_; my $fh; if( ref( $arg ) ) { $fh= $arg; } elsif( "GLOB" eq $arg ) { $fh= \$arg; # or $fh= *{$arg}{IO}; } else { # It is a plain string. If you want to # treat it as the name of a glob, then # you'll need to check for /::|'/ and # prepend caller().'::', unless it is # special like 'STDOUT'. # You'll also need 'no strict;' here. ... }

      - tye        

      The truth is that {...} in my sample code stands in for a multitude of sins. In reality, I loop through @_ looking for one I like, splice it out into a lexical, test it for writability, then stash it in caller's namespace and source filter his code to call a routine in My::Module which grabs $fh from stash, prints to it, and so forth. Deliberately, I'm not showing any of this, including the import sub, which isn't one anyway. Sorry. our $fh was just a shorthand for the wacky stash-and-grab.

      I'll rewrite the example along your improved lines, since none of this has any bearing on the question of what kind of syntax caller is allowed.

      - the lyf so short, the craft so long to lerne -

      At the risk of beating a rotten equine carcass, our $fh doesn't really work nicely no matter what its scope. If the user's code contains two calling modules, each of which use My::Module $fh, there will be a conflict. I'm sure that's obvious. The standard way of dealing with this is like:

      use My::Module; our $tank = My::Module->new($fh);

      ... but since My::Module is actually a fire-and-forget source filter, caller never explicitly does anything with $tank, which should be a package variable anyway. I'm not willing to demand that caller declare $::tank himself according to some prescribed cargo cult code; therefore, I stash-and-grab.

      I promise that all this will make more sense when I get the thing out the door.

      - the lyf so short, the craft so long to lerne -
Re: Passing a Filehandle that Might be a Bareword
by proceng (Scribe) on May 30, 2010 at 04:58 UTC
    Should I allow for the possibility that caller may decide to supply the filehandle using another syntax? If so, what should I allow?
    Right off the top of my head, I would say that it is a very bad idea to accept a file handle from an unknown source. It would be better (IMHO) to accept a file name (in taint mode) and sanitize it within your module.

    Picture this: A program manages to escalate it's privileges, then opens a system file for write access. It then passes your module the file handle.

    If you (instead) receive the file name, you have a better chance of making sure that any action your module takes is benign.

      I have to concur with anonymonk, what could the bad guys do by passing module X a particular open filehandle, that they couldn't do themselves?

      Format the contents nicely perhaps.

        It would be better (IMHO) to accept a file name (in taint mode) and sanitize it within your module.
        The intent was to reinforce that incoming information (in this case a file handle with no other attached information) should be sanitized within your module.
        While the source could well be benign, it is not that hard to use secure practices from the start.
        Given that my background is information and systems security, I tend to preach security best practices (in conjunction with PBP ;-), of course).
      Right off the top of my head, I would say that it is a very bad idea to accept a file handle from an unknown source.

      What unknown source? Filehandles are special in-memory data structures. For one to exist, a program has to be running already. If a program is already running on your machine, its already running, the game is over, it doesn't need your module to do anything, its already running.

      If you think the module should do anything related to security paranoia (most modules don't need this, and there isn't any indication this is the OPs intend), the safe thing is for the module to accept a file handle, instead of a file name.

      That allows the caller of the module to open a file, and actually drop privileges before calling the module. For instance, a daemon could start as a root user, open the necessary files, drop privileges to a normal user, and then call a third party module, passing it file handles (which where opened as the root user). Said module can no longer open files that require root privileges. Your suggestion however, requires the code in the module to run using root privileges.

      Picture this: A program manages to escalate it's privileges, then opens a system file for write access. It then passes your module the file handle.
      Then what? What damage is the module going to do the program cannot do already? Besides, if the program manage to escalate the privileges before opening the file, the privileges will certainly still be up by the time the module is asked to open the file.
      If you (instead) receive the file name, you have a better chance of making sure that any action your module takes is benign.
      How?
Re: Passing a Filehandle that Might be a Bareword
by ikegami (Patriarch) on May 31, 2010 at 03:26 UTC
    my $fh; BEGIN { open $fh, '>>', '/var/foo.log' or die; } use My::Module $fh;
    could be written as
    use My::Module do { open my $fh, '>>', '/var/foo.log' or die; $fh };

    But it's still rather icky.

    As for validating the handle, I suggest you don't. You'll get an error when you try to use the bad argument as a file handle, and that should be good enough here.