From b586c1650acb2f1acc4a1b1a7f9fc3d1554bec78 Mon Sep 17 00:00:00 2001 From: Tony Cook Date: Fri, 9 Jul 2021 10:22:40 +1000 Subject: [PATCH] detect struct stat.st_dev's size and signedness, and return it safely 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. --- Configure | 72 ++++++++++++++++++++++++++++++++++ Cross/config.sh-arm-linux | 2 + Cross/config.sh-arm-linux-n770 | 2 + NetWare/config.wc | 2 + Porting/config.sh | 2 + config_h.SH | 9 +++++ configure.com | 2 + plan9/config_sh.sample | 2 + pp_sys.c | 31 +++++++++++++++ t/op/stat.t | 20 +++++++++- uconfig.h | 13 +++++- uconfig.sh | 2 + uconfig64.sh | 2 + win32/config.gc | 2 + win32/config.vc | 2 + 15 files changed, 162 insertions(+), 3 deletions(-) diff --git a/Configure b/Configure index 32a02544b79b..5155c1f00847 100755 --- a/Configure +++ b/Configure @@ -1320,6 +1320,8 @@ shsharp='' spitshell='' src='' ssizetype='' +st_dev_sign='' +st_dev_size='' st_ino_sign='' st_ino_size='' startperl='' @@ -22695,6 +22697,74 @@ else fi $rm_try +: Check the size of st_dev +$echo " " +$echo "Checking the size of st_dev..." >&4 +$cat > try.c < +#include +#$i_stdlib I_STDLIB +#ifdef I_STDLIB +#include +#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 < +#include +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 ' | $cppstdin $cppminus > stdioh @@ -25250,6 +25320,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' diff --git a/Cross/config.sh-arm-linux b/Cross/config.sh-arm-linux index 8c263cef4332..d5a8019eb788 100644 --- a/Cross/config.sh-arm-linux +++ b/Cross/config.sh-arm-linux @@ -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' diff --git a/Cross/config.sh-arm-linux-n770 b/Cross/config.sh-arm-linux-n770 index 4104807906b0..915aeca4de95 100644 --- a/Cross/config.sh-arm-linux-n770 +++ b/Cross/config.sh-arm-linux-n770 @@ -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' diff --git a/NetWare/config.wc b/NetWare/config.wc index cd2ec7361937..7791b6c86f2a 100644 --- a/NetWare/config.wc +++ b/NetWare/config.wc @@ -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' diff --git a/Porting/config.sh b/Porting/config.sh index 082463cda98d..6951d8bb707e 100644 --- a/Porting/config.sh +++ b/Porting/config.sh @@ -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.2' diff --git a/config_h.SH b/config_h.SH index 40a80e312e0f..2ec91e515aa4 100755 --- a/config_h.SH +++ b/config_h.SH @@ -4193,6 +4193,13 @@ sed <$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. */ @@ -4200,6 +4207,8 @@ sed <$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 */ diff --git a/configure.com b/configure.com index 2704cb24595e..d36c60330362 100644 --- a/configure.com +++ b/configure.com @@ -6970,6 +6970,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 + "'" diff --git a/plan9/config_sh.sample b/plan9/config_sh.sample index d879b3af2570..174769377930 100644 --- a/plan9/config_sh.sample +++ b/plan9/config_sh.sample @@ -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' diff --git a/pp_sys.c b/pp_sys.c index 8c3e47850154..e72b96d371fc 100644 --- a/pp_sys.c +++ b/pp_sys.c @@ -2915,7 +2915,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 diff --git a/t/op/stat.t b/t/op/stat.t index 099a3f1e9806..6397e20fa278 100644 --- a/t/op/stat.t +++ b/t/op/stat.t @@ -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; diff --git a/uconfig.h b/uconfig.h index 40ca1fd42c14..6170314fde07 100644 --- a/uconfig.h +++ b/uconfig.h @@ -4158,6 +4158,13 @@ */ #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. */ @@ -4165,6 +4172,8 @@ * 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 */ @@ -5313,6 +5322,6 @@ #endif /* Generated from: - * 55a531381747550c11c2c61b9a9da2dacde4df465b874df55a9c923e495deb3a config_h.SH - * 2fece1e405c60ae089fe55acaa42471b6fba78b7ab4cefc6d5e18a94b72fc2c4 uconfig.sh + * e953597150b0e47d177abc2f4dd1351a49557e0b537ff593acf85f76709bcce7 config_h.SH + * 801886c55550c022c8edfea3ef6873294a11a495843466cf80bd32f3bcee95e4 uconfig.sh * ex: set ro: */ diff --git a/uconfig.sh b/uconfig.sh index 898ff9979fe5..87256fb96ad4 100644 --- a/uconfig.sh +++ b/uconfig.sh @@ -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' diff --git a/uconfig64.sh b/uconfig64.sh index c9a9559af7ad..bda4fb9fc4d2 100644 --- a/uconfig64.sh +++ b/uconfig64.sh @@ -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' diff --git a/win32/config.gc b/win32/config.gc index 55280f61a1ed..c9cd56f60430 100644 --- a/win32/config.gc +++ b/win32/config.gc @@ -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' diff --git a/win32/config.vc b/win32/config.vc index 0f89bc6ee0eb..43a7b0f66988 100644 --- a/win32/config.vc +++ b/win32/config.vc @@ -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'