Add fsync() and fdatasync() functions

fsync is a straightforward wrapper around the same C function
(implemented on Windows API as _commit() with identical signature).

From the man pages:

    fsync() transfers ("flushes") all modified in-core data of (i.e.,
    modified buffer cache pages for) the file referred to by the file
    descriptor fd to the disk device (or other permanent storage
    device) so that all changed information can be retrieved even if
    the system crashes or is rebooted.  This includes writing through
    or flushing a disk cache if present.  The call blocks until the
    device reports that the transfer has completed.

RFC: https://wiki.php.net/rfc/fsync_function

Closes GH-6650.
This commit is contained in:
David Gebler 2021-04-13 16:04:29 +02:00 committed by Nikita Popov
parent 0c5711856f
commit cbcfd86026
9 changed files with 259 additions and 1 deletions

View File

@ -283,6 +283,11 @@ PHP 8.1 UPGRADE NOTES
. Added array_is_list(array $array), which will return true if the array keys are 0 .. count($array)-1 in that order.
RFC: https://wiki.php.net/rfc/is_list
- Standard:
. Added fsync() and fdatasync(), which instruct the operating system to
flush its buffers to physical storage.
RFC: https://wiki.php.net/rfc/fsync_function
========================================
7. New Classes and Interfaces
========================================

View File

@ -825,6 +825,12 @@ function ftell($stream): int|false {}
/** @param resource $stream */
function fflush($stream): bool {}
/** @param resource $stream */
function fsync($stream): bool {}
/** @param resource $stream */
function fdatasync($stream): bool {}
/** @param resource $stream */
function fwrite($stream, string $data, ?int $length = null): int|false {}

View File

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 469c5fbccef35aa6bd3893589495071d2250078f */
* Stub hash: 2d92e992837a61a052eea6d0257837aaccc54be6 */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0)
@ -1270,6 +1270,10 @@ ZEND_END_ARG_INFO()
#define arginfo_fflush arginfo_rewind
#define arginfo_fsync arginfo_rewind
#define arginfo_fdatasync arginfo_rewind
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_fwrite, 0, 2, MAY_BE_LONG|MAY_BE_FALSE)
ZEND_ARG_INFO(0, stream)
ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0)
@ -2562,6 +2566,8 @@ ZEND_FUNCTION(fstat);
ZEND_FUNCTION(fseek);
ZEND_FUNCTION(ftell);
ZEND_FUNCTION(fflush);
ZEND_FUNCTION(fsync);
ZEND_FUNCTION(fdatasync);
ZEND_FUNCTION(fwrite);
ZEND_FUNCTION(mkdir);
ZEND_FUNCTION(rename);
@ -3198,6 +3204,8 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(fseek, arginfo_fseek)
ZEND_FE(ftell, arginfo_ftell)
ZEND_FE(fflush, arginfo_fflush)
ZEND_FE(fsync, arginfo_fsync)
ZEND_FE(fdatasync, arginfo_fdatasync)
ZEND_FE(fwrite, arginfo_fwrite)
ZEND_FALIAS(fputs, fwrite, arginfo_fputs)
ZEND_FE(mkdir, arginfo_mkdir)

View File

@ -1464,6 +1464,44 @@ PHP_FUNCTION(unlink)
}
/* }}} */
PHP_FUNCTION(fsync)
{
zval *res;
php_stream *stream;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_RESOURCE(res)
ZEND_PARSE_PARAMETERS_END();
PHP_STREAM_TO_ZVAL(stream, res);
if (!php_stream_sync_supported(stream)) {
php_error_docref(NULL, E_WARNING, "Can't fsync this stream!");
RETURN_FALSE;
}
RETURN_BOOL(php_stream_sync(stream, /* data_only */ 0) == 0);
}
PHP_FUNCTION(fdatasync)
{
zval *res;
php_stream *stream;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_RESOURCE(res)
ZEND_PARSE_PARAMETERS_END();
PHP_STREAM_TO_ZVAL(stream, res);
if (!php_stream_sync_supported(stream)) {
php_error_docref(NULL, E_WARNING, "Can't fsync this stream!");
RETURN_FALSE;
}
RETURN_BOOL(php_stream_sync(stream, /* data_only */ 1) == 0);
}
/* {{{ Truncate file to 'size' length */
PHP_FUNCTION(ftruncate)
{

View File

@ -0,0 +1,73 @@
--TEST--
Test fdatasync() function: basic functionality
--FILE--
<?php
echo "*** Testing fdatasync(): writing to a file and reading the contents ***\n";
$data = <<<EOD
first line of string
second line of string
third line of string
EOD;
$file_path = __DIR__;
$filename = "$file_path/fdatasync_basic.tmp";
// opening a file
$file_handle = fopen($filename, "w");
if($file_handle == false)
exit("Error:failed to open file $filename");
if(PHP_OS_FAMILY == 'Windows') {
$data = str_replace("\r",'', $data);
}
// writing data to the file
var_dump( fwrite($file_handle, $data) );
var_dump( fdatasync($file_handle) );
var_dump( readfile($filename) );
echo "\n*** Testing fdatasync(): for return type ***\n";
$return_value = fdatasync($file_handle);
var_dump( is_bool($return_value) );
fclose($file_handle);
echo "\n*** Testing fdatasync(): attempting to sync stdin ***\n";
$file_handle = fopen("php://stdin", "w");
var_dump(fdatasync($file_handle));
fclose($file_handle);
echo "\n*** Testing fdatasync(): for non-file stream ***\n";
$file_handle = fopen("php://memory", "w");
$return_value = fdatasync($file_handle);
var_dump( ($return_value) );
fclose($file_handle);
echo "\n*** Done ***";
?>
--CLEAN--
<?php
$file_path = __DIR__;
$filename = "$file_path/fdatasync_basic.tmp";
unlink($filename);
?>
--EXPECTF--
*** Testing fdatasync(): writing to a file and reading the contents ***
int(63)
bool(true)
first line of string
second line of string
third line of stringint(63)
*** Testing fdatasync(): for return type ***
bool(true)
*** Testing fdatasync(): attempting to sync stdin ***
bool(false)
*** Testing fdatasync(): for non-file stream ***
Warning: fdatasync(): Can't fsync this stream! in %s on line %d
bool(false)
*** Done ***

View File

@ -0,0 +1,73 @@
--TEST--
Test fsync() function: basic functionality
--FILE--
<?php
echo "*** Testing fsync(): writing to a file and reading the contents ***\n";
$data = <<<EOD
first line of string
second line of string
third line of string
EOD;
$file_path = __DIR__;
$filename = "$file_path/fsync_basic.tmp";
// opening a file
$file_handle = fopen($filename, "w");
if($file_handle == false)
exit("Error:failed to open file $filename");
if(PHP_OS_FAMILY == 'Windows') {
$data = str_replace("\r",'', $data);
}
// writing data to the file
var_dump( fwrite($file_handle, $data) );
var_dump( fsync($file_handle) );
var_dump( readfile($filename) );
echo "\n*** Testing fsync(): for return type ***\n";
$return_value = fsync($file_handle);
var_dump( is_bool($return_value) );
fclose($file_handle);
echo "\n*** Testing fsync(): attempting to sync stdin ***\n";
$file_handle = fopen("php://stdin", "w");
var_dump(fsync($file_handle));
fclose($file_handle);
echo "\n*** Testing fsync(): for non-file stream ***\n";
$file_handle = fopen("php://memory", "w");
$return_value = fsync($file_handle);
var_dump( ($return_value) );
fclose($file_handle);
echo "\n*** Done ***";
?>
--CLEAN--
<?php
$file_path = __DIR__;
$filename = "$file_path/fsync_basic.tmp";
unlink($filename);
?>
--EXPECTF--
*** Testing fsync(): writing to a file and reading the contents ***
int(63)
bool(true)
first line of string
second line of string
third line of stringint(63)
*** Testing fsync(): for return type ***
bool(true)
*** Testing fsync(): attempting to sync stdin ***
bool(false)
*** Testing fsync(): for non-file stream ***
Warning: fsync(): Can't fsync this stream! in %s on line %d
bool(false)
*** Done ***

View File

@ -334,6 +334,9 @@ PHPAPI int _php_stream_putc(php_stream *stream, int c);
PHPAPI int _php_stream_flush(php_stream *stream, int closing);
#define php_stream_flush(stream) _php_stream_flush((stream), 0)
PHPAPI int _php_stream_sync(php_stream *stream, bool data_only);
#define php_stream_sync(stream, d) _php_stream_sync((stream), (d))
PHPAPI char *_php_stream_get_line(php_stream *stream, char *buf, size_t maxlen, size_t *returned_len);
#define php_stream_gets(stream, buf, maxlen) _php_stream_get_line((stream), (buf), (maxlen), NULL)
@ -442,6 +445,15 @@ END_EXTERN_C()
/* Enable/disable blocking reads on anonymous pipes on Windows. */
#define PHP_STREAM_OPTION_PIPE_BLOCKING 13
/* Stream can support fsync operation */
#define PHP_STREAM_OPTION_SYNC_API 14
#define PHP_STREAM_SYNC_SUPPORTED 0
#define PHP_STREAM_SYNC_FSYNC 1
#define PHP_STREAM_SYNC_FDSYNC 2
#define php_stream_sync_supported(stream) (_php_stream_set_option((stream), PHP_STREAM_OPTION_SYNC_API, PHP_STREAM_SYNC_SUPPORTED, NULL) == PHP_STREAM_OPTION_RETURN_OK ? 1 : 0)
#define PHP_STREAM_OPTION_RETURN_OK 0 /* option set OK */
#define PHP_STREAM_OPTION_RETURN_ERR -1 /* problem setting option */
#define PHP_STREAM_OPTION_RETURN_NOTIMPL -2 /* underlying stream does not implement; streams can handle it instead */

View File

@ -54,6 +54,8 @@ extern int php_get_gid_by_name(const char *name, gid_t *gid);
#if defined(PHP_WIN32)
# define PLAIN_WRAP_BUF_SIZE(st) (((st) > UINT_MAX) ? UINT_MAX : (unsigned int)(st))
#define fsync _commit
#define fdatasync fsync
#else
# define PLAIN_WRAP_BUF_SIZE(st) (st)
#endif
@ -537,6 +539,28 @@ static int php_stdiop_flush(php_stream *stream)
return 0;
}
static int php_stdiop_sync(php_stream *stream, bool dataonly)
{
php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
FILE *fp;
int fd;
if (php_stream_cast(stream, PHP_STREAM_AS_STDIO, (void**)&fp, REPORT_ERRORS) == FAILURE) {
return -1;
}
if (php_stdiop_flush(stream) == 0) {
PHP_STDIOP_GET_FD(fd, data);
if (dataonly) {
return fdatasync(fd);
} else {
return fsync(fd);
}
}
return -1;
}
static int php_stdiop_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset)
{
php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
@ -885,6 +909,16 @@ static int php_stdiop_set_option(php_stream *stream, int option, int value, void
#endif
return PHP_STREAM_OPTION_RETURN_NOTIMPL;
case PHP_STREAM_OPTION_SYNC_API:
switch (value) {
case PHP_STREAM_SYNC_SUPPORTED:
return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK;
case PHP_STREAM_SYNC_FSYNC:
return php_stdiop_sync(stream, 0) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
case PHP_STREAM_SYNC_FDSYNC:
return php_stdiop_sync(stream, 1) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR;
}
case PHP_STREAM_OPTION_TRUNCATE_API:
switch (value) {
case PHP_STREAM_TRUNCATE_SUPPORTED:

View File

@ -1406,6 +1406,15 @@ PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, voi
return ret;
}
PHPAPI int _php_stream_sync(php_stream *stream, bool data_only)
{
int op = PHP_STREAM_SYNC_FSYNC;
if (data_only) {
op = PHP_STREAM_SYNC_FDSYNC;
}
return php_stream_set_option(stream, PHP_STREAM_OPTION_SYNC_API, op, NULL);
}
PHPAPI int _php_stream_truncate_set_size(php_stream *stream, size_t newsize)
{
return php_stream_set_option(stream, PHP_STREAM_OPTION_TRUNCATE_API, PHP_STREAM_TRUNCATE_SET_SIZE, &newsize);