Skip to content

Add is_cacheable() stream wrapper operation #976

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ PHP 7.1 UPGRADE NOTES
- Core
. Added void return type, which requires that a function not return a value.
(RFC: https://wiki.php.net/rfc/void_return_type)
. Added is_cacheable() streams operation.

========================================
3. Changes in SAPI modules
Expand Down
22 changes: 22 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ PHP 7.1 INTERNALS UPGRADE NOTES

0. Wiki Examples
1. Internal API changes
q. New streams operations

2. Build system changes
a. Unix build system changes
Expand All @@ -21,6 +22,27 @@ changes. See: https://wiki.php.net/phpng-upgrading
1. Internal API changes
========================

q. New streams operation

is_cacheable()

An element named 'stream_is_cacheable' is added after 'stream_metadata' in the
php_stream_wrapper_ops structure. This is an optional pointer to a function
able to determine if a given URI is cacheable or not. This function receives
an URI, an options bitfield, and an optional context. It returns a value
different of 0 if the URI is cacheable. If the 'stream_is_cacheable' element
is null, every URI for this wrapper are considered as non-cacheable.
A new C function named php_stream_is_cacheable(const char *path, int options, php_stream_context *context)
is defined. It determines the right wrapper from the path it receives and
forwards the request to the corresponding stream_is_cacheable() function,
if it exists. If the stream_is_cacheable() element is not defined, 0 is
returned.
Userspace stream wrappers can define a method named is_cacheable(path [,options]).
This method determines if the input path is cacheable and returns true or
false. If the method is not defined, every path for this wrapper are non-cacheable.
For completeness, a new PHP function named file_is_cacheable(path [, options [, context]])
is defined. It allows to determine from a PHP script whether a path is cacheable.

========================
2. Build system changes
========================
Expand Down
3 changes: 1 addition & 2 deletions ext/opcache/ZendAccelerator.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,7 @@ static inline int is_stream_path(const char *filename)

static inline int is_cacheable_stream_path(const char *filename)
{
return memcmp(filename, "file://", sizeof("file://") - 1) == 0 ||
memcmp(filename, "phar://", sizeof("phar://") - 1) == 0;
return php_stream_is_cacheable(filename, 0, NULL);
}

/* O+ overrides PHP chdir() function and remembers the current working directory
Expand Down
14 changes: 13 additions & 1 deletion ext/phar/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ php_stream_wrapper_ops phar_stream_wops = {
phar_wrapper_unlink, /* unlink */
phar_wrapper_rename, /* rename */
phar_wrapper_mkdir, /* create directory */
phar_wrapper_rmdir, /* remove directory */
phar_wrapper_rmdir, /* remove directory */
NULL, /* Metadata handling */
phar_wrapper_is_cacheable /* is_cacheable */
};

php_stream_wrapper php_stream_phar_wrapper = {
Expand Down Expand Up @@ -984,6 +986,16 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
}
/* }}} */

/**
* Called by opcode cache to know if they can cache an URL
*/
static int phar_wrapper_is_cacheable(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context) /* {{{ */
{
/* Phar URLs are always cacheable */
return 1;
}
/* }}} */

/*
* Local variables:
* tab-width: 4
Expand Down
1 change: 1 addition & 0 deletions ext/phar/stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ static php_stream* phar_wrapper_open_url(php_stream_wrapper *wrapper, const char
static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context);
static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context);
static int phar_wrapper_stat(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context);
static int phar_wrapper_is_cacheable(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context);

/* file/stream handlers */
static size_t phar_stream_write(php_stream *stream, const char *buf, size_t count);
Expand Down
17 changes: 17 additions & 0 deletions ext/phar/tests/phar_is_cacheable_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Phar URI is cacheable
--SKIPIF--
<?php if (!extension_loaded("phar")) die("skip"); ?>
--FILE--
<?php
function check_cacheable($path)
{
$ret=file_is_cacheable($path);
echo "$path is ".($ret ? '' : 'not ')."cacheable\n";
}
//-----------

check_cacheable('phar:///Foo/Bar/Moo.phar.php');
?>
--EXPECTF--
%s is cacheable
8 changes: 8 additions & 0 deletions ext/standard/basic_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,13 @@ ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO(arginfo_sys_get_temp_dir, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_file_is_cacheable, 0, 0, 1)
ZEND_ARG_INFO(0, filename)
ZEND_ARG_INFO(0, options)
ZEND_ARG_INFO(0, context)
ZEND_END_ARG_INFO()

/* }}} */
/* {{{ filestat.c */
ZEND_BEGIN_ARG_INFO(arginfo_disk_total_space, 0)
Expand Down Expand Up @@ -3371,6 +3378,7 @@ const zend_function_entry basic_functions[] = { /* {{{ */
PHP_FE(output_reset_rewrite_vars, arginfo_output_reset_rewrite_vars)

PHP_FE(sys_get_temp_dir, arginfo_sys_get_temp_dir)
PHP_FE(file_is_cacheable, arginfo_file_is_cacheable)

PHP_FE_END
};
Expand Down
20 changes: 20 additions & 0 deletions ext/standard/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -2519,6 +2519,26 @@ PHP_FUNCTION(sys_get_temp_dir)
}
/* }}} */

/* {{{ proto bool file_is_cacheable(string filename [, int options [, resource context]])
Determine if a path can be cached */
PHP_FUNCTION(file_is_cacheable)
{
char *filename;
size_t filename_len;
php_stream *stream;
zend_long options = 0;
zval *zcontext = NULL;

/* Parse arguments */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|lr!", &filename, &filename_len
, &options, &zcontext) == FAILURE) {
return;
}

RETURN_BOOL(php_stream_is_cacheable(filename, options, php_stream_context_from_zval(zcontext, 0)));
}
/* }}} */

/*
* Local variables:
* tab-width: 4
Expand Down
1 change: 1 addition & 0 deletions ext/standard/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ PHP_FUNCTION(fnmatch);
PHP_NAMED_FUNCTION(php_if_ftruncate);
PHP_NAMED_FUNCTION(php_if_fstat);
PHP_FUNCTION(sys_get_temp_dir);
PHP_FUNCTION(file_is_cacheable);

PHP_MINIT_FUNCTION(user_streams);

Expand Down
17 changes: 17 additions & 0 deletions ext/standard/tests/streams/http_url_is_not_cacheable_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
HTTP URL is not cacheable
--FILE--
<?php
function check_cacheable($path)
{
$ret=file_is_cacheable($path);
echo "$path is ".($ret ? '' : 'not ')."cacheable\n";
}
//-----------

check_cacheable('http://acme.com/Foo.php');
check_cacheable('https://acme.com/Foo.php');
?>
--EXPECTF--
%s is not cacheable
%s is not cacheable
17 changes: 17 additions & 0 deletions ext/standard/tests/streams/plain_file_is_cacheable_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Plain file is cacheable
--FILE--
<?php
function check_cacheable($path)
{
$ret=file_is_cacheable($path);
echo "$path is ".($ret ? '' : 'not ')."cacheable\n";
}
//-----------

check_cacheable('/Foo/Bar/Moo.php');
check_cacheable('file:///Foo/Bar/Moo.php');
?>
--EXPECTF--
%s is cacheable
%s is cacheable
6 changes: 6 additions & 0 deletions main/php_streams.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ typedef struct _php_stream_wrapper_ops {
int (*stream_rmdir)(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context);
/* Metadata handling */
int (*stream_metadata)(php_stream_wrapper *wrapper, const char *url, int options, void *value, php_stream_context *context);
/* Asks whether a path is cacheable */
int (*stream_is_cacheable)(php_stream_wrapper *wrapper, const char *url,
int options, php_stream_context *context);
} php_stream_wrapper_ops;

struct _php_stream_wrapper {
Expand Down Expand Up @@ -374,6 +377,9 @@ PHPAPI int _php_stream_scandir(const char *dirname, zend_string **namelist[], in
PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, void *ptrparam);
#define php_stream_set_option(stream, option, value, ptrvalue) _php_stream_set_option((stream), (option), (value), (ptrvalue))

PHPAPI int _php_stream_is_cacheable(const char *path, int options, php_stream_context *context);
#define php_stream_is_cacheable(path, options, context) _php_stream_is_cacheable((path), (options), (context))

#define php_stream_set_chunk_size(stream, size) _php_stream_set_option((stream), PHP_STREAM_OPTION_SET_CHUNK_SIZE, (size), NULL)

END_EXTERN_C()
Expand Down
8 changes: 7 additions & 1 deletion main/streams/plain_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -1386,6 +1386,11 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url
return 1;
}

static int php_plain_files_is_cacheable(php_stream_wrapper *wrapper, const char *url, int option, php_stream_context *context)
{
/* Plain files are always cacheable */
return 1;
}

static php_stream_wrapper_ops php_plain_files_wrapper_ops = {
php_plain_files_stream_opener,
Expand All @@ -1398,7 +1403,8 @@ static php_stream_wrapper_ops php_plain_files_wrapper_ops = {
php_plain_files_rename,
php_plain_files_mkdir,
php_plain_files_rmdir,
php_plain_files_metadata
php_plain_files_metadata,
php_plain_files_is_cacheable
};

PHPAPI php_stream_wrapper php_plain_files_wrapper = {
Expand Down
25 changes: 25 additions & 0 deletions main/streams/streams.c
Original file line number Diff line number Diff line change
Expand Up @@ -2324,6 +2324,31 @@ PHPAPI int _php_stream_scandir(const char *dirname, zend_string **namelist[], in
}
/* }}} */

/* {{{ php_stream_is_cacheable
*/
PHPAPI int _php_stream_is_cacheable(const char *path, int options, php_stream_context *context)
{
php_stream_wrapper *wrapper = NULL;
int ret;

if (!path || !*path) {
return 0;
}

wrapper = php_stream_locate_url_wrapper(path, NULL, options);

if (wrapper && wrapper->wops->stream_is_cacheable) {
ret = wrapper->wops->stream_is_cacheable(wrapper,
path, options, context);

} else {
ret = 0;
}

return ret;
}
/* }}} */

/*
* Local variables:
* tab-width: 4
Expand Down
61 changes: 57 additions & 4 deletions main/streams/userspace.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context);
static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context);
static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, int option, void *value, php_stream_context *context);
static int user_wrapper_is_cacheable(php_stream_wrapper *wrapper, const char *url, int option, php_stream_context *context);
static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char *filename, const char *mode,
int options, zend_string **opened_path, php_stream_context *context STREAMS_DC);

Expand All @@ -66,7 +67,8 @@ static php_stream_wrapper_ops user_stream_wops = {
user_wrapper_rename,
user_wrapper_mkdir,
user_wrapper_rmdir,
user_wrapper_metadata
user_wrapper_metadata,
user_wrapper_is_cacheable
};


Expand Down Expand Up @@ -148,6 +150,7 @@ typedef struct _php_userstream_data php_userstream_data_t;
#define USERSTREAM_SET_OPTION "stream_set_option"
#define USERSTREAM_TRUNCATE "stream_truncate"
#define USERSTREAM_METADATA "stream_metadata"
#define USERSTREAM_IS_CACHEABLE "is_cacheable"

/* {{{ class should have methods like these:

Expand Down Expand Up @@ -279,6 +282,11 @@ typedef struct _php_userstream_data php_userstream_data_t;
return true / false;
}

function is_cacheable(string $path, int $flags)
{
return true / false;
}

}}} **/

static void user_stream_create_object(struct php_user_stream_wrapper *uwrap, php_stream_context *context, zval *object)
Expand Down Expand Up @@ -364,7 +372,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *
return NULL;
}

/* call it's stream_open method - set up params first */
/* call its stream_open method - set up params first */
ZVAL_STRING(&args[0], filename);
ZVAL_STRING(&args[1], mode);
ZVAL_LONG(&args[2], options);
Expand Down Expand Up @@ -441,7 +449,7 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char
return NULL;
}

/* call it's dir_open method - set up params first */
/* call its dir_open method - set up params first */
ZVAL_STRING(&args[0], filename);
ZVAL_LONG(&args[1], options);

Expand Down Expand Up @@ -1362,7 +1370,7 @@ static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, i
return ret;
}

/* call it's stat_url method - set up params first */
/* call its stat_url method - set up params first */
ZVAL_STRING(&args[0], url);
ZVAL_LONG(&args[1], flags);

Expand Down Expand Up @@ -1397,6 +1405,51 @@ static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, i
return ret;

}
static int user_wrapper_is_cacheable(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_context *context)
{
struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
zval zfuncname, zretval;
zval args[2];
int call_result;
zval object;
int ret;

/* create an instance of our class */
user_stream_create_object(uwrap, context, &object);
if (Z_TYPE(object) == IS_UNDEF) {
return 0;
}

/* call its is_cacheable method - set up params first */
ZVAL_STRING(&args[0], url);
ZVAL_LONG(&args[1], flags);

ZVAL_STRING(&zfuncname, USERSTREAM_IS_CACHEABLE);

call_result = call_user_function_ex(NULL,
&object,
&zfuncname,
&zretval,
2, args,
0, NULL );

if (call_result == SUCCESS) {
ret = zend_is_true(&zretval);
} else {
/* No error if method is not implemented */
ret=0;
}

/* clean up */
zval_ptr_dtor(&object);
zval_ptr_dtor(&zretval);

zval_ptr_dtor(&zfuncname);
zval_ptr_dtor(&args[1]);
zval_ptr_dtor(&args[0]);

return ret;
}

static size_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t count)
{
Expand Down