In the previous article, we explained the principles of php. ini configuration. in this article, we will modify the php configuration dynamically. For more information, see section 1. change the configuration during runtime.
As mentioned in the previous article, the ini_set function can dynamically modify some php configurations during php execution. Note that not all configurations can be modified dynamically. For information on the modifiable ini configuration, see: http://php.net/manual/zh/configuration.changes.modes.php
We directly go to the implementation of ini_set. although the function is a bit long, the logic is clear:
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;
}
// Obtain the configuration value from 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 the security mode is enabled, the following ini configurations may involve file operations. you need to check the 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 security mode, the following ini files 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;
}
}
We can see that in addition to some necessary verification work, the main task is to call zend_alter_ini_entry_ex.
We will continue to follow up on the zend_alter_ini_entry_ex function:
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_direves VES)
If (zend_hash_find (EG (ini_directives), name, name_length, (void **) & ini_entry) = FAILURE ){
Return FAILURE;
}
// Whether it is modified or not
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 modify
If (! Force_change ){
If (! (Ini_entry-> modifiable & modify_type )){
Return FAILURE;
}
}
// EG (modified_ini_directives) is used to store the modified ini_entry
// Mainly used for restoration
If (! EG (modified_ini_directives )){
ALLOC_HASHTABLE (EG (modified_ini_directives ));
Zend_hash_init (EG (modified_ini_directives), 8, NULL, NULL, 0 );
}
// Modify the value and length of the value in ini_entry and keep it in orig_xxx.
// You can restore the ini_entry at the end of the request.
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, upgrade, ini_entry-> upgrade, stage break) = SUCCESS ){
// Same as above. if you modify the value multiple times, you need to release the value modified previously.
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 logics that need to be carefully understood:
1) the modified field in ini_entry indicates whether the configuration has been dynamically modified. Once the ini configuration is modified, the modified value is set to 1. There is a key section in the above code:
The code is as follows:
// If you call ini_set multiple times, always keep the original values such as orig_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 indicates that no matter how many times we call ini_set in the php code, the logic is entered only when ini_set is used for the first time, and the orig_value is set. Starting from the second call of ini_set, this branch will not be executed again, because the modified has been set to 1. Therefore, ini_entry-> orig_value always saves the previous configuration value (that is, the original configuration) for the first modification ).
2) the on_modify callback function is required to enable the ini_set configuration to take effect immediately.
As described in the previous article, on_modify is called to update the global variables of the module. Recall that, first, the configuration in the global variable of the module is no longer of the string type. bool and int are used for bool. Second, each ini_entry stores the address of the global variable and the corresponding offset of the module, so that on_modify can be quickly modified in memory. In addition, do not forget that after on_modify is called, you still need to update ini_entry-> value, so that the configuration value in EG (ini_directives) is the latest.
3) a new hash table (EG (modified_ini_directives) is displayed ).
EG (modified_ini_directives) is only used to store dynamically modified ini configurations. If an ini configuration has been dynamically modified, it is stored in EG (ini_directives, also exists in EG (modified_ini_directives. Since every ini_entry has a modified field for marking, isn't it possible to traverse EG (ini_directives) to obtain all the modified configurations?
The answer is yes. I personally think that the EG (modified_ini_directives) here is mainly to improve the performance, it is enough to directly traverse EG (modified_ini_directives. In addition, the initialization of EG (modified_ini_directives) is postponed to zend_alter_ini_entry_ex. the detailed performance optimization points of php can also be seen.
2. restore configuration
The time when ini_set takes effect is different from the time when the php. ini file takes effect. Once the request execution ends, ini_set becomes 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 is triggered. php_request_startup and php_request_startup correspond to each other. If php is attached to apache/nginx, php_request_shutdown is called every time an http request is processed. If php is run in CLI mode, after the script is executed, php_request_shutdown is also called.
In php_request_shutdown, we can see the recovery for ini:
The code is as follows:
/* 7. Shutdown restart/executor/compiler and restore ini entries */
Zend_deactivate (TSRMLS_C );
Go to zend_deactivate, and you can see that the zend_ini_deactivate function is called. zend_ini_deactivate is responsible for restoring the php configuration.
The code is as follows:
Zend_try {
Zend_ini_deactivate (TSRMLS_C );
} Zend_end_try ();
Let's take a look at the implementation of zend_ini_deactivate:
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 );
// Recycle
Zend_hash_destroy (EG (modified_ini_directives ));
FREE_HASHTABLE (EG (modified_ini_directives ));
EG (modified_ini_directives) = NULL;
}
Return SUCCESS;
}
From zend_hash_apply, the task of actually recovering ini is finally implemented to the zend_restore_ini_entry_wrapper callback function.
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 the modified ini items
If (ini_entry-> modified ){
If (ini_entry-> on_modify ){
// Use orig_value to reset related fields in XXX_G
Zend_try {
Result = ini_entry-> on_modify (ini_entry, ini_entry-> orig_value, ini_entry-> Export, ini_entry-> Export, ini_entry-> mh_arg2, ini_entry-> mh_arg3, stage upgrade );
} 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 restores to the 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. To sum up the ini configuration restoration process:
The code is as follows:
Php_request_shutdown ---> zend_deactivate ---> zend_ini_deactivate ---> zend_restore_ini_entry_wrapper ---> zend_restore_ini_entry_cb
3. destroy configuration
When the sapi lifecycle ends, for example, apache is disabled and cli is executed. At this stage, all the previously mentioned configuration_hash and EG (ini_directives) need to be destroyed, and the memory space used must be released.
1. php ends all modules in sequence and calls 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 the global space release of the module. the memory of XXX_globals is stored in the static data area and does not need to be recycled.
UNREGISTER_INI_ENTRIES is mainly used to delete the ini_entry configuration of a module from the EG (ini_directives) table. After deletion, the ini_entry space will be recycled, but ini_entry-> value may not be recycled.
After the PHP_MSHUTDOWN_FUNCTION of all modules call UNREGISTER_INI_ENTRIES once, only the ini configuration of the Core module is left in EG (ini_directives. In this case, you need to manually call UNREGISTER_INI_ENTRIES to delete the Core module configuration.
The code is as follows:
Void php_module_shutdown (TSRMLS_D)
{
...
// Zend_shutdown will close all php modules except Core in turn
// When the function is disabled, the PHP_MSHUTDOWN_FUNCTION of each module is called.
Zend_shutdown (TSRMLS_C );
...
// At this point, the Core module configuration is left in EG (ini_directives ).
// Manually clear it here
UNREGISTER_INI_ENTRIES ();
// Reclaim configuration_hash
Php_shutdown_config ();
// Reclaim EG (ini_directives)
Zend_ini_shutdown (TSRMLS_C );
...
}
After you manually call UNREGISTER_INI_ENTRIES, the EG (ini_directives) does not contain any elements. Theoretically, the EG (ini_directives) is an empty hash table.
2. the collection of configuration_hash occurs after EG (ini_directives). The Code posted above contains the function call of php_shutdown_config. Php_shutdown_config is mainly responsible for revoking configuration_hash.
The code is as follows:
Int php_shutdown_config (void)
{
// Reclaim 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 recycled manually.
3. when php_shutdown_config is complete, only the space of EG (ini_directives) is not released. Therefore, the last step is to call zend_ini_shutdown. Zend_ini_shutdown is used to release EG (ini_directives ). As mentioned above, the EG (ini_directives) is theoretically an empty hash table, so the space occupied by the HashTable itself needs to be released.
The code is as follows:
ZEND_API int zend_ini_shutdown (TSRMLS_D)
{
// EG (ini_directives) is the space dynamically allocated and needs to be recycled.
Zend_hash_destroy (EG (ini_directives ));
Free (EG (ini_directives ));
Return SUCCESS;
}
4. Summary
Use a figure to roughly describe the process related to the ini configuration: