diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f58412..41c6efd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,10 +13,16 @@ jobs: build: name: PHP ${{ matrix.php }} runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental || false }} strategy: fail-fast: false matrix: php: ['8.1', '8.2', '8.3', '8.4', '8.5'] + include: + # nightly build of php-src master; allowed to fail so an unrelated + # upstream break does not gate pull requests + - php: '8.6' + experimental: true steps: - name: Checkout @@ -25,7 +31,7 @@ jobs: - name: Install librrd run: | sudo apt-get update - sudo apt-get install -y librrd-dev pkg-config + sudo apt-get install -y librrd-dev pkg-config rrdtool - name: Setup PHP uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.37.2 @@ -40,6 +46,9 @@ jobs: ./configure --with-rrd make -j"$(nproc)" + - name: Generate test fixtures + run: make -C tests/data all + - name: Test run: make test env: diff --git a/.gitignore b/.gitignore index 1f91b0f..8cd5787 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,19 @@ modules run-tests.php tmp-php.ini tests/rrdtool-bin.inc +*.dep +configure.ac +tests/*.diff +tests/*.exp +tests/*.log +tests/*.out +tests/*.php +tests/*.sh +tests/*.txt +tests/*.png +tests/*.rrd +tests/data/*.png +tests/data/*.rrd +tests/data/*.txt +tests/data/*.xml +tests/data/Makefile diff --git a/rrd.c b/rrd.c index ff51776..96d65cb 100644 --- a/rrd.c +++ b/rrd.c @@ -61,6 +61,7 @@ PHP_FUNCTION(rrd_fetch) time_t start, end; unsigned long step, ds_cnt; /* count of data sources */ + unsigned ds_counter; char **ds_namv; /* list of data source names */ rrd_value_t *ds_data; /* all data from all sources */ @@ -95,11 +96,11 @@ PHP_FUNCTION(rrd_fetch) /* add "ds_namv" and "data" array into return values if there is any * result data */ - if (!ds_data || !ds_namv || !ds_cnt) { + if (!ds_data || !ds_namv || !ds_cnt || !step) { add_assoc_null(return_value, "data"); } else { rrd_value_t *datap = ds_data; - unsigned timestamp, ds_counter; + time_t timestamp; /* final array for all data from all data sources */ zval zv_data_array; @@ -124,20 +125,26 @@ PHP_FUNCTION(rrd_fetch) /* pointer for one data source retrieved data */ zval *ds_data_array; /* value for key (timestamp) in data array */ - char str_timestamp[11]; - ZEND_LTOA((zend_ulong)timestamp, str_timestamp, sizeof(str_timestamp)); + /* time_t is wider than zend_long on 32-bit builds, so + this cannot go through ZEND_LONG_FMT */ + char str_timestamp[24]; + snprintf(str_timestamp, sizeof(str_timestamp), + "%" PRId64, (int64_t)timestamp); - /* gets pointer for data source result array */ + /* gets pointer for data source result array */ ds_data_array = zend_hash_get_current_data(Z_ARRVAL(zv_data_array)); + if (!ds_data_array) break; add_assoc_double(ds_data_array, str_timestamp, *(datap++)); zend_hash_move_forward(Z_ARRVAL(zv_data_array)); } } add_assoc_zval(return_value, "data", &zv_data_array); + } - /* free data from rrd_fetch */ - free(ds_data); + /* free data from rrd_fetch */ + free(ds_data); + if (ds_namv) { for (ds_counter = 0; ds_counter < ds_cnt; ds_counter++) { free(ds_namv[ds_counter]); } @@ -334,7 +341,7 @@ PHP_FUNCTION(rrd_restore) argv = rrd_args_init_by_phparray("restore", xml_filename, &zv_options); if (!argv) { zend_error(E_WARNING, "cannot allocate arguments options"); - zval_dtor(&zv_options); + zval_ptr_dtor_nogc(&zv_options); RETURN_FALSE; } @@ -346,7 +353,7 @@ PHP_FUNCTION(rrd_restore) } else { RETVAL_TRUE; } - zval_dtor(&zv_options); + zval_ptr_dtor_nogc(&zv_options); rrd_args_free(argv); } /* }}} */ @@ -436,8 +443,15 @@ PHP_FUNCTION(rrd_xport) add_assoc_long(return_value, "step", step); /* no data available */ - if (!data) { + if (!data || !step) { add_assoc_null(return_value, "data"); + if (legend_v) { + for (outvar_index = 0; outvar_index < outvar_count; outvar_index++) { + free(legend_v[outvar_index]); + } + free(legend_v); + } + free(data); return; } @@ -463,8 +477,9 @@ PHP_FUNCTION(rrd_xport) data_ptr = data + outvar_index; for (time_index = start + step; time_index <= end; time_index += step) { /* value for key (timestamp) in data array */ - char str_timestamp[11]; - ZEND_LTOA((zend_ulong)time_index, str_timestamp, sizeof(str_timestamp)); + char str_timestamp[24]; + snprintf(str_timestamp, sizeof(str_timestamp), + "%" PRId64, (int64_t)time_index); add_assoc_double(&time_data, str_timestamp, *data_ptr); data_ptr += outvar_count; @@ -664,13 +679,21 @@ rrd_args *rrd_args_init_by_phparray(const char *command_name, const char *filena zend_hash_internal_pointer_reset(Z_ARRVAL_P(options)); for (i=0; i < option_count; i++) { zval *item; + zend_string *item_str; smart_string option = {0}; /* one argument option */ /* force using strings as array items */ item = zend_hash_get_current_data(Z_ARRVAL_P(options)); - if (Z_TYPE_P(item) != IS_STRING) convert_to_string(item); - smart_string_appendl(&option, Z_STRVAL_P(item), Z_STRLEN_P(item)); + item_str = zval_try_get_string(item); + if (!item_str) { + smart_string_free(&option); + result->count = args_counter; + rrd_args_free(result); + return NULL; + } + smart_string_appendl(&option, ZSTR_VAL(item_str), ZSTR_LEN(item_str)); smart_string_0(&option); + zend_string_release(item_str); result->args[args_counter++] = estrdup(option.c); smart_string_free(&option); diff --git a/rrd_create.c b/rrd_create.c index 94cf16b..df20e1b 100644 --- a/rrd_create.c +++ b/rrd_create.c @@ -47,7 +47,7 @@ typedef struct _rrd_create_object { * fetch our custom object from user space object */ static inline rrd_create_object *php_rrd_create_fetch_object(zend_object *obj) { - return (rrd_create_object *)((char*)(obj) - XtOffsetOf(rrd_create_object, std)); + return (rrd_create_object *)((char*)(obj) - offsetof(rrd_create_object, std)); } /* {{{ rrd_create_object_dtor @@ -63,11 +63,11 @@ static void rrd_create_object_dtor(zend_object *object) if (intern_obj->start_time) efree(intern_obj->start_time); if (!Z_ISUNDEF(intern_obj->zv_step)) - zval_dtor(&intern_obj->zv_step); + zval_ptr_dtor_nogc(&intern_obj->zv_step); if (!Z_ISUNDEF(intern_obj->zv_arr_data_sources)) - zval_dtor(&intern_obj->zv_arr_data_sources); + zval_ptr_dtor_nogc(&intern_obj->zv_arr_data_sources); if (!Z_ISUNDEF(intern_obj->zv_arr_archives)) - zval_dtor(&intern_obj->zv_arr_archives); + zval_ptr_dtor_nogc(&intern_obj->zv_arr_archives); zend_object_std_dtor(&intern_obj->std); } @@ -104,7 +104,7 @@ PHP_METHOD(RRDCreator, __construct) rrd_create_object *intern_obj; char *path; size_t path_length; /* better to set defaults for optional parameters */ - zend_string *start_time; + zend_string *start_time = NULL; long step = 0; int argc = ZEND_NUM_ARGS(); @@ -131,8 +131,27 @@ PHP_METHOD(RRDCreator, __construct) } intern_obj = php_rrd_create_fetch_object(Z_OBJ_P(getThis())); + if (intern_obj->file_path) efree(intern_obj->file_path); + intern_obj->file_path = NULL; + if (intern_obj->start_time) efree(intern_obj->start_time); + intern_obj->start_time = NULL; + + /* a second __construct otherwise inherits the previous step and sources */ + if (!Z_ISUNDEF(intern_obj->zv_step)) { + zval_ptr_dtor_nogc(&intern_obj->zv_step); + ZVAL_UNDEF(&intern_obj->zv_step); + } + if (!Z_ISUNDEF(intern_obj->zv_arr_data_sources)) { + zval_ptr_dtor_nogc(&intern_obj->zv_arr_data_sources); + ZVAL_UNDEF(&intern_obj->zv_arr_data_sources); + } + if (!Z_ISUNDEF(intern_obj->zv_arr_archives)) { + zval_ptr_dtor_nogc(&intern_obj->zv_arr_archives); + ZVAL_UNDEF(&intern_obj->zv_arr_archives); + } + intern_obj->file_path = estrdup(path); - if (start_time) intern_obj->start_time = estrdup(ZSTR_VAL(start_time)); + if (start_time) intern_obj->start_time = estrndup(ZSTR_VAL(start_time), ZSTR_LEN(start_time)); if (step) { ZVAL_LONG(&intern_obj->zv_step, step); } @@ -218,6 +237,15 @@ PHP_METHOD(RRDCreator, save) zval zv_create_argv; rrd_args *create_argv; + if (!intern_obj->file_path) { + zend_throw_exception(NULL, "the object was not constructed", 0); + return; + } + + if (php_check_open_basedir(intern_obj->file_path)) { + RETURN_FALSE; + } + array_init(&zv_create_argv); if (intern_obj->start_time) { @@ -249,13 +277,17 @@ PHP_METHOD(RRDCreator, save) } /* add array of archive and data source strings into argument list */ - php_array_merge(Z_ARRVAL(zv_create_argv), Z_ARRVAL(intern_obj->zv_arr_data_sources)); - php_array_merge(Z_ARRVAL(zv_create_argv), Z_ARRVAL(intern_obj->zv_arr_archives)); + if (!Z_ISUNDEF(intern_obj->zv_arr_data_sources)) { + php_array_merge(Z_ARRVAL(zv_create_argv), Z_ARRVAL(intern_obj->zv_arr_data_sources)); + } + if (!Z_ISUNDEF(intern_obj->zv_arr_archives)) { + php_array_merge(Z_ARRVAL(zv_create_argv), Z_ARRVAL(intern_obj->zv_arr_archives)); + } create_argv = rrd_args_init_by_phparray("create", intern_obj->file_path, &zv_create_argv); if (!create_argv) { zend_error(E_WARNING, "cannot allocate arguments options"); - zval_dtor(&zv_create_argv); + zval_ptr_dtor_nogc(&zv_create_argv); RETURN_FALSE; } @@ -263,7 +295,7 @@ PHP_METHOD(RRDCreator, save) /* call rrd_create and test if fails */ if (rrd_create(create_argv->count - 1, &create_argv->args[1]) == -1) { - zval_dtor(&zv_create_argv); + zval_ptr_dtor_nogc(&zv_create_argv); rrd_args_free(create_argv); /* throw exception with rrd error string */ @@ -272,7 +304,7 @@ PHP_METHOD(RRDCreator, save) return; } - zval_dtor(&zv_create_argv); + zval_ptr_dtor_nogc(&zv_create_argv); rrd_args_free(create_argv); RETURN_TRUE; } @@ -345,6 +377,6 @@ void rrd_create_minit() memcpy(&rrd_create_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); rrd_create_handlers.clone_obj = NULL; - rrd_create_handlers.offset = XtOffsetOf(rrd_create_object, std); + rrd_create_handlers.offset = offsetof(rrd_create_object, std); rrd_create_handlers.free_obj = rrd_create_object_dtor; } diff --git a/rrd_graph.c b/rrd_graph.c index 0e43550..30b68b1 100644 --- a/rrd_graph.c +++ b/rrd_graph.c @@ -45,7 +45,7 @@ typedef struct _rrd_graph_object { * fetch our custom object from user space object */ static inline rrd_graph_object *php_rrd_graph_fetch_object(zend_object *obj) { - return (rrd_graph_object *)((char*)(obj) - XtOffsetOf(rrd_graph_object, std)); + return (rrd_graph_object *)((char*)(obj) - offsetof(rrd_graph_object, std)); } /* {{{ rrd_graph_object_dtor @@ -60,7 +60,7 @@ static void rrd_graph_object_dtor(zend_object *object) efree(intern_obj->file_path); } if (!Z_ISUNDEF(intern_obj->zv_arr_options)) { - zval_dtor(&intern_obj->zv_arr_options); + zval_ptr_dtor_nogc(&intern_obj->zv_arr_options); } zend_object_std_dtor(&intern_obj->std); @@ -100,7 +100,9 @@ PHP_METHOD(RRDGraph, __construct) } intern_obj = php_rrd_graph_fetch_object(Z_OBJ_P(getThis())); - intern_obj->file_path = estrdup(path); + if (intern_obj->file_path) efree(intern_obj->file_path); + intern_obj->file_path = NULL; + intern_obj->file_path = estrndup(path, path_length); } /* }}} */ @@ -120,7 +122,7 @@ PHP_METHOD(RRDGraph, setOptions) /* if our array is initialized, so delete it first */ if (!Z_ISUNDEF(intern_obj->zv_arr_options)) { - zval_dtor(&intern_obj->zv_arr_options); + zval_ptr_dtor_nogc(&intern_obj->zv_arr_options); } /* copy array from parameter */ @@ -131,7 +133,8 @@ PHP_METHOD(RRDGraph, setOptions) /* {{{ creates arguments for rrd_graph call for RRDGraph instance options */ -static rrd_args *rrd_graph_obj_create_argv(const char *command_name, const rrd_graph_object *obj) +static rrd_args *rrd_graph_obj_create_argv(const char *command_name, + const rrd_graph_object *obj, const char *file_path) { /* iterated item and keys*/ zval *zv_option_val; @@ -140,12 +143,18 @@ static rrd_args *rrd_graph_obj_create_argv(const char *command_name, const rrd_g /* arguments for rrd_graph call as php array - temporary storage */ zval zv_argv; rrd_args *result; + /* converting a value runs __toString, and setOptions() from there would + * free the array this loop is walking + */ + HashTable *ht = Z_ARRVAL(obj->zv_arr_options); + GC_ADDREF(ht); array_init(&zv_argv); - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(obj->zv_arr_options), num_key, zs_key, zv_option_val) { + ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, zs_key, zv_option_val) { (void)num_key; /* to avoid -Wunused-but-set-variable */ smart_string option = {0}; /* one argument option */ + zend_string *option_str; /* option with string key means long option, hence they are used as * "key=value" e.g. "--start=920804400" @@ -156,20 +165,27 @@ static rrd_args *rrd_graph_obj_create_argv(const char *command_name, const rrd_g } /* use always string for option value */ - if (Z_TYPE_P(zv_option_val) != IS_STRING) { - convert_to_string(zv_option_val); + option_str = zval_try_get_string(zv_option_val); + if (!option_str) { + smart_string_free(&option); + zval_ptr_dtor_nogc(&zv_argv); + zend_array_release(ht); + return NULL; } - smart_string_appendl(&option, Z_STRVAL_P(zv_option_val), Z_STRLEN_P(zv_option_val)); + smart_string_appendl(&option, ZSTR_VAL(option_str), ZSTR_LEN(option_str)); smart_string_0(&option); + zend_string_release(option_str); add_next_index_string(&zv_argv, option.c); smart_string_free(&option); } ZEND_HASH_FOREACH_END(); - result = rrd_args_init_by_phparray(command_name, obj->file_path, &zv_argv); - zval_dtor(&zv_argv); + zend_array_release(ht); + + result = rrd_args_init_by_phparray(command_name, file_path, &zv_argv); + zval_ptr_dtor_nogc(&zv_argv); return result; } @@ -189,6 +205,12 @@ PHP_METHOD(RRDGraph, save) /* arguments for rrd_graph call */ rrd_args *graph_argv; + char *checked_path; + + if (!intern_obj->file_path) { + zend_throw_exception(NULL, "the object was not constructed", 0); + return; + } if (Z_TYPE(intern_obj->zv_arr_options) != IS_ARRAY) { zend_throw_exception(NULL, "options aren't correctly set", 0); @@ -199,7 +221,9 @@ PHP_METHOD(RRDGraph, save) RETURN_FALSE; } - graph_argv = rrd_graph_obj_create_argv("graph", intern_obj); + checked_path = estrdup(intern_obj->file_path); + graph_argv = rrd_graph_obj_create_argv("graph", intern_obj, checked_path); + efree(checked_path); if (!graph_argv) { zend_error(E_WARNING, "cannot allocate arguments options"); RETURN_FALSE; @@ -260,13 +284,25 @@ PHP_METHOD(RRDGraph, saveVerbose) /* arguments for rrd_graph call */ rrd_args *graph_argv; + char *checked_path; + + if (!intern_obj->file_path) { + zend_throw_exception(NULL, "the object was not constructed", 0); + return; + } if (Z_TYPE(intern_obj->zv_arr_options) != IS_ARRAY) { - zend_throw_exception(NULL, "options aren't correctly set", 0); + zend_throw_exception(NULL, "options aren't correctly set", 0); return; } - graph_argv = rrd_graph_obj_create_argv("graphv", intern_obj); + if (php_check_open_basedir(intern_obj->file_path)) { + RETURN_FALSE; + } + + checked_path = estrdup(intern_obj->file_path); + graph_argv = rrd_graph_obj_create_argv("graphv", intern_obj, checked_path); + efree(checked_path); if (!graph_argv) { zend_error(E_WARNING, "cannot allocate arguments options"); RETURN_FALSE; @@ -391,6 +427,6 @@ void rrd_graph_minit() memcpy(&rrd_graph_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); rrd_graph_handlers.clone_obj = NULL; - rrd_graph_handlers.offset = XtOffsetOf(rrd_graph_object, std); + rrd_graph_handlers.offset = offsetof(rrd_graph_object, std); rrd_graph_handlers.free_obj = rrd_graph_object_dtor; } diff --git a/rrd_update.c b/rrd_update.c index e21c778..9490b47 100644 --- a/rrd_update.c +++ b/rrd_update.c @@ -44,7 +44,7 @@ typedef struct _rrd_update_object { * fetch our custom object from user space object */ static inline rrd_update_object *php_rrd_update_fetch_object(zend_object *obj) { - return (rrd_update_object *)((char*)(obj) - XtOffsetOf(rrd_update_object, std)); + return (rrd_update_object *)((char*)(obj) - offsetof(rrd_update_object, std)); } /* {{{ rrd_update_object_dtor @@ -94,7 +94,9 @@ PHP_METHOD(RRDUpdater, __construct) } intern_obj = php_rrd_update_fetch_object(Z_OBJ_P(getThis())); - intern_obj->file_path = estrdup(path); + if (intern_obj->file_path) efree(intern_obj->file_path); + intern_obj->file_path = NULL; + intern_obj->file_path = estrndup(path, path_length); } /* }}} */ @@ -117,8 +119,13 @@ PHP_METHOD(RRDUpdater, update) size_t time_str_length = 1; int argc = ZEND_NUM_ARGS(); - zend_string *zs_ds_name; + zend_string *zs_ds_name, *zs_ds_val; zval *zv_ds_val; + HashTable *values_ht; + /* __construct is callable again, so a __toString below could repoint the + * object after the open_basedir check; work from a snapshot instead + */ + char *checked_path; /* string for all data source names formated for rrd_update call */ smart_string ds_names = {0}; @@ -136,34 +143,64 @@ PHP_METHOD(RRDUpdater, update) intern_obj = php_rrd_update_fetch_object(Z_OBJ_P(getThis())); + if (!intern_obj->file_path) { + zend_throw_exception(NULL, "the object was not constructed", 0); + return; + } + if (php_check_open_basedir(intern_obj->file_path)) { RETURN_FALSE; } + checked_path = estrdup(intern_obj->file_path); if (argc > 1 && time_str_length == 0) { + efree(checked_path); zend_throw_exception(NULL, "time cannot be empty string", 0); return; } - ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zv_values_array), zs_ds_name, zv_ds_val) { + /* converting a value runs __toString, which must not free this array */ + values_ht = Z_ARRVAL_P(zv_values_array); + GC_ADDREF(values_ht); + + ZEND_HASH_FOREACH_STR_KEY_VAL(values_ht, zs_ds_name, zv_ds_val) { + if (!zs_ds_name) { + smart_string_free(&ds_names); + smart_string_free(&ds_vals); + zend_array_release(values_ht); + efree(checked_path); + zend_throw_exception(NULL, + "values array must be keyed by data source name", 0); + return; + } + if (ds_names.len) { smart_string_appendc(&ds_names, ':'); } else { smart_string_appends(&ds_names, "--template="); } - smart_string_appends(&ds_names, ZSTR_VAL(zs_ds_name)); + smart_string_appendl(&ds_names, ZSTR_VAL(zs_ds_name), ZSTR_LEN(zs_ds_name)); /* "timestamp:ds1Value:ds2Value" string */ if (!ds_vals.len) { smart_string_appends(&ds_vals, time); } smart_string_appendc(&ds_vals, ':'); - if (Z_TYPE_P(zv_ds_val) != IS_STRING) { - convert_to_string(zv_ds_val); + zs_ds_val = zval_try_get_string(zv_ds_val); + if (!zs_ds_val) { + smart_string_free(&ds_names); + smart_string_free(&ds_vals); + zend_array_release(values_ht); + efree(checked_path); + return; } - smart_string_appendl(&ds_vals, Z_STRVAL_P(zv_ds_val), Z_STRLEN_P(zv_ds_val)); + smart_string_appendl(&ds_vals, ZSTR_VAL(zs_ds_val), ZSTR_LEN(zs_ds_val)); + zend_string_release(zs_ds_val); } ZEND_HASH_FOREACH_END(); + + zend_array_release(values_ht); + smart_string_0(&ds_names); smart_string_0(&ds_vals); @@ -176,11 +213,11 @@ PHP_METHOD(RRDUpdater, update) smart_string_free(&ds_names); smart_string_free(&ds_vals); - update_argv = rrd_args_init_by_phparray("update", intern_obj->file_path, &zv_update_argv); + update_argv = rrd_args_init_by_phparray("update", checked_path, &zv_update_argv); + efree(checked_path); if (!update_argv) { zend_error(E_WARNING, "cannot allocate arguments options"); - zval_dtor(&zv_update_argv); - if (time_str_length == 0) efree(time); + zval_ptr_dtor_nogc(&zv_update_argv); RETURN_FALSE; } @@ -188,7 +225,7 @@ PHP_METHOD(RRDUpdater, update) /* call rrd_update and test if fails */ if (rrd_update(update_argv->count - 1, &update_argv->args[1]) == -1) { - zval_dtor(&zv_update_argv); + zval_ptr_dtor_nogc(&zv_update_argv); rrd_args_free(update_argv); /* throw exception with rrd error string */ @@ -197,7 +234,7 @@ PHP_METHOD(RRDUpdater, update) return; } - zval_dtor(&zv_update_argv); + zval_ptr_dtor_nogc(&zv_update_argv); rrd_args_free(update_argv); RETURN_TRUE; @@ -265,6 +302,6 @@ void rrd_update_minit() memcpy(&rrd_update_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); rrd_update_handlers.clone_obj = NULL; - rrd_update_handlers.offset = XtOffsetOf(rrd_update_object, std); + rrd_update_handlers.offset = offsetof(rrd_update_object, std); rrd_update_handlers.free_obj = rrd_update_object_dtor; } diff --git a/tests/rrd_016.phpt b/tests/rrd_016.phpt index 14b2c7d..d79fe57 100644 --- a/tests/rrd_016.phpt +++ b/tests/rrd_016.phpt @@ -22,4 +22,3 @@ var_dump(rrd_tune($destFile, array("true"))); bool(true) bool(false) bool(true) -%s \ No newline at end of file diff --git a/tests/rrd_017.phpt b/tests/rrd_017.phpt index 633cf95..11e3423 100644 --- a/tests/rrd_017.phpt +++ b/tests/rrd_017.phpt @@ -11,92 +11,66 @@ if (!file_exists($data_updatedDb)) { --FILE-- $result["end"]) { $aligned = false; } +} +var_dump($aligned, count($speed) === count($real)); ?> ---EXPECTF-- +--EXPECT-- array(4) { - ["start"]=> + [0]=> + string(5) "start" + [1]=> + string(3) "end" + [2]=> + string(4) "step" + [3]=> + string(4) "data" +} +int(920804700) +int(300) +array(2) { + [0]=> + string(7) "myspeed" + [1]=> + string(9) "realspeed" +} +array(3) { + [0]=> int(920804700) - ["end"]=> - int(920808300) - ["step"]=> - int(300) - ["data"]=> - array(2) { - [0]=> - array(2) { - ["legend"]=> - string(7) "myspeed" - ["data"]=> - array(13) { - [920804700]=> - float(NAN) - [920805000]=> - float(0.04) - [920805300]=> - float(0.02) - [920805600]=> - float(0) - [920805900]=> - float(0) - [920806200]=> - float(0.0333333333%s) - [920806500]=> - float(0.0333333333%s) - [920806800]=> - float(0.0333333333%s) - [920807100]=> - float(0.02) - [920807400]=> - float(0.02) - [920807700]=> - float(0.02) - [920808000]=> - float(0.0133333333%s) - [920808300]=> - float(0.0166666666%s) - } - } - [1]=> - array(2) { - ["legend"]=> - string(9) "realspeed" - ["data"]=> - array(13) { - [920804700]=> - float(NAN) - [920805000]=> - float(40) - [920805300]=> - float(20) - [920805600]=> - float(0) - [920805900]=> - float(0) - [920806200]=> - float(33.333333333%s) - [920806500]=> - float(33.333333333%s) - [920806800]=> - float(33.333333333%s) - [920807100]=> - float(20) - [920807400]=> - float(20) - [920807700]=> - float(20) - [920808000]=> - float(13.333333333%s) - [920808300]=> - float(16.666666666%s) - } - } - } + [1]=> + int(920805000) + [2]=> + int(920805300) } +bool(true) +0.0400 0.0200 0.0333 +40.0000 20.0000 33.3333 +bool(true) +bool(true) diff --git a/tests/rrd_023.phpt b/tests/rrd_023.phpt new file mode 100644 index 0000000..7a335b5 --- /dev/null +++ b/tests/rrd_023.phpt @@ -0,0 +1,27 @@ +--TEST-- +RRDCreator::__construct() with no startTime argument +--SKIPIF-- + +--FILE-- +addDataSource("speed:COUNTER:600:U:U"); +$creator->addArchive("AVERAGE:0.5:1:24"); +var_dump($creator->save()); +var_dump(file_exists($file)); + +@unlink($file); +@unlink($primer_file); +?> +--EXPECT-- +bool(true) +bool(true) diff --git a/tests/rrd_024.phpt b/tests/rrd_024.phpt new file mode 100644 index 0000000..5960a56 --- /dev/null +++ b/tests/rrd_024.phpt @@ -0,0 +1,15 @@ +--TEST-- +RRDUpdater::update() rejects a values array with integer keys +--SKIPIF-- + +--FILE-- +update(array(1.0)); +} catch (Exception $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Exception: values array must be keyed by data source name diff --git a/tests/rrd_025.phpt b/tests/rrd_025.phpt new file mode 100644 index 0000000..c1cd788 --- /dev/null +++ b/tests/rrd_025.phpt @@ -0,0 +1,12 @@ +--TEST-- +RRDCreator::save() with no data source and no archive +--SKIPIF-- + +--FILE-- +save()); +?> +--EXPECTF-- +Warning: cannot allocate arguments options in %s on line %d +bool(false) diff --git a/tests/rrd_026.phpt b/tests/rrd_026.phpt new file mode 100644 index 0000000..a9049e9 --- /dev/null +++ b/tests/rrd_026.phpt @@ -0,0 +1,33 @@ +--TEST-- +RRDCreator, RRDUpdater and RRDGraph without a constructor call +--SKIPIF-- + +--FILE-- +newInstanceWithoutConstructor(); + try { + switch ($class) { + case "RRDCreator": + $obj->addDataSource("speed:COUNTER:600:U:U"); + $obj->addArchive("AVERAGE:0.5:1:24"); + $obj->save(); + break; + case "RRDUpdater": + $obj->update(array("speed" => 1)); + break; + case "RRDGraph": + $obj->setOptions(array("--start" => "920804400")); + $obj->save(); + break; + } + echo $class, ": no exception\n"; + } catch (Exception $e) { + echo $class, ": ", $e->getMessage(), "\n"; + } +} +?> +--EXPECT-- +RRDCreator: the object was not constructed +RRDUpdater: the object was not constructed +RRDGraph: the object was not constructed diff --git a/tests/rrd_027.phpt b/tests/rrd_027.phpt new file mode 100644 index 0000000..ae73cee --- /dev/null +++ b/tests/rrd_027.phpt @@ -0,0 +1,41 @@ +--TEST-- +rrd_fetch and rrd_xport with timestamps wider than 10 digits +--SKIPIF-- + +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + int(1700000000400) + [1]=> + int(1700000000700) +} +int(5) diff --git a/tests/rrd_028.phpt b/tests/rrd_028.phpt new file mode 100644 index 0000000..ce4fc4c --- /dev/null +++ b/tests/rrd_028.phpt @@ -0,0 +1,23 @@ +--TEST-- +options arrays are not modified by value conversion +--SKIPIF-- + +--FILE-- + 1.5); +$values_copy = $values; +$updater = new RRDUpdater(dirname(__FILE__) . "/no-mutate.rrd"); +try { $updater->update($values); } catch (Exception $e) {} +var_dump($values["speed"], $values_copy["speed"]); +?> +--EXPECT-- +int(300) +int(300) +float(1.5) +float(1.5) diff --git a/tests/rrd_029.phpt b/tests/rrd_029.phpt new file mode 100644 index 0000000..716af20 --- /dev/null +++ b/tests/rrd_029.phpt @@ -0,0 +1,39 @@ +--TEST-- +RRDCreator::save() and RRDGraph::saveVerbose() honour open_basedir +--SKIPIF-- + +--INI-- +open_basedir= +--FILE-- +addDataSource("speed:COUNTER:600:U:U"); +$creator->addArchive("AVERAGE:0.5:1:24"); +var_dump($creator->save()); + +$graph = new RRDGraph("$outside/graph.png"); +$graph->setOptions(array( + "--start" => "920804400", + "--end" => "920808000", + 0 => "DEF:myspeed=$data_updatedDb:speed:AVERAGE", + 1 => "LINE1:myspeed#FF0000", +)); +var_dump($graph->saveVerbose()); +?> +--EXPECTF-- +Warning: RRDCreator::save(): open_basedir restriction in effect. File(%s) is not within the allowed path(s): (%s) in %s on line %d +bool(false) + +Warning: RRDGraph::saveVerbose(): open_basedir restriction in effect. File(%s) is not within the allowed path(s): (%s) in %s on line %d +bool(false) diff --git a/tests/rrd_030.phpt b/tests/rrd_030.phpt new file mode 100644 index 0000000..adfa7b8 --- /dev/null +++ b/tests/rrd_030.phpt @@ -0,0 +1,29 @@ +--TEST-- +calling __construct a second time resets the object +--SKIPIF-- + +--FILE-- +addDataSource("speed:COUNTER:600:U:U"); +$creator->addArchive("AVERAGE:0.5:1:24"); + +/* the second construction must not inherit the first one's sources */ +$creator->__construct($second, "920804400", 600); +try { + var_dump($creator->save()); +} catch (Exception $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; +} +var_dump(file_exists($first), file_exists($second)); + +@unlink($first); @unlink($second); +?> +--EXPECT-- +Exception: you must define at least one Round Robin Archive +bool(false) +bool(false) diff --git a/tests/rrd_031.phpt b/tests/rrd_031.phpt new file mode 100644 index 0000000..7defa2d --- /dev/null +++ b/tests/rrd_031.phpt @@ -0,0 +1,39 @@ +--TEST-- +an unconvertible option value aborts before anything is written +--SKIPIF-- + +--FILE-- +update(array("speed" => new NoString)); + echo "update: no throw\n"; +} catch (Throwable $e) { + echo "update: ", get_class($e), "\n"; +} +var_dump(file_exists($file)); +?> +--EXPECT-- +NoString: Error +bool(false) +Throws: RuntimeException +bool(false) +update: Error +bool(false) diff --git a/tests/rrd_032.phpt b/tests/rrd_032.phpt new file mode 100644 index 0000000..5bcb60c --- /dev/null +++ b/tests/rrd_032.phpt @@ -0,0 +1,40 @@ +--TEST-- +__toString() cannot repoint the object after the path has been checked +--SKIPIF-- + +--FILE-- +__construct(self::$target); + return "1"; + } +} + +$dir = dirname(__FILE__); +$intended = "$dir/toctou-intended.rrd"; +$other = "$dir/toctou-other.rrd"; +@unlink($intended); @unlink($other); + +foreach (array($intended, $other) as $f) { + rrd_create($f, array("--start", "920804400", "--step", "300", + "DS:speed:GAUGE:600:U:U", "RRA:AVERAGE:0.5:1:24")); +} + +$updater = new RRDUpdater($intended); +Repoint::$obj = $updater; +Repoint::$target = $other; + +try { $updater->update(array("speed" => new Repoint), "920804700"); } +catch (Throwable $e) { echo "caught\n"; } + +/* the write must land on the path that was checked, not the repointed one */ +var_dump(rrd_last($intended), rrd_last($other)); + +@unlink($intended); @unlink($other); +?> +--EXPECT-- +int(920804700) +int(920804400) diff --git a/tests/rrd_033.phpt b/tests/rrd_033.phpt new file mode 100644 index 0000000..e0d5831 --- /dev/null +++ b/tests/rrd_033.phpt @@ -0,0 +1,35 @@ +--TEST-- +__toString() freeing the options array while it is being walked +--SKIPIF-- + +--FILE-- +setOptions(array("--start" => "920804400")); + return "920804400"; + } +} +class Stop { + /* throws once the walk has moved past the freed buckets, so librrd is + never reached and the test does not depend on a renderer */ + public function __toString(): string { throw new RuntimeException("stop"); } +} + +$graph = new RRDGraph(dirname(__FILE__) . "/reenter.png"); +Reenter::$graph = $graph; + +$options = array("--start" => new Reenter); +for ($i = 0; $i < 64; $i++) { $options["--pad$i"] = str_repeat("A", 64); } +$options["--end"] = new Stop; +$graph->setOptions($options); + +try { $graph->save(); } catch (Throwable $e) { echo get_class($e), "\n"; } +echo "survived\n"; +?> +--EXPECTF-- +Warning: cannot allocate arguments options in %s on line %d +RuntimeException +survived diff --git a/tests/rrd_034.phpt b/tests/rrd_034.phpt new file mode 100644 index 0000000..f4d7f8f --- /dev/null +++ b/tests/rrd_034.phpt @@ -0,0 +1,33 @@ +--TEST-- +rrd_fetch on a file whose header repeats a data source name +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +array(1) { + [0]=> + string(6) "speed1" +} +survived diff --git a/tests/rrd_035.phpt b/tests/rrd_035.phpt new file mode 100644 index 0000000..d54c6f7 --- /dev/null +++ b/tests/rrd_035.phpt @@ -0,0 +1,39 @@ +--TEST-- +re-constructing RRDGraph and RRDUpdater replaces the previous path +--SKIPIF-- + +--FILE-- +__construct($second); +$updater->update(array("speed" => 7), "920804700"); +var_dump(rrd_last($first), rrd_last($second)); + +/* RRDGraph: open_basedir is checked before librrd is called at all, so a + re-construction pointing outside it proves the path was replaced without + rendering anything */ +$graph = new RRDGraph("$dir/reconstruct.png"); +$graph->setOptions(array("--start" => "920804400")); +$graph->__construct(sys_get_temp_dir() . "/reconstruct-outside.png"); +ini_set("open_basedir", $dir . DIRECTORY_SEPARATOR); +var_dump($graph->save()); + +@unlink($first); @unlink($second); +?> +--EXPECTF-- +int(920804400) +int(920804700) + +Warning: RRDGraph::save(): open_basedir restriction in effect. File(%sreconstruct-outside.png) is not within the allowed path(s): (%s) in %s on line %d +bool(false) diff --git a/tests/rrd_036.phpt b/tests/rrd_036.phpt new file mode 100644 index 0000000..5bff939 --- /dev/null +++ b/tests/rrd_036.phpt @@ -0,0 +1,29 @@ +--TEST-- +methods on an object whose constructor threw +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +$creator = (new ReflectionClass("RRDCreator"))->newInstanceWithoutConstructor(); +try { $creator->__construct(""); } catch (Exception $e) { } +try { $creator->save(); } catch (Exception $e) { echo "save: ", $e->getMessage(), "\n"; } + +$updater = (new ReflectionClass("RRDUpdater"))->newInstanceWithoutConstructor(); +try { $updater->update(array("speed" => 1)); } catch (Exception $e) { echo "update: ", $e->getMessage(), "\n"; } + +$graph = (new ReflectionClass("RRDGraph"))->newInstanceWithoutConstructor(); +$graph->setOptions(array("--start" => "920804400")); +try { $graph->saveVerbose(); } catch (Exception $e) { echo "saveVerbose: ", $e->getMessage(), "\n"; } +?> +--EXPECT-- +construct: path for rrd file cannot be empty string +save: the object was not constructed +update: the object was not constructed +saveVerbose: the object was not constructed