1#!./perl 2 3BEGIN { 4 chdir 't' if -d 't'; 5 @INC = '../lib'; 6 require "./test.pl"; 7} 8 9use strict; 10use Fcntl ":seek"; 11use Config; 12use Errno; 13use Cwd "getcwd"; 14 15Win32::FsType() eq 'NTFS' 16 or skip_all("need NTFS"); 17 18my (undef, $maj, $min) = Win32::GetOSVersion(); 19 20my $vista_or_later = $maj >= 6; 21 22my $tmpfile1 = tempfile(); 23my $tmpfile2 = tempfile(); 24 25# test some of the win32 specific stat code, since we 26# don't depend on the CRT for some of it 27 28ok(link($0, $tmpfile1), "make a link to test nlink"); 29 30my @st = stat $0; 31open my $fh, "<", $0 or die; 32my @fst = stat $fh; 33 34ok(seek($fh, 0, SEEK_END), "seek to end"); 35my $size = tell($fh); 36close $fh; 37 38# the ucrt stat() is inconsistent here, using an A=0 drive letter for stat() 39# and the fd for fstat(), I assume that's something backward compatible. 40# 41# I don't see anything we could reasonable populate it with either. 42$st[6] = $fst[6] = 0; 43 44is("@st", "@fst", "check named stat vs handle stat"); 45 46ok($st[0], "we set dev by default now"); 47ok($st[1], "and ino"); 48 49# unlikely, but someone else might have linked to win32/stat.t 50cmp_ok($st[3], '>', 1, "should be more than one link"); 51 52# we now populate all stat fields ourselves, so check what we can 53is($st[7], $size, "we fetch size correctly"); 54 55cmp_ok($st[9], '<=', time(), "modification time before or on now"); 56ok(-f $0, "yes, we are a file"); 57ok(-d "win32", "and win32 is a directory"); 58pipe(my ($p1, $p2)); 59ok(-p $p1, "a pipe is a pipe"); 60close $p1; close $p2; 61ok(-r $0, "we are readable"); 62ok(!-x $0, "but not executable"); 63ok(-e $0, "we exist"); 64 65ok(open(my $nul, ">", "nul"), "open nul"); 66ok(-c $nul, "nul is a character device"); 67close $nul; 68 69my $nlink = $st[3]; 70 71# check we get nlinks etc for a directory 72@st = stat("win32"); 73ok($st[0], "got dev for a directory"); 74ok($st[1], "got ino for a directory"); 75ok($st[3], "got nlink for a directory"); 76 77# symbolic links 78unlink($tmpfile1); # no more hard link 79 80if (open my $fh, ">", "$tmpfile1.bat") { 81 ok(-x "$tmpfile1.bat", 'batch file is "executable"'); 82 SKIP: { 83 skip "executable bit for handles needs vista or later", 1 84 unless $vista_or_later; 85 ok(-x $fh, 'batch file handle is "executable"'); 86 } 87 close $fh; 88 unlink "$tmpfile1.bat"; 89} 90 91# mklink is available from Vista onwards 92# this may only work in an admin shell 93# MKLINK [[/D] | [/H] | [/J]] Link Target 94if (system("mklink $tmpfile1 win32\\stat.t") == 0) { 95 ok(-l $tmpfile1, "lstat sees a symlink"); 96 97 # check stat on file vs symlink 98 @st = stat $0; 99 my @lst = stat $tmpfile1; 100 101 $st[6] = $lst[6] = 0; 102 103 is("@st", "@lst", "check stat on file vs link"); 104 105 # our hard link no longer exists, check that is reflected in nlink 106 is($st[3], $nlink-1, "check nlink updated"); 107 108 is((lstat($tmpfile1))[7], length(readlink($tmpfile1)), 109 "check size matches length of link"); 110 111 unlink($tmpfile1); 112} 113 114# similarly for a directory 115if (system("mklink /d $tmpfile1 win32") == 0) { 116 ok(-l $tmpfile1, "lstat sees a symlink on the directory symlink"); 117 118 # check stat on directory vs symlink 119 @st = stat "win32"; 120 my @lst = stat $tmpfile1; 121 122 $st[6] = $lst[6] = 0; 123 124 is("@st", "@lst", "check stat on dir vs link"); 125 126 # for now at least, we need to rmdir symlinks to directories 127 rmdir( $tmpfile1 ); 128} 129 130# check a junction looks like a symlink 131 132if (system("mklink /j $tmpfile1 win32") == 0) { 133 ok(-l $tmpfile1, "lstat sees a symlink on the directory junction"); 134 135 my @st = lstat($tmpfile1); 136 is($st[7], length(readlink($tmpfile1)), 137 "check returned length matches POSIX"); 138 139 rmdir( $tmpfile1 ); 140} 141 142# test interaction between stat and utime 143if (ok(open(my $fh, ">", $tmpfile1), "make a work file")) { 144 # make our test file 145 close $fh; 146 147 my @st = stat $tmpfile1; 148 ok(@st, "stat our work file"); 149 150 # switch to the other half of the year, to flip from/to daylight 151 # savings time. It won't always do so, but it's close enough and 152 # avoids having to deal with working out exactly when it 153 # starts/ends (if it does), along with the hemisphere. 154 # 155 # By basing this on the current file times and using an offset 156 # that's the multiple of an hour we ensure the filesystem 157 # resolution supports the time we set. 158 my $moffset = 6 * 30 * 24 * 3600; 159 my $aoffset = $moffset - 24 * 3600;; 160 my $mymt = $st[9] - $moffset; 161 my $myat = $st[8] - $aoffset; 162 ok(utime($myat, $mymt, $tmpfile1), "set access and mod times"); 163 my @mst = stat $tmpfile1; 164 ok(@mst, "fetch stat after utime"); 165 is($mst[9], $mymt, "check mod time"); 166 is($mst[8], $myat, "check access time"); 167 168 unlink $tmpfile1; 169} 170 171# same for a directory 172if (ok(mkdir($tmpfile1), "make a work directory")) { 173 my @st = stat $tmpfile1; 174 ok(@st, "stat our work directory"); 175 176 my $moffset = 6 * 30 * 24 * 3600; 177 my $aoffset = $moffset - 24 * 3600;; 178 my $mymt = $st[9] - $moffset; 179 my $myat = $st[8] - $aoffset; 180 ok(utime($myat, $mymt, $tmpfile1), "set access and mod times"); 181 my @mst = stat $tmpfile1; 182 ok(@mst, "fetch stat after utime"); 183 is($mst[9], $mymt, "check mod time"); 184 is($mst[8], $myat, "check access time"); 185 186 rmdir $tmpfile1; 187} 188 189 SKIP: 190{ # github 19668 191 $Config{ivsize} == 8 192 or skip "Need 64-bit int", 1; 193 open my $tmp, ">", $tmpfile1 194 or skip "Cannot create test file: $!", 1; 195 close $tmp; 196 fresh_perl_is("utime(500_000_000_000, 500_000_000_000, '$tmpfile1')", 197 "", { stderr => 1 }, 198 "check debug output removed"); 199 unlink $tmpfile1; 200} 201 202# Other stat issues possibly fixed by the stat() re-work 203 204# https://github.com/Perl/perl5/issues/9025 - win32 - file test operators don't work for //?/UNC/server/file filenames 205# can't really make a reliable regression test for this 206# reproduced original problem with a gcc build 207# confirmed fixed with a gcc build 208 209# https://github.com/Perl/perl5/issues/8502 - filetest problem with STDIN/OUT on Windows 210 211{ 212 ok(-r *STDIN, "check stdin is readable"); 213 ok(-w *STDOUT, "check stdout is writable"); 214 215 # CompareObjectHandles() could fix this, but requires Windows 10 216 local our $TODO = "dupped *STDIN and *STDOUT not read/write"; 217 open my $dupin, "<&STDIN" or die; 218 open my $dupout, ">&STDOUT" or die; 219 ok(-r $dupin, "check duplicated stdin is readable"); 220 ok(-w $dupout, "check duplicated stdout is writable"); 221} 222 223# https://github.com/Perl/perl5/issues/6080 - Last mod time from stat() can be wrong on Windows NT/2000/XP 224# tested already 225 226# https://github.com/Perl/perl5/issues/4145 - Problem with filetest -x _ on Win2k AS Perl build 626 227# tested already 228 229# https://github.com/Perl/perl5/issues/14687 - Function lstat behavior case differs between Windows and Unix #14687 230 231{ 232 local our $TODO = "... .... treated as .. by Win32 API"; 233 ok(!-e ".....", "non-existing many dots shouldn't returned existence"); 234} 235 236# https://github.com/Perl/perl5/issues/7410 - -e tests not reliable under Win32 237{ 238 # there's to issues here: 239 # 1) CreateFile() successfully opens " . . " when opened with backup 240 # semantics/directory 241 # 2) opendir(" . . ") becomes FindFirstFile(" . . /*") which fails 242 # 243 # So we end up with success for the first and failure for the second, 244 # making them inconsistent, there may be a Vista level fix for this, 245 # but if we expect -e " . . " to fail we need a more complex fix. 246 local our $TODO = "strange space handling by Windows"; 247 ok(!-e " ", "filename ' ' shouldn't exist"); 248 ok(!-e " . . ", "filename ' . . ' shouldn't exist"); 249 ok(!-e " .. ", "filename ' .. ' shouldn't exist"); 250 ok(!-e " . ", "filename ' . ' shouldn't exist"); 251 252 ok(!!-e " . . " == !!opendir(FOO, " . . "), 253 "these should be consistent"); 254} 255 256# https://github.com/Perl/perl5/issues/12431 - Win32: -e '"' always returns true 257 258{ 259 ok(!-e '"', qq(filename '"' shouldn't exist)); 260} 261 262# https://github.com/Perl/perl5/issues/20204 263# Win32: stat/unlink fails on UNIX sockets 264SKIP: 265{ 266 use IO::Socket; 267 unlink $tmpfile1; 268 my $listen = IO::Socket::UNIX->new(Local => $tmpfile1, Listen => 0) 269 or skip "Cannot create unix socket", 1; 270 ok(-S $tmpfile1, "can stat a socket"); 271 ok(!-l $tmpfile1, "doesn't look like a symlink"); 272 unlink $tmpfile2; 273 if (system("mklink $tmpfile2 $tmpfile1") == 0) { 274 ok(-l $tmpfile2, "symlink to socket is a symlink (via lstat)"); 275 ok(-S $tmpfile2, "symlink to socket is also a socket (via stat)"); 276 unlink $tmpfile2; 277 } 278 close $listen; 279 unlink $tmpfile1; 280} 281 282{ 283 # if a symlink chain leads to a socket, or loops, or is broken, 284 # CreateFileA() fails, so we do our own link following. 285 # The link leading to a socket is checked above, here check loops 286 # fail, and that we get ELOOP (which isn't what MSVC returns, but 287 # try to be better). 288 if (system("mklink $tmpfile1 $tmpfile2") == 0 289 && system("mklink $tmpfile2 $tmpfile1") == 0) { 290 ok(!stat($tmpfile1), "looping symlink chain fails stat"); 291 is($!+0, &Errno::ELOOP, "check error set"); 292 ok(lstat($tmpfile1), "looping symlink chain passes lstat"); 293 294 unlink $tmpfile2; 295 ok(!stat($tmpfile1), "broken symlink"); 296 is($!+0, &Errno::ENOENT, "check error set"); 297 ok(lstat($tmpfile1), "broken symlink chain passes lstat"); 298 } 299 unlink $tmpfile1, $tmpfile2; 300} 301 302{ 303 # $tmpfile4 -> $tmpfile1/file1 -> ../$tmpfile2 -> abspath($tmpfile3) 304 # $tmpfile3 either doesn't exist, is a file, or is a socket 305 my ($tmpfile3, $tmpfile4) = (tempfile(), tempfile()); 306 ok(mkdir($tmpfile1), "make a directory"); 307 my $cwd = getcwd(); 308 if (system(qq(mklink $tmpfile4 $tmpfile1\\file1)) == 0 309 && system(qq(mklink $tmpfile1\\file1 ..\\$tmpfile2)) == 0 310 && system(qq(mklink $tmpfile2 "$cwd\\$tmpfile3")) == 0) { 311 ok(-l $tmpfile4, "yes, $tmpfile4 is a symlink"); 312 ok(!-e $tmpfile4, "but we can't stat it"); 313 314 open my $fh, ">", $tmpfile3 or die $!; 315 close $fh; 316 ok(-f $tmpfile4, "now $tmpfile4 leads to a file"); 317 unlink $tmpfile3; 318 319 SKIP: 320 { 321 my $listen = IO::Socket::UNIX->new(Local => $tmpfile3, Listen => 0) 322 or skip "Cannot create unix socket", 1; 323 ok(!-f $tmpfile4, "$tmpfile4 no longer leads to a file"); 324 ok(-S $tmpfile4, "now $tmpfile4 leads to a socket"); 325 ok(-S "$tmpfile1/file1", "$tmpfile1/file1 should lead to a socket"); 326 ok(-S $tmpfile2, "$tmpfile2 should lead to a socket"); 327 unlink $tmpfile3; 328 } 329 } 330 unlink $tmpfile2, $tmpfile4, "$tmpfile1/file1"; 331 rmdir $tmpfile1; 332} 333done_testing(); 334