b8c914
From 8c7182b26a43f14cd8afbfbe4448cbbd691c3609 Mon Sep 17 00:00:00 2001
b8c914
From: Zefram <zefram@fysh.org>
b8c914
Date: Wed, 15 Nov 2017 08:11:37 +0000
b8c914
Subject: [PATCH] set $! when statting a closed filehandle
b8c914
MIME-Version: 1.0
b8c914
Content-Type: text/plain; charset=UTF-8
b8c914
Content-Transfer-Encoding: 8bit
b8c914
b8c914
When a stat fails because it's on a closed or otherwise invalid
b8c914
filehandle, $! was often not being set, depending on the operation
b8c914
and the nature of the invalidity.  Consistently set it to EBADF.
b8c914
Fixes [perl #108288].
b8c914
b8c914
Petr Písař: Ported to 5.26.1.
b8c914
b8c914
Signed-off-by: Petr Písař <ppisar@redhat.com>
b8c914
---
b8c914
 MANIFEST           |  1 +
b8c914
 doio.c             | 10 +++++++++-
b8c914
 pp_sys.c           | 22 ++++++++++++---------
b8c914
 t/op/stat_errors.t | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
b8c914
 4 files changed, 80 insertions(+), 10 deletions(-)
b8c914
 create mode 100644 t/op/stat_errors.t
b8c914
b8c914
diff --git a/MANIFEST b/MANIFEST
b8c914
index fcbf5cc..996759e 100644
b8c914
--- a/MANIFEST
b8c914
+++ b/MANIFEST
b8c914
@@ -5670,6 +5670,7 @@ t/op/srand.t			See if srand works
b8c914
 t/op/sselect.t			See if 4 argument select works
b8c914
 t/op/stash.t			See if %:: stashes work
b8c914
 t/op/stat.t			See if stat works
b8c914
+t/op/stat_errors.t		See if stat and file tests handle threshold errors
b8c914
 t/op/state.t			See if state variables work
b8c914
 t/op/study.t			See if study works
b8c914
 t/op/studytied.t		See if study works with tied scalars
b8c914
diff --git a/doio.c b/doio.c
b8c914
index 70d7747..71dc6e4 100644
b8c914
--- a/doio.c
b8c914
+++ b/doio.c
b8c914
@@ -1437,8 +1437,11 @@ Perl_my_stat_flags(pTHX_ const U32 flags)
b8c914
     if (PL_op->op_flags & OPf_REF) {
b8c914
 	gv = cGVOP_gv;
b8c914
       do_fstat:
b8c914
-        if (gv == PL_defgv)
b8c914
+        if (gv == PL_defgv) {
b8c914
+	    if (PL_laststatval < 0)
b8c914
+		SETERRNO(EBADF,RMS_IFI);
b8c914
             return PL_laststatval;
b8c914
+	}
b8c914
 	io = GvIO(gv);
b8c914
         do_fstat_have_io:
b8c914
         PL_laststype = OP_STAT;
b8c914
@@ -1449,6 +1452,7 @@ Perl_my_stat_flags(pTHX_ const U32 flags)
b8c914
                 int fd = PerlIO_fileno(IoIFP(io));
b8c914
                 if (fd < 0) {
b8c914
                     /* E.g. PerlIO::scalar has no real fd. */
b8c914
+		    SETERRNO(EBADF,RMS_IFI);
b8c914
                     return (PL_laststatval = -1);
b8c914
                 } else {
b8c914
                     return (PL_laststatval = PerlLIO_fstat(fd, &PL_statcache));
b8c914
@@ -1459,6 +1463,7 @@ Perl_my_stat_flags(pTHX_ const U32 flags)
b8c914
         }
b8c914
 	PL_laststatval = -1;
b8c914
 	report_evil_fh(gv);
b8c914
+	SETERRNO(EBADF,RMS_IFI);
b8c914
 	return -1;
b8c914
     }
b8c914
     else if ((PL_op->op_private & (OPpFT_STACKED|OPpFT_AFTER_t))
b8c914
@@ -1511,6 +1516,8 @@ Perl_my_lstat_flags(pTHX_ const U32 flags)
b8c914
 	if (cGVOP_gv == PL_defgv) {
b8c914
 	    if (PL_laststype != OP_LSTAT)
b8c914
 		Perl_croak(aTHX_ "%s", no_prev_lstat);
b8c914
+	    if (PL_laststatval < 0)
b8c914
+		SETERRNO(EBADF,RMS_IFI);
b8c914
 	    return PL_laststatval;
b8c914
 	}
b8c914
 	PL_laststatval = -1;
b8c914
@@ -1520,6 +1527,7 @@ Perl_my_lstat_flags(pTHX_ const U32 flags)
b8c914
 		              "Use of -l on filehandle %" HEKf,
b8c914
 			      HEKfARG(GvENAME_HEK(cGVOP_gv)));
b8c914
 	}
b8c914
+	SETERRNO(EBADF,RMS_IFI);
b8c914
 	return -1;
b8c914
     }
b8c914
     if ((PL_op->op_private & (OPpFT_STACKED|OPpFT_AFTER_t))
b8c914
diff --git a/pp_sys.c b/pp_sys.c
b8c914
index fefbea3..87961f1 100644
b8c914
--- a/pp_sys.c
b8c914
+++ b/pp_sys.c
b8c914
@@ -2925,10 +2925,11 @@ PP(pp_stat)
b8c914
 		Perl_croak(aTHX_ "The stat preceding lstat() wasn't an lstat");
b8c914
 	}
b8c914
 
b8c914
-	if (gv != PL_defgv) {
b8c914
-	    bool havefp;
b8c914
+	if (gv == PL_defgv) {
b8c914
+	    if (PL_laststatval < 0)
b8c914
+		SETERRNO(EBADF,RMS_IFI);
b8c914
+	} else {
b8c914
           do_fstat_have_io:
b8c914
-	    havefp = FALSE;
b8c914
 	    PL_laststype = OP_STAT;
b8c914
 	    PL_statgv = gv ? gv : (GV *)io;
b8c914
             SvPVCLEAR(PL_statname);
b8c914
@@ -2939,22 +2940,25 @@ PP(pp_stat)
b8c914
                     if (IoIFP(io)) {
b8c914
                         int fd = PerlIO_fileno(IoIFP(io));
b8c914
                         if (fd < 0) {
b8c914
+			    report_evil_fh(gv);
b8c914
                             PL_laststatval = -1;
b8c914
                             SETERRNO(EBADF,RMS_IFI);
b8c914
                         } else {
b8c914
                             PL_laststatval = PerlLIO_fstat(fd, &PL_statcache);
b8c914
-                            havefp = TRUE;
b8c914
                         }
b8c914
                     } else if (IoDIRP(io)) {
b8c914
                         PL_laststatval =
b8c914
                             PerlLIO_fstat(my_dirfd(IoDIRP(io)), &PL_statcache);
b8c914
-                        havefp = TRUE;
b8c914
                     } else {
b8c914
+			report_evil_fh(gv);
b8c914
                         PL_laststatval = -1;
b8c914
+			SETERRNO(EBADF,RMS_IFI);
b8c914
                     }
b8c914
-            }
b8c914
-	    else PL_laststatval = -1;
b8c914
-	    if (PL_laststatval < 0 && !havefp) report_evil_fh(gv);
b8c914
+            } else {
b8c914
+		report_evil_fh(gv);
b8c914
+		PL_laststatval = -1;
b8c914
+		SETERRNO(EBADF,RMS_IFI);
b8c914
+	    }
b8c914
         }
b8c914
 
b8c914
 	if (PL_laststatval < 0) {
b8c914
@@ -3451,7 +3455,7 @@ PP(pp_fttty)
b8c914
     else if (name && isDIGIT(*name) && grok_atoUV(name, &uv, NULL) && uv <= PERL_INT_MAX)
b8c914
         fd = (int)uv;
b8c914
     else
b8c914
-	FT_RETURNUNDEF;
b8c914
+	fd = -1;
b8c914
     if (fd < 0) {
b8c914
         SETERRNO(EBADF,RMS_IFI);
b8c914
 	FT_RETURNUNDEF;
b8c914
diff --git a/t/op/stat_errors.t b/t/op/stat_errors.t
b8c914
new file mode 100644
b8c914
index 0000000..e043c61
b8c914
--- /dev/null
b8c914
+++ b/t/op/stat_errors.t
b8c914
@@ -0,0 +1,57 @@
b8c914
+#!./perl
b8c914
+
b8c914
+BEGIN {
b8c914
+    chdir 't' if -d 't';
b8c914
+    require './test.pl';
b8c914
+    set_up_inc('../lib');
b8c914
+}
b8c914
+
b8c914
+plan(tests => 2*11*29);
b8c914
+
b8c914
+use Errno qw(EBADF ENOENT);
b8c914
+
b8c914
+open(SCALARFILE, "<", \"wibble") or die $!;
b8c914
+open(CLOSEDFILE, "<", "./test.pl") or die $!;
b8c914
+close(CLOSEDFILE) or die $!;
b8c914
+opendir(CLOSEDDIR, "../lib") or die $!;
b8c914
+closedir(CLOSEDDIR) or die $!;
b8c914
+
b8c914
+foreach my $op (
b8c914
+    qw(stat lstat),
b8c914
+    (map { "-$_" } qw(r w x o R W X O e z s f d l p S b c t u g k T B M A C)),
b8c914
+) {
b8c914
+    foreach my $arg (
b8c914
+	(map { ($_, "\\*$_") }
b8c914
+	    qw(NEVEROPENED SCALARFILE CLOSEDFILE CLOSEDDIR _)),
b8c914
+	"\"tmpnotexist\"",
b8c914
+    ) {
b8c914
+	my $argdesc = $arg;
b8c914
+	if ($arg eq "_") {
b8c914
+	    my @z = lstat "tmpnotexist";
b8c914
+	    $argdesc .= " with prior stat fail";
b8c914
+	}
b8c914
+	SKIP: {
b8c914
+	    if ($op eq "-l" && $arg =~ /\A\\/) {
b8c914
+		# The op weirdly stringifies the globref and uses it as
b8c914
+		# a filename, rather than treating it as a file handle.
b8c914
+		# That might be a bug, but while that behaviour exists it
b8c914
+		# needs to be exempted from these tests.
b8c914
+		skip "-l on globref", 2;
b8c914
+	    }
b8c914
+	    if ($op eq "-t" && $arg eq "\"tmpnotexist\"") {
b8c914
+		# The op doesn't operate on filenames.
b8c914
+		skip "-t on filename", 2;
b8c914
+	    }
b8c914
+	    $! = 0;
b8c914
+	    my $res = eval "$op $arg";
b8c914
+	    my $err = $!;
b8c914
+	    is $res, $op =~ /\A-/ ? undef : !!0, "result of $op $arg";
b8c914
+	    is 0+$err,
b8c914
+		$arg eq "\"tmpnotexist\"" ||
b8c914
+		    ($op =~ /\A-[TB]\z/ && $arg =~ /_\z/) ? ENOENT : EBADF,
b8c914
+		"error from $op $arg";
b8c914
+	}
b8c914
+    }
b8c914
+}
b8c914
+
b8c914
+1;
b8c914
-- 
b8c914
2.13.6
b8c914