The File Access Operators: To Use, or Not to Use

The file access operators are, for the purpose of this blog entry, the file test operators -r, -w, -x, -R, -W, and -X. The upper case operators test the ability of the user's real UID to read, write, or execute the file being tested. The lower case operators do the same for the user's effective UID.

Though Perl provides these, their documentation comes with cautions about their use. The rest of this blog entry represents my thoughts on their use or avoidance.

The primary limitation on these operators is that they only test the mode bits returned by the stat() function. The result of this test does not guarantee what will happen if you actually try to operate on the file. For example, most operating systems support Access Control Lists (ACLs) on files, and these can override the mode bits. Even without these, a file system can be mounted read-only, and this will probably not be reflected in the results of the -w and -W tests. An executable file can be corrupt in various ways, including something as simple as a shebang line that points to a non-existent executable.

If you actually intend to use the file or not based on the results of the test, there is another consideration. Even if the results of the test reflect reality, access to the file can change between the time you test it and the time you use it. The Perl documentation calls this out specifically for the access operators, though it seems to me that it is an issue for any file test operator.

So with all these problems, why would anyone use these tests at all?

I suppose one reason is to audit file protections. For this you might want to test in the scope of the filetest pragma if ACLs might be present.

But there are cases where, even if the intent is to open a file, the correct way to avoid the access operators is not obvious. For example, dos2unix() in core module ExtUtils::Command wants to silently skip files when it lacks the permission to rewrite them. As of version 7.64 (Perl 5.36.0) this is accomplished with

    # Note that the name of the file is in $_
    return if -d;
    return unless -w _;
    return unless -r _;
    return if -B _;

local $\;

my $orig = $_;
my $temp = '.dos2unix_tmp';
open ORIG, $_ or do { warn "dos2unix can't open $_: $!"; return };
open TEMP, ">$temp" or
do { warn "dos2unix can't create .dos2unix_tmp: $!"; return };

I think the access operators could be eliminated by changing the open() statements to something along the lines of

    open ORIG, $_ or
	do { $!{EACCES} or warn "dos2unix can't open $_: $!"; return };

For %!, see my blog entry on Errno.

Do I have enough chutzpah to submit a pull request on such a key module? No way! I am probably not alone in this, or the code would use lexical file handles and the three-argument version of open().

I suspect that many of my own uses of these tests are badly-thought-out ad-hocery. I feel that I can justify some of the -x operators because I might be feeling around in a system trying to figure out which executables are actually available. I can not say whether this is a good justification. The first couple uses of -w I looked at were things that, when I thought about them, needed to be re-coded -- sometimes (I am embarrassed to say) in terms of error-handling of the relevant open().

With all this, it surprises me that there is no Perl-Critic policy prohibiting the file test access operators; but if there is, I have been unable to find it. The closest I have come is Kevin Ryde's Perl::Critic::Policy::ValuesAndExpressions::ProhibitFiletest_f, which is part of his Perl-Critic-Pulp distribution. This could be easily modified to check for use of the access operators. Allowing them in the scope of a use filetest 'access'; might well be desirable, but would definitely be harder.

1 Comment

I just never hear this another way, I do not expect that my computer will lie to me:(

Leave a comment

About Tom Wyant

user-pic I blog about Perl.