From cce702ae30050923e4bad33e8dd02ed36f24ca2b Mon Sep 17 00:00:00 2001 From: Francois Laupretre Date: Sun, 4 Jan 2015 01:46:06 +0100 Subject: [PATCH] Add is_cacheable() streams operation --- UPGRADING | 1 + UPGRADING.INTERNALS | 22 +++++++ ext/opcache/ZendAccelerator.c | 3 +- ext/phar/stream.c | 14 ++++- ext/phar/stream.h | 1 + ext/phar/tests/phar_is_cacheable_001.phpt | 17 ++++++ ext/standard/basic_functions.c | 8 +++ ext/standard/file.c | 20 ++++++ ext/standard/file.h | 1 + .../http_url_is_not_cacheable_001.phpt | 17 ++++++ .../streams/plain_file_is_cacheable_001.phpt | 17 ++++++ main/php_streams.h | 6 ++ main/streams/plain_wrapper.c | 8 ++- main/streams/streams.c | 25 ++++++++ main/streams/userspace.c | 61 +++++++++++++++++-- 15 files changed, 213 insertions(+), 8 deletions(-) create mode 100644 ext/phar/tests/phar_is_cacheable_001.phpt create mode 100644 ext/standard/tests/streams/http_url_is_not_cacheable_001.phpt create mode 100644 ext/standard/tests/streams/plain_file_is_cacheable_001.phpt diff --git a/UPGRADING b/UPGRADING index 19bee705a2e76..7368c1270dd6a 100644 --- a/UPGRADING +++ b/UPGRADING @@ -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 diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 6e30f7cad8328..efc41707c119d 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -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 @@ -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 ======================== diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 51500a95ffda3..43a6e7717e176 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -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 diff --git a/ext/phar/stream.c b/ext/phar/stream.c index 13177cb91793c..0d54a57922ad7 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -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 = { @@ -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 diff --git a/ext/phar/stream.h b/ext/phar/stream.h index 2b637c0caf81a..67263209285ff 100644 --- a/ext/phar/stream.h +++ b/ext/phar/stream.h @@ -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); diff --git a/ext/phar/tests/phar_is_cacheable_001.phpt b/ext/phar/tests/phar_is_cacheable_001.phpt new file mode 100644 index 0000000000000..eda0bed012a5f --- /dev/null +++ b/ext/phar/tests/phar_is_cacheable_001.phpt @@ -0,0 +1,17 @@ +--TEST-- +Phar URI is cacheable +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +%s is cacheable diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index e9613717bee4b..59ebbad47fc01 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -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) @@ -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 }; diff --git a/ext/standard/file.c b/ext/standard/file.c index f523f9f183eef..6a59fa5b910c9 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -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 diff --git a/ext/standard/file.h b/ext/standard/file.h index f84dee354797e..be345e3f6d667 100644 --- a/ext/standard/file.h +++ b/ext/standard/file.h @@ -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); diff --git a/ext/standard/tests/streams/http_url_is_not_cacheable_001.phpt b/ext/standard/tests/streams/http_url_is_not_cacheable_001.phpt new file mode 100644 index 0000000000000..69cda3ee52553 --- /dev/null +++ b/ext/standard/tests/streams/http_url_is_not_cacheable_001.phpt @@ -0,0 +1,17 @@ +--TEST-- +HTTP URL is not cacheable +--FILE-- + +--EXPECTF-- +%s is not cacheable +%s is not cacheable diff --git a/ext/standard/tests/streams/plain_file_is_cacheable_001.phpt b/ext/standard/tests/streams/plain_file_is_cacheable_001.phpt new file mode 100644 index 0000000000000..7b5ab31cb9377 --- /dev/null +++ b/ext/standard/tests/streams/plain_file_is_cacheable_001.phpt @@ -0,0 +1,17 @@ +--TEST-- +Plain file is cacheable +--FILE-- + +--EXPECTF-- +%s is cacheable +%s is cacheable diff --git a/main/php_streams.h b/main/php_streams.h index e9da55f635740..2a6d82d1f51bc 100644 --- a/main/php_streams.h +++ b/main/php_streams.h @@ -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 { @@ -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() diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c index 066f7789a7a28..7dcd31fabac88 100644 --- a/main/streams/plain_wrapper.c +++ b/main/streams/plain_wrapper.c @@ -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, @@ -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 = { diff --git a/main/streams/streams.c b/main/streams/streams.c index d0f8a44b989e2..7c98f38cc93c9 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -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 diff --git a/main/streams/userspace.c b/main/streams/userspace.c index 9bbf5b0c4e57c..e119bfbe2831c 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -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); @@ -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 }; @@ -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: @@ -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) @@ -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); @@ -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); @@ -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); @@ -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) {