This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
detect struct stat.st_dev's size and signedness, and return it safely
authorTony Cook <tony@develop-help.com>
Fri, 9 Jul 2021 00:22:40 +0000 (10:22 +1000)
committerTony Cook <tony@develop-help.com>
Wed, 1 Sep 2021 00:59:44 +0000 (10:59 +1000)
On FreeBSD dev_t (and hence the st_dev member of struct stat) is an
unsigned 64-bit integer, and the previous simple PUSHi() corrupted
that.

A previous version of this reflected the st_ino code and implemented
our own number to string conversion, but a system with such a large
st_dev should be assumed to have inttypes.h, and an intmax_t which is
no smaller than st_dev.

The st_ino code could probably be changed similarly, but 64-bit inode
numbers are not a new thing, so it may be riskier.

15 files changed:
Configure
Cross/config.sh-arm-linux
Cross/config.sh-arm-linux-n770
NetWare/config.wc
Porting/config.sh
config_h.SH
configure.com
plan9/config_sh.sample
pp_sys.c
t/op/stat.t
uconfig.h
uconfig.sh
uconfig64.sh
win32/config.gc
win32/config.vc

index 55e2ebb..08f2445 100755 (executable)
--- a/Configure
+++ b/Configure
@@ -1321,6 +1321,8 @@ shsharp=''
 spitshell=''
 src=''
 ssizetype=''
+st_dev_sign=''
+st_dev_size=''
 st_ino_sign=''
 st_ino_size=''
 startperl=''
@@ -22744,6 +22746,74 @@ else
 fi
 $rm_try
 
+: Check the size of st_dev
+$echo " "
+$echo "Checking the size of st_dev..." >&4
+$cat > try.c <<EOCP
+#include <sys/stat.h>
+#include <stdio.h>
+#$i_stdlib I_STDLIB
+#ifdef I_STDLIB
+#include <stdlib.h>
+#endif
+int main() {
+    struct stat st;
+    printf("%d\n", (int)sizeof(st.st_dev));
+    exit(0);
+}
+EOCP
+set try
+if eval $compile_ok; then
+       val=`$run ./try`
+       case "$val" in
+       '')     st_dev_size=4
+               $echo "(I can't execute the test program--guessing $st_dev_size.)" >&4
+               ;;
+       *)      st_dev_size=$val
+               $echo "Your st_dev is $st_dev_size bytes long."
+               ;;
+       esac
+else
+       st_dev_size=4
+       $echo "(I can't compile the test program--guessing $st_dev_size.)" >&4
+fi
+$rm_try
+
+: Check if st_dev is signed
+$echo " "
+$echo "Checking the sign of st_dev..." >&4
+$cat > try.c <<EOCP
+#include <sys/stat.h>
+#include <stdio.h>
+int main() {
+       struct stat foo;
+        foo.st_dev = -1;
+       if (foo.st_dev < 0)
+               printf("-1\n");
+       else
+               printf("1\n");
+}
+EOCP
+set try
+if eval $compile; then
+       val=`$run ./try`
+       case "$val" in
+       '')     st_dev_sign=1
+               $echo "(I can't execute the test program--guessing unsigned.)" >&4
+               ;;
+       *)      st_dev_sign=$val
+               case "$st_dev_sign" in
+                1) $echo "Your st_dev is unsigned." ;;
+               -1) $echo "Your st_dev is signed."   ;;
+               esac
+               ;;
+       esac
+else
+       st_dev_sign=1
+       $echo "(I can't compile the test program--guessing unsigned.)" >&4
+fi
+$rm_try
+
 : see what type of char stdio uses.
 echo " "
 echo '#include <stdio.h>' | $cppstdin $cppminus > stdioh
@@ -25299,6 +25369,8 @@ srand48_r_proto='$srand48_r_proto'
 srandom_r_proto='$srandom_r_proto'
 src='$src'
 ssizetype='$ssizetype'
+st_dev_sign='$st_dev_sign'
+st_dev_size='$st_dev_size'
 st_ino_sign='$st_ino_sign'
 st_ino_size='$st_ino_size'
 startperl='$startperl'
index fd1a198..3df3dcf 100644 (file)
@@ -1078,6 +1078,8 @@ srand48_r_proto='0'
 srandom_r_proto='0'
 src='.'
 ssizetype='ssize_t'
+st_dev_sign='1'
+st_dev_size='4'
 st_ino_sign='1'
 st_ino_size='4'
 startperl='#!/usr/bin/perl'
index bd9f154..8e13f41 100644 (file)
@@ -1076,6 +1076,8 @@ srand48_r_proto='0'
 srandom_r_proto='0'
 src='.'
 ssizetype='ssize_t'
+st_dev_sign='1'
+st_dev_size='4'
 st_ino_sign='1'
 st_ino_size='4'
 startperl='#!/usr/bin/perl'
index e6f9650..e4c6fd9 100644 (file)
@@ -1042,6 +1042,8 @@ srand48_r_proto='0'
 srandom_r_proto='0'
 src=''
 ssizetype='int'
+st_dev_sign='1'
+st_dev_size='4'
 st_ino_sign='1'
 st_ino_size='4'
 startperl='#!perl'
index 16be950..26fdf0d 100644 (file)
@@ -1107,6 +1107,8 @@ srand48_r_proto='REENTRANT_PROTO_I_LS'
 srandom_r_proto='REENTRANT_PROTO_I_TS'
 src='.'
 ssizetype='ssize_t'
+st_dev_sign='1'
+st_dev_size='4'
 st_ino_sign='1'
 st_ino_size='8'
 startperl='#!/opt/perl/bin/perl5.35.4'
index b172eae..82677a4 100755 (executable)
@@ -4198,6 +4198,13 @@ sed <<!GROK!THIS! >$CONFIG_H -e 's!^#undef\(.*/\)\*!/\*#define\1 \*!' -e 's!^#un
  */
 #define SELECT_MIN_BITS        $selectminbits  /**/
 
+/* ST_DEV_SIZE:
+ *     This variable contains the size of struct stat's st_dev in bytes.
+ */
+/* ST_DEV_SIGN:
+ *     This symbol holds the signedness of struct stat's st_dev.
+ *     1 for unsigned, -1 for signed.
+ */
 /* ST_INO_SIZE:
  *     This variable contains the size of struct stat's st_ino in bytes.
  */
@@ -4205,6 +4212,8 @@ sed <<!GROK!THIS! >$CONFIG_H -e 's!^#undef\(.*/\)\*!/\*#define\1 \*!' -e 's!^#un
  *     This symbol holds the signedness of struct stat's st_ino.
  *     1 for unsigned, -1 for signed.
  */
+#define ST_DEV_SIGN $st_dev_sign       /* st_dev sign */
+#define ST_DEV_SIZE $st_dev_size       /* st_dev size */
 #define ST_INO_SIGN $st_ino_sign       /* st_ino sign */
 #define ST_INO_SIZE $st_ino_size       /* st_ino size */
 
index 126a372..b35e314 100644 (file)
@@ -6971,6 +6971,8 @@ $ WC "src='" + src + "'"
 $ WC "ssizetype='int'"
 $ WC "startperl=" + startperl ! This one's special--no enclosing single quotes
 $ WC "static_ext='" + static_ext + "'"
+$ WC "st_dev_size='"4"'"
+$ WC "st_dev_sign='1'"
 $ WC "st_ino_size='" + st_ino_size + "'"
 $ WC "st_ino_sign='1'"
 $ WC "stdchar='" + stdchar + "'"
index baba3ec..d1a89dc 100644 (file)
@@ -1049,6 +1049,8 @@ srand48_r_proto='0'
 srandom_r_proto='0'
 src='.'
 ssizetype='ssize_t'
+st_dev_sign='1'
+st_dev_size='4'
 st_ino_sign='1'
 st_ino_size='4'
 startperl='#!/bin/perl'
index 9b2d64a..cb449c3 100644 (file)
--- a/pp_sys.c
+++ b/pp_sys.c
@@ -2916,7 +2916,38 @@ PP(pp_stat)
     if (max) {
         EXTEND(SP, max);
         EXTEND_MORTAL(max);
+#if ST_DEV_SIZE < IVSIZE || (ST_DEV_SIZE == IVSIZE && ST_DEV_SIGN < 0)
         mPUSHi(PL_statcache.st_dev);
+#elif ST_DEV_SIZE == IVSIZE
+        mPUSHu(PL_statcache.st_dev);
+#else
+#  if ST_DEV_SIGN < 0
+        if (LIKELY((IV)PL_statcache.st_dev == PL_statcache.st_dev)) {
+            mPUSHi((IV)PL_statcache.st_dev);
+        }
+#  else
+        if (LIKELY((UV)PL_statcache.st_dev == PL_statcache.st_dev)) {
+            mPUSHu((UV)PL_statcache.st_dev);
+        }
+#  endif
+        else {
+            char buf[sizeof(PL_statcache.st_dev)*3+1];
+            /* sv_catpvf() casts 'j' size values down to IV, so it
+               isn't suitable for use here.
+            */
+#    if defined(I_INTTYPES) && defined(HAS_SNPRINTF)
+#      if ST_DEV_SIGN < 0
+            int size = snprintf(buf, sizeof(buf), "%" PRIdMAX, (intmax_t)PL_statcache.st_dev);
+#      else
+            int size = snprintf(buf, sizeof(buf), "%" PRIuMAX, (uintmax_t)PL_statcache.st_dev);
+#      endif
+            STATIC_ASSERT_STMT(sizeof(intmax_t) >= sizeof(PL_statcache.st_dev));
+            mPUSHp(buf, size);
+#    else
+#      error extraordinarily large st_dev but no inttypes.h or no snprintf
+#    endif
+        }
+#endif
         {
             /*
              * We try to represent st_ino as a native IV or UV where
index 099a3f1..6397e20 100644 (file)
@@ -29,7 +29,7 @@ if ($^O eq 'MSWin32') {
 
 my $Errno_loaded = eval { require Errno };
 
-plan tests => 110;
+plan tests => 111;
 
 my $Perl = which_perl();
 
@@ -664,6 +664,24 @@ SKIP: {
     unlink $link;
 }
 
+ SKIP:
+{
+    # test needs a FreeBSD /usr/bin/stat
+    # /tmp is typically tmpfs on a new FreeBSD
+    $^O eq "freebsd"
+        or skip "only checking freebsd for now", 1;
+    -x "/usr/bin/stat"
+        or skip "no /usr/bin/stat", 1;
+    my @s = stat "/tmp";
+    @s or skip "No /tmp found", 1;
+    my $test = `/usr/bin/stat -f '%d %i' /tmp`;
+    $test && $test =~ /^-?\d+ -?\d+/
+        or skip "stat didn't return an expected result";
+    chomp $test;
+    is("$s[0] $s[1]", $test,
+       "perl stat didn't match system stat utility");
+}
+
 END {
     chmod 0666, $tmpfile;
     unlink_all $tmpfile;
index adb7269..9acfb12 100644 (file)
--- a/uconfig.h
+++ b/uconfig.h
  */
 #define SELECT_MIN_BITS        32      /**/
 
+/* ST_DEV_SIZE:
+ *     This variable contains the size of struct stat's st_dev in bytes.
+ */
+/* ST_DEV_SIGN:
+ *     This symbol holds the signedness of struct stat's st_dev.
+ *     1 for unsigned, -1 for signed.
+ */
 /* ST_INO_SIZE:
  *     This variable contains the size of struct stat's st_ino in bytes.
  */
  *     This symbol holds the signedness of struct stat's st_ino.
  *     1 for unsigned, -1 for signed.
  */
+#define ST_DEV_SIGN 1  /* st_dev sign */
+#define ST_DEV_SIZE 4  /* st_dev size */
 #define ST_INO_SIGN 1  /* st_ino sign */
 #define ST_INO_SIZE 4  /* st_ino size */
 
 #endif
 
 /* Generated from:
- * f88762343858d290ac1b2bce8613d97ce15c18eada7a5a587fb9b8a8053d0d7f config_h.SH
- * 24570f2ff38b22b436d34127ff85a3f3a075a4dea1359c56f16196bdbe376b1c uconfig.sh
+ * 4769474cd122c46e0efc0224475eb6401d7837cf93c9484af644422d1c5a68e3 config_h.SH
+ * 9aacfe7bf1c55c60b2116e885bafc3f0852a9f52f1ba926d598dd6aa271aa8a2 uconfig.sh
  * ex: set ro: */
index 0be8388..1e353b4 100644 (file)
@@ -850,6 +850,8 @@ srand48_r_proto='0'
 srandom_r_proto='0'
 src='.'
 ssizetype=int
+st_dev_sign='1'
+st_dev_size='4'
 st_ino_sign='1'
 st_ino_size='4'
 startperl='#!perl'
index 307e502..53a7ee8 100644 (file)
@@ -850,6 +850,8 @@ srand48_r_proto='0'
 srandom_r_proto='0'
 src='.'
 ssizetype=long
+st_dev_sign='1'
+st_dev_size='4'
 st_ino_sign='1'
 st_ino_size='8'
 startperl='#!perl'
index 25fe7f0..80e73ba 100644 (file)
@@ -1073,6 +1073,8 @@ srand48_r_proto='0'
 srandom_r_proto='0'
 src=''
 ssizetype='int'
+st_dev_sign='1'
+st_dev_size='4'
 st_ino_sign='1'
 st_ino_size='4'
 startperl='#!perl'
index dbc8967..f01ad8a 100644 (file)
@@ -1072,6 +1072,8 @@ srand48_r_proto='0'
 srandom_r_proto='0'
 src=''
 ssizetype='int'
+st_dev_sign='1'
+st_dev_size='4'
 st_ino_sign='1'
 st_ino_size='4'
 startperl='#!perl'