Skip to content

Commit 9306248

Browse files
committed
Fix bug #72666: stat cache not cleared for plain paths
This adds more aggressive clearing of stat cache. It is added to the filestat as well as plain wrapper operations which covers stream file accessing as well as exec functions (using pipes). It should hopefully fix the most visible issues with the stat cache. Closes GH-17681
1 parent 3b4a58d commit 9306248

File tree

7 files changed

+158
-4
lines changed

7 files changed

+158
-4
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ PHP NEWS
6262
. Fixed bug GH-15902 (Core dumped in ext/reflection/php_reflection.c).
6363
(DanielEScherzer)
6464

65+
- Standard:
66+
. Fixed bug #72666 (stat cache clearing inconsistent between file:// paths
67+
and plain paths). (Jakub Zelenka)
68+
6569
- Streams:
6670
. Fixed bug GH-17650 (realloc with size 0 in user_filters.c). (nielsdos)
6771
. Fix memory leak on overflow in _php_stream_scandir(). (nielsdos)

ext/standard/filestat.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,9 @@ static void php_do_chgrp(INTERNAL_FUNCTION_PARAMETERS, int do_lchgrp) /* {{{ */
388388
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
389389
RETURN_FALSE;
390390
}
391+
392+
php_clear_stat_cache(0, NULL, 0);
393+
391394
RETURN_TRUE;
392395
#endif
393396
}
@@ -527,6 +530,9 @@ static void php_do_chown(INTERNAL_FUNCTION_PARAMETERS, int do_lchown) /* {{{ */
527530
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
528531
RETURN_FALSE;
529532
}
533+
534+
php_clear_stat_cache(0, NULL, 0);
535+
530536
RETURN_TRUE;
531537
#endif
532538
}
@@ -591,6 +597,9 @@ PHP_FUNCTION(chmod)
591597
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
592598
RETURN_FALSE;
593599
}
600+
601+
php_clear_stat_cache(0, NULL, 0);
602+
594603
RETURN_TRUE;
595604
}
596605
/* }}} */
@@ -676,6 +685,9 @@ PHP_FUNCTION(touch)
676685
php_error_docref(NULL, E_WARNING, "Utime failed: %s", strerror(errno));
677686
RETURN_FALSE;
678687
}
688+
689+
php_clear_stat_cache(0, NULL, 0);
690+
679691
RETURN_TRUE;
680692
}
681693
/* }}} */
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Bug #72666 (stat cache clearing inconsistent - touch)
3+
--FILE--
4+
<?php
5+
$filename = __DIR__ . '/bug72666_variation1.txt';
6+
7+
touch($filename);
8+
var_dump(filemtime($filename) > 2);
9+
touch($filename, 1);
10+
var_dump(filemtime($filename));
11+
12+
?>
13+
--CLEAN--
14+
<?php
15+
unlink(__DIR__ . '/bug72666_variation1.txt');
16+
?>
17+
--EXPECT--
18+
bool(true)
19+
int(1)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
Bug #72666 (stat cache clearing inconsistent - chgrp, chmod)
3+
--SKIPIF--
4+
<?php
5+
if (substr(PHP_OS, 0, 3) == 'WIN') die('skip no windows support');
6+
if (!function_exists("posix_getgid")) die("skip no posix_getgid()");
7+
?>
8+
--FILE--
9+
<?php
10+
$filename = __DIR__ . '/bug72666_variation2.txt';
11+
12+
$gid = posix_getgid();
13+
14+
var_dump(touch($filename));
15+
$ctime1 = filectime($filename);
16+
sleep(1);
17+
var_dump(chgrp($filename,$gid));
18+
$ctime2 = filectime($filename);
19+
sleep(1);
20+
var_dump(chmod($filename, 0777));
21+
$ctime3 = filectime($filename);
22+
23+
var_dump($ctime2 > $ctime1);
24+
var_dump($ctime3 > $ctime2);
25+
?>
26+
--CLEAN--
27+
<?php
28+
unlink(__DIR__ . '/bug72666_variation2.txt');
29+
?>
30+
--EXPECT--
31+
bool(true)
32+
bool(true)
33+
bool(true)
34+
bool(true)
35+
bool(true)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
Bug #72666 (stat cache clearing inconsistent - plain wrapper)
3+
--FILE--
4+
<?php
5+
$filename = __DIR__ . '/bug72666_variation3.txt';
6+
7+
file_put_contents($filename, "test");
8+
$fd = fopen($filename, "r");
9+
$atime1 = fileatime($filename);
10+
sleep(1);
11+
var_dump(fread($fd, 4));
12+
$atime2 = fileatime($filename);
13+
$mtime1 = filemtime($filename);
14+
fclose($fd);
15+
$fd = fopen($filename, "w");
16+
sleep(1);
17+
var_dump(fwrite($fd, "data"));
18+
$mtime2 = filemtime($filename);
19+
if (substr(PHP_OS, 0, 3) == 'WIN') {
20+
// Windows do not hundle atime
21+
var_dump($atime2 == $atime1);
22+
} else {
23+
var_dump($atime2 > $atime1);
24+
}
25+
var_dump($mtime2 > $mtime1);
26+
?>
27+
--CLEAN--
28+
<?php
29+
unlink(__DIR__ . '/bug72666_variation3.txt');
30+
?>
31+
--EXPECT--
32+
string(4) "test"
33+
int(4)
34+
bool(true)
35+
bool(true)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Bug #72666 (stat cache clearing inconsistent - exec)
3+
--FILE--
4+
<?php
5+
$filename = __DIR__ . '/bug72666_variation4.txt';
6+
7+
touch($filename, 1);
8+
var_dump(filemtime($filename));
9+
exec("touch $filename");
10+
var_dump(filemtime($filename) > 1);
11+
12+
13+
touch($filename, 1);
14+
var_dump(filemtime($filename));
15+
shell_exec("touch $filename");
16+
var_dump(filemtime($filename) > 1);
17+
?>
18+
--CLEAN--
19+
<?php
20+
unlink(__DIR__ . '/bug72666_variation4.txt');
21+
?>
22+
--EXPECT--
23+
int(1)
24+
bool(true)
25+
int(1)
26+
bool(true)

main/streams/plain_wrapper.c

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -349,14 +349,15 @@ PHPAPI php_stream *_php_stream_fopen_from_pipe(FILE *file, const char *mode STRE
349349
static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t count)
350350
{
351351
php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
352+
ssize_t bytes_written;
352353

353354
assert(data != NULL);
354355

355356
if (data->fd >= 0) {
356357
#ifdef PHP_WIN32
357-
ssize_t bytes_written = _write(data->fd, buf, PLAIN_WRAP_BUF_SIZE(count));
358+
bytes_written = _write(data->fd, buf, PLAIN_WRAP_BUF_SIZE(count));
358359
#else
359-
ssize_t bytes_written = write(data->fd, buf, count);
360+
bytes_written = write(data->fd, buf, count);
360361
#endif
361362
if (bytes_written < 0) {
362363
if (PHP_IS_TRANSIENT_ERROR(errno)) {
@@ -370,7 +371,6 @@ static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t coun
370371
php_error_docref(NULL, E_NOTICE, "Write of %zu bytes failed with errno=%d %s", count, errno, strerror(errno));
371372
}
372373
}
373-
return bytes_written;
374374
} else {
375375

376376
#ifdef HAVE_FLUSHIO
@@ -380,8 +380,15 @@ static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t coun
380380
data->last_op = 'w';
381381
#endif
382382

383-
return (ssize_t) fwrite(buf, 1, count, data->file);
383+
bytes_written = (ssize_t) fwrite(buf, 1, count, data->file);
384384
}
385+
386+
if (EG(active)) {
387+
/* clear stat cache as mtime and ctime got changed */
388+
php_clear_stat_cache(0, NULL, 0);
389+
}
390+
391+
return bytes_written;
385392
}
386393

387394
static ssize_t php_stdiop_read(php_stream *stream, char *buf, size_t count)
@@ -460,6 +467,12 @@ static ssize_t php_stdiop_read(php_stream *stream, char *buf, size_t count)
460467

461468
stream->eof = feof(data->file);
462469
}
470+
471+
if (EG(active)) {
472+
/* clear stat cache as atime got changed */
473+
php_clear_stat_cache(0, NULL, 0);
474+
}
475+
463476
return ret;
464477
}
465478

@@ -540,6 +553,10 @@ static int php_stdiop_flush(php_stream *stream)
540553
* something completely different.
541554
*/
542555
if (data->file) {
556+
if (EG(active)) {
557+
/* clear stat cache as there might be a write so mtime and ctime might have changed */
558+
php_clear_stat_cache(0, NULL, 0);
559+
}
543560
return fflush(data->file);
544561
}
545562
return 0;
@@ -1154,6 +1171,12 @@ PHPAPI php_stream *_php_stream_fopen(const char *filename, const char *mode, zen
11541171
ret = php_stream_fopen_from_fd_rel(fd, mode, persistent_id, (open_flags & O_APPEND) == 0);
11551172
}
11561173

1174+
if (EG(active)) {
1175+
/* clear stat cache as mtime and ctime might got changed - phar can use stream before
1176+
* cache is initialized so we need to check if the execution is active. */
1177+
php_clear_stat_cache(0, NULL, 0);
1178+
}
1179+
11571180
if (ret) {
11581181
if (opened_path) {
11591182
*opened_path = zend_string_init(realpath, strlen(realpath), 0);

0 commit comments

Comments
 (0)