Home  >  Article  >  php教程  >  Dynamically modify ini configuration in php_php basics

Dynamically modify ini configuration in php_php basics

WBOY
WBOYOriginal
2016-05-16 08:59:562020browse

1. Change configuration during runtime
As mentioned in the previous article, the ini_set function can dynamically modify some configurations of PHP during the execution of PHP. Note that this is only part of it, not all configurations can be modified dynamically. Regarding the modifiable ini configuration, see: http://php.net/manual/zh/configuration. changes.modes.php

We go directly to the implementation of ini_set. Although the function is a bit long, the logic is very clear:

Copy code The code is as follows:

PHP_FUNCTION(ini_set)
{
    char *varname, *new_value;
    int varname_len, new_value_len;
    char *old_value;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &varname, &varname_len, &new_value, &new_value_len) == FAILURE) {
        return;
    }

    // 去EG(ini_directives)中获取配置的值
    old_value = zend_ini_string(varname, varname_len 1, 0);

    /* copy to return here, because alter might free it! */
    if (old_value) {
        RETVAL_STRING(old_value, 1);
    } else {
        RETVAL_FALSE;
    }

// If safe mode is turned on, the following ini configurations may involve file operations and require auxiliary checking of uid
#define _CHECK_PATH(var, var_len, ini) php_ini_check_path(var, var_len, ini, sizeof(ini))
/* safe_mode & basedir check */
If (PG(safe_mode) || PG(open_basedir)) {
If (_CHECK_PATH(varname, varname_len, "error_log") ||
​​​​​​ _CHECK_PATH(varname, varname_len, "java.class.path") ||
​​​​​​ _CHECK_PATH(varname, varname_len, "java.home") ||
​​​​​​ _CHECK_PATH(varname, varname_len, "mail.log") ||
​​​​​​ _CHECK_PATH(varname, varname_len, "java.library.path") ||
​​​​​​ _CHECK_PATH(varname, varname_len, "vpopmail.directory")) {
If (PG(safe_mode) && (!php_checkuid(new_value, NULL, CHECKUID_CHECK_FILE_AND_DIR))) {
                   zval_dtor(return_value);
RETURN_FALSE;
             }
                 if (php_check_open_basedir(new_value TSRMLS_CC)) {
                   zval_dtor(return_value);
RETURN_FALSE;
             }
         }
}

// In safe mode, the following ini's are protected and will not be dynamically modified
If (PG(safe_mode)) {
If (!strncmp("max_execution_time", varname, sizeof("max_execution_time")) ||
               !strncmp("memory_limit", varname, sizeof("memory_limit")) ||
               !strncmp("child_terminate", varname, sizeof("child_terminate"))
) {
               zval_dtor(return_value);
               RETURN_FALSE;
         }
}

// Call zend_alter_ini_entry_ex to dynamically modify the ini configuration
If (zend_alter_ini_entry_ex(varname, varname_len 1, new_value, new_value_len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0 TSRMLS_CC) == FAILURE) {
          zval_dtor(return_value);
RETURN_FALSE;
}
}

As you can see, in addition to some necessary verification work, the main thing is to call zend_alter_ini_entry_ex.

We continue to follow up in the zend_alter_ini_entry_ex function:

Copy code The code is as follows:

ZEND_API int zend_alter_ini_entry_ex(char *name, uint name_length, char *new_value, uint new_value_length, int modify_type, int stage, int force_change TSRMLS_DC) /* {{{ */
{
zend_ini_entry *ini_entry;
char *duplicate;
zend_bool modifiable;
zend_bool modified;

// Find the corresponding ini_entry in EG (ini_directives)
If (zend_hash_find(EG(ini_directives), name, name_length, (void **) &ini_entry) == FAILURE) {
         return FAILURE;
}

// Whether it has been modified and whether it can be modified
Modifiable = ini_entry->modifiable;
Modified = ini_entry->modified;

if (stage == ZEND_INI_STAGE_ACTIVATE && modify_type == ZEND_INI_SYSTEM) {
ini_entry->modifiable = ZEND_INI_SYSTEM;
}

// Whether to force modification
If (!force_change) {
If (!(ini_entry->modifiable & modify_type)) {
               return FAILURE;
         }
}

// EG (modified_ini_directives) is used to store modified ini_entry
// Mainly used for recovery
If (!EG(modified_ini_directives)) {
ALLOC_HASHTABLE(EG(modified_ini_directives));
         zend_hash_init(EG(modified_ini_directives), 8, NULL, NULL, 0);
}

//Reserve the value in ini_entry, the length of the value, and the modifiable range into orig_xxx
// So that ini_entry can be restored when the request ends
If (!modified) {
ini_entry->orig_value = ini_entry->value;
ini_entry->orig_value_length = ini_entry->value_length;
ini_entry->orig_modifiable = modifiable;
ini_entry->modified = 1;
         zend_hash_add(EG(modified_ini_directives), name, name_length, &ini_entry, sizeof(zend_ini_entry*), NULL);
}

duplicate = estrndup(new_value, new_value_length);

// Call modify to update the corresponding ini configuration in XXX_G
If (!ini_entry->on_modify || ini_entry->on_modify(ini_entry, duplicate, new_value_length, ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry->mh_arg3, stage TSRMLS_CC) == SUCCESS) {
//Same as above, if modified multiple times, the previously modified value needs to be released
If (modified && ini_entry->orig_value != ini_entry->value) {
             efree(ini_entry->value);
         }
ini_entry->value = duplicate;
ini_entry->value_length = new_value_length;
} else {
        efree(duplicate);
         return FAILURE;
}

return SUCCESS;
}

There are three pieces of logic that we need to understand carefully:

1) The modified field in ini_entry is used to indicate whether the configuration has been dynamically modified. Once the ini configuration is modified, modified will be set to 1. There is a crucial section in the above code:

Copy code The code is as follows:

// If ini_set is called multiple times, orig_value, etc. always maintain the original value
if (!modified) {
ini_entry->orig_value = ini_entry->value;
ini_entry->orig_value_length = ini_entry->value_length;
ini_entry->orig_modifiable = modifiable;
ini_entry->modified = 1;
zend_hash_add(EG(modified_ini_directives), name, name_length, &ini_entry, sizeof(zend_ini_entry*), NULL);
}

This code means that no matter how many times we call ini_set in the php code, only the first ini_set will enter this logic and set the orig_value. Starting from the second call to ini_set, this branch will not be executed again, because modified at this time has been set to 1. Therefore, ini_entry->orig_value always saves the configuration value before the first modification (that is, the most original configuration).

2) In order to make the configuration modified by ini_set take effect immediately, the on_modify callback function is required.

As mentioned in the previous article, on_modify is called to be able to update the module's global variables. Recall again, first of all, the configuration in the module global variables is no longer of string type. Use bool when it should use bool, and int when it should use int. Secondly, each ini_entry stores the address of the module's global variable and the corresponding offset, so that on_modify can quickly modify the memory. In addition, don't forget that after on_modify is called, ini_entry->value still needs to be further updated so that the configuration value in EG (ini_directives) is the latest.

3) A new hash table appears here, EG (modified_ini_directives).

EG (modified_ini_directives) is only used to store dynamically modified ini configurations. If an ini configuration is dynamically modified, then it exists in both EG (ini_directives) and EG (modified_ini_directives). Since each ini_entry is marked with a modified field, isn't it possible to traverse EG (ini_directives) to obtain all modified configurations?

The answer is yes. Personally, I feel that the EG (modified_ini_directives) here is mainly to improve performance, and it is enough to directly traverse the EG (modified_ini_directives). In addition, by deferring the initialization of EG (modified_ini_directives) to zend_alter_ini_entry_ex, you can also see the performance optimization points of PHP in details.

2. Restore configuration
The action time of ini_set is different from that of the php.ini file. Once the request execution ends, ini_set will become invalid. In addition, when the ini_restore function is called in our code, the configuration previously set through ini_set will also become invalid.

After each php request is executed, php_request_shutdown will be triggered. It and php_request_startup are two corresponding processes. If php is hooked under apache/nginx, php_request_shutdown will be called every time an http request is processed; if php is run in CLI mode, php_request_shutdown will also be called after the script is executed.

In php_request_shutdown, we can see the recovery processing for ini:

Copy code The code is as follows:

/* 7. Shutdown scanner/executor/compiler and restore ini entries */
zend_deactivate(TSRMLS_C);

Entering zend_deactivate, you can further see that the zend_ini_deactivate function is called, and zend_ini_deactivate is responsible for restoring the PHP configuration.

Copy code The code is as follows:

zend_try {
zend_ini_deactivate(TSRMLS_C);
} zend_end_try();

Let’s take a closer look at the implementation of zend_ini_deactivate:

Copy code The code is as follows:

ZEND_API int zend_ini_deactivate(TSRMLS_D) /* {{{ */
{
If (EG(modified_ini_directives)) {
// Traverse this table in EG (modified_ini_directives)
// Call zend_restore_ini_entry_wrapper
for each ini_entry          zend_hash_apply(EG(modified_ini_directives), (apply_func_t) zend_restore_ini_entry_wrapper TSRMLS_CC);
                                                                               
// Recycling operation
        zend_hash_destroy(EG(modified_ini_directives));
FREE_HASHTABLE(EG(modified_ini_directives));
         EG(modified_ini_directives) = NULL;
}
Return SUCCESS;
}

From the perspective of zend_hash_apply, the real task of restoring ini finally falls to the zend_restore_ini_entry_wrapper callback function.

Copy code The code is as follows:

static int zend_restore_ini_entry_wrapper(zend_ini_entry **ini_entry TSRMLS_DC)
{
// zend_restore_ini_entry_wrapper is the encapsulation of zend_restore_ini_entry_cb
zend_restore_ini_entry_cb(*ini_entry, ZEND_INI_STAGE_DEACTIVATE TSRMLS_CC);
Return 1;
}

static int zend_restore_ini_entry_cb(zend_ini_entry *ini_entry, int stage TSRMLS_DC)
{
int result = FAILURE;

// Only view modified ini items
If (ini_entry->modified) {
If (ini_entry->on_modify) {
//Use orig_value to reset the relevant fields in XXX_G
              zend_try {
result = ini_entry->on_modify(ini_entry, ini_entry->orig_value, ini_entry->orig_value_length, ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry-phpc ngtphpcnmh_arg3, stage TSRMLS_CC);
                           zend_end_try();
         }
If (stage == ZEND_INI_STAGE_RUNTIME && result == FAILURE) {
                  /* runtime failure is OK */
               return 1;
         }
If (ini_entry->value != ini_entry->orig_value) {
             efree(ini_entry->value);
         }
                                                                               
// ini_entry itself is restored to its original value
ini_entry->value = ini_entry->orig_value;
ini_entry->value_length = ini_entry->orig_value_length;
ini_entry->modifiable = ini_entry->orig_modifiable;
ini_entry->modified = 0;
ini_entry->orig_value = NULL;
ini_entry->orig_value_length = 0;
ini_entry->orig_modifiable = 0;
}
Return 0;
}

The logic is quite clear, I believe readers can understand it. Summarize the recovery process of ini configuration:

Copy code The code is as follows:

php_request_shutdown--->zend_deactivate--->zend_ini_deactivate--->zend_restore_ini_entry_wrapper--->zend_restore_ini_entry_cb

3. Destruction of configuration
At the end of the sapi life cycle, for example, apache is shut down, the cli program is executed, etc. Once entering this stage, the previously mentioned configuration_hash, EG (ini_directives), etc. need to be destroyed, and the memory space used by them needs to be released.

1. PHP will end all modules in sequence and call UNREGISTER_INI_ENTRIES in PHP_MSHUTDOWN_FUNCTION of each module. UNREGISTER_INI_ENTRIES corresponds to REGISTER_INI_ENTRIES, but UNREGISTER_INI_ENTRIES is not responsible for releasing the global space of the module. The memory of XXX_globals is placed in the static data area and does not need to be manually recycled.

The main thing UNREGISTER_INI_ENTRIES does is to delete the ini_entry configuration of a certain module from the EG (ini_directives) table. After deletion, the space of ini_entry itself will be recycled, but ini_entry->value may not be recycled.

After PHP_MSHUTDOWN_FUNCTION of all modules calls UNREGISTER_INI_ENTRIES once, only the ini configuration of the Core module is left in EG (ini_directives). At this time, you need to manually call UNREGISTER_INI_ENTRIES to complete the deletion of the Core module configuration.

Copy code The code is as follows:

void php_module_shutdown(TSRMLS_D)
{
...

// zend_shutdown will shut down all php modules except Core in sequence
// PHP_MSHUTDOWN_FUNCTION of each module will be called when closing
zend_shutdown(TSRMLS_C);

...

// At this point, only the configuration of the Core module is left in EG (ini_directives)
// Clean it manually here
UNREGISTER_INI_ENTRIES();

// Recycle configuration_hash
php_shutdown_config();

// Recycle EG(ini_directives)
zend_ini_shutdown(TSRMLS_C);

...
}

When the manual call to UNREGISTER_INI_ENTRIES is completed, EG (ini_directives) no longer contains any elements. Theoretically, EG (ini_directives) at this time is an empty hash table.

2. The recycling of configuration_hash occurs after EG (ini_directives). The code posted above contains the function call about php_shutdown_config. php_shutdown_config is mainly responsible for recycling configuration_hash.

Copy code The code is as follows:

int php_shutdown_config(void)
{
// Recycle configuration_hash
zend_hash_destroy(&configuration_hash);

...

Return SUCCESS;
}

Note that zend_hash_destroy does not release the space of configuration_hash itself. Like the global space of the module accessed by XXX_G, configuration_hash is also a global variable and does not need to be manually recycled.

3. When php_shutdown_config is completed, only the own space of EG (ini_directives) has not been released. So the last step calls zend_ini_shutdown. zend_ini_shutdown is used to release EG (ini_directives). As mentioned above, the EG (ini_directives) at this time is theoretically an empty hash table, so the space occupied by the HashTable itself needs to be released.

Copy code The code is as follows:

ZEND_API int zend_ini_shutdown(TSRMLS_D)
{
// EG (ini_directives) is dynamically allocated space and needs to be recycled
zend_hash_destroy(EG(ini_directives));
free(EG(ini_directives));
Return SUCCESS;
}

4. Summary
Use a picture to roughly describe the process related to ini configuration:

Dynamically modify ini configuration in php_php basics

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn