From a7780753dc940102cc7a3faaece9780a7e9dd702 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Sun, 5 Feb 2023 12:23:01 +0200 Subject: [PATCH 1/6] Support registering ini definitions for modules PHP extensions that want to provide ini settings can do so using `IniEntryDef::register()`; values can then be fetched via `GlobalExecutor::ini_values()`. --- allowed_bindings.rs | 6 +++++ docsrs_bindings.rs | 36 +++++++++++++++++++++++++ src/flags.rs | 19 ++++++++++--- src/wrapper.h | 1 + src/zend/globals.rs | 26 +++++++++++++++++- src/zend/ini_entry_def.rs | 57 +++++++++++++++++++++++++++++++++++++++ src/zend/mod.rs | 2 ++ 7 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 src/zend/ini_entry_def.rs diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 7943f33396..d40bd9d09a 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -98,6 +98,8 @@ bind! { zend_objects_clone_members, zend_register_bool_constant, zend_register_double_constant, + zend_register_ini_entries, + zend_ini_entry_def, zend_register_internal_class_ex, zend_register_long_constant, zend_register_string_constant, @@ -141,6 +143,10 @@ bind! { IS_PTR, MAY_BE_ANY, MAY_BE_BOOL, + PHP_INI_USER, + PHP_INI_PERDIR, + PHP_INI_SYSTEM, + PHP_INI_ALL, USING_ZTS, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index f4fb348490..052a14b795 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -83,6 +83,10 @@ pub const ZEND_MODULE_API_NO: u32 = 20220829; pub const USING_ZTS: u32 = 0; pub const MAY_BE_BOOL: u32 = 12; pub const MAY_BE_ANY: u32 = 1022; +pub const PHP_INI_USER: u32 = 1; +pub const PHP_INI_PERDIR: u32 = 2; +pub const PHP_INI_SYSTEM: u32 = 4; +pub const PHP_INI_ALL: u32 = 7; pub const CONST_CS: u32 = 0; pub const CONST_PERSISTENT: u32 = 1; pub const CONST_NO_FILE_CACHE: u32 = 2; @@ -1343,6 +1347,32 @@ extern "C" { } #[repr(C)] #[derive(Debug, Copy, Clone)] +pub struct _zend_ini_entry_def { + pub name: *const ::std::os::raw::c_char, + pub on_modify: ::std::option::Option< + unsafe extern "C" fn( + entry: *mut zend_ini_entry, + new_value: *mut zend_string, + mh_arg1: *mut ::std::os::raw::c_void, + mh_arg2: *mut ::std::os::raw::c_void, + mh_arg3: *mut ::std::os::raw::c_void, + stage: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int, + >, + pub mh_arg1: *mut ::std::os::raw::c_void, + pub mh_arg2: *mut ::std::os::raw::c_void, + pub mh_arg3: *mut ::std::os::raw::c_void, + pub value: *const ::std::os::raw::c_char, + pub displayer: ::std::option::Option< + unsafe extern "C" fn(ini_entry: *mut zend_ini_entry, type_: ::std::os::raw::c_int), + >, + pub value_length: u32, + pub name_length: u16, + pub modifiable: u8, +} +pub type zend_ini_entry_def = _zend_ini_entry_def; +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct _zend_ini_entry { pub name: *mut zend_string, pub on_modify: ::std::option::Option< @@ -1368,6 +1398,12 @@ pub struct _zend_ini_entry { pub orig_modifiable: u8, pub modified: u8, } +extern "C" { + pub fn zend_register_ini_entries( + ini_entry: *const zend_ini_entry_def, + module_number: ::std::os::raw::c_int, + ) -> zend_result; +} extern "C" { pub fn zend_register_bool_constant( name: *const ::std::os::raw::c_char, diff --git a/src/flags.rs b/src/flags.rs index 20378e62b5..1461126036 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -8,10 +8,11 @@ use crate::ffi::{ CONST_CS, CONST_DEPRECATED, CONST_NO_FILE_CACHE, CONST_PERSISTENT, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_LONG, IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, - IS_UNDEF, IS_VOID, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, ZEND_ACC_CALL_VIA_TRAMPOLINE, - ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED, ZEND_ACC_CTOR, - ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING, ZEND_ACC_FAKE_CLOSURE, - ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK, ZEND_ACC_HAS_RETURN_TYPE, + IS_UNDEF, IS_VOID, PHP_INI_ALL, PHP_INI_PERDIR, PHP_INI_SYSTEM, PHP_INI_USER, + ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, ZEND_ACC_CALL_VIA_TRAMPOLINE, ZEND_ACC_CHANGED, + ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED, ZEND_ACC_CTOR, ZEND_ACC_DEPRECATED, + ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING, ZEND_ACC_FAKE_CLOSURE, ZEND_ACC_FINAL, + ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK, ZEND_ACC_HAS_RETURN_TYPE, ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE, ZEND_ACC_IMPLICIT_ABSTRACT_CLASS, ZEND_ACC_INTERFACE, ZEND_ACC_LINKED, ZEND_ACC_NEARLY_LINKED, ZEND_ACC_NEVER_CACHE, ZEND_ACC_NO_DYNAMIC_PROPERTIES, ZEND_ACC_PRELOADED, ZEND_ACC_PRIVATE, @@ -171,6 +172,16 @@ bitflags! { } } +bitflags! { + /// Represents permissions for where a configuration setting may be set. + pub struct IniEntryPermission: u32 { + const User = PHP_INI_USER; + const PerDir = PHP_INI_PERDIR; + const System = PHP_INI_SYSTEM; + const All = PHP_INI_ALL; + } +} + /// Valid data types for PHP. #[repr(C, u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] diff --git a/src/wrapper.h b/src/wrapper.h index 2813263676..ed9dea6294 100644 --- a/src/wrapper.h +++ b/src/wrapper.h @@ -20,6 +20,7 @@ #include "zend_exceptions.h" #include "zend_inheritance.h" #include "zend_interfaces.h" +#include "zend_ini.h" zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, bool persistent); void ext_php_rs_zend_string_release(zend_string *zs); diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 7ba76c4980..236a88837f 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -1,11 +1,12 @@ //! Types related to the PHP executor globals. +use std::collections::HashMap; use std::ops::{Deref, DerefMut}; use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::boxed::ZBox; -use crate::ffi::{_zend_executor_globals, ext_php_rs_executor_globals}; +use crate::ffi::{_zend_executor_globals, ext_php_rs_executor_globals, zend_ini_entry}; use crate::types::{ZendHashTable, ZendObject}; @@ -50,6 +51,29 @@ impl ExecutorGlobals { unsafe { self.class_table.as_ref() } } + /// Retrieves the ini values for all ini directives in the current executor context.. + pub fn ini_values(&self) -> HashMap> { + let hash_table = unsafe { self.ini_directives.as_ref().unwrap() }; + let mut ini_hash_map: HashMap> = HashMap::new(); + for (_index, key, value) in hash_table.iter() { + match key { + Some(key) => { + ini_hash_map.insert(key, unsafe { + let ini_entry = &*value.ptr::().unwrap(); + if ini_entry.value.is_null() { + None + } else { + Some((&*ini_entry.value).as_str().unwrap().to_owned()) + } + }); + () + } + None => (), + } + } + ini_hash_map + } + /// Attempts to extract the last PHP exception captured by the interpreter. /// Returned inside a [`ZBox`]. /// diff --git a/src/zend/ini_entry_def.rs b/src/zend/ini_entry_def.rs new file mode 100644 index 0000000000..367ef2ce35 --- /dev/null +++ b/src/zend/ini_entry_def.rs @@ -0,0 +1,57 @@ +//! Builder for creating inis and methods in PHP. +//! See https://www.phpinternalsbook.com/php7/extensions_design/ini_settings.html for details. + +use std::{ffi::CString, os::raw::c_char, ptr}; + +use crate::{ffi::zend_ini_entry_def, ffi::zend_register_ini_entries, flags::IniEntryPermission}; + +/// A Zend ini entry definition. +/// +/// To register ini definitions for extensions, the IniEntryDef builder should be used. Ini +/// entries should be registered in your module's startup_function via IniEntryDef::register(Vec). +pub type IniEntryDef = zend_ini_entry_def; + +impl IniEntryDef { + /// Returns an empty ini entry, signifying the end of a ini list. + pub fn new(name: String, default_value: String, permission: IniEntryPermission) -> Self { + let mut template = Self::end(); + let name = CString::new(name).unwrap(); + let value = CString::new(default_value).unwrap(); + template.name_length = name.as_bytes().len() as _; + template.name = name.into_raw(); + template.value_length = value.as_bytes().len() as _; + template.value = value.into_raw(); + template.modifiable = IniEntryPermission::PerDir.bits() as _; + template.modifiable = permission.bits() as _; + template + } + + /// Returns an empty ini entry def, signifying the end of a ini list. + pub fn end() -> Self { + Self { + name: ptr::null() as *const c_char, + on_modify: None, + mh_arg1: std::ptr::null_mut(), + mh_arg2: std::ptr::null_mut(), + mh_arg3: std::ptr::null_mut(), + value: std::ptr::null_mut(), + displayer: None, + modifiable: 0, + value_length: 0, + name_length: 0, + } + } + + /// Converts the ini entry into a raw and pointer, releasing it to the + /// C world. + pub fn into_raw(self) -> *mut Self { + Box::into_raw(Box::new(self)) + } + + pub fn register(mut entries: Vec, module_number: i32) { + entries.push(Self::end()); + let entries = Box::into_raw(entries.into_boxed_slice()) as *const Self; + + unsafe { zend_register_ini_entries(entries, module_number) }; + } +} diff --git a/src/zend/mod.rs b/src/zend/mod.rs index 74547f044a..b69aa52fdd 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -7,6 +7,7 @@ mod ex; mod function; mod globals; mod handlers; +mod ini_entry_def; mod module; use crate::{error::Result, ffi::php_printf}; @@ -18,6 +19,7 @@ pub use ex::ExecuteData; pub use function::FunctionEntry; pub use globals::ExecutorGlobals; pub use handlers::ZendObjectHandlers; +pub use ini_entry_def::IniEntryDef; pub use module::ModuleEntry; // Used as the format string for `php_printf`. From 99439bf7f5e233b444be5520dd5a65d770598787 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Thu, 13 Jul 2023 15:24:29 +0200 Subject: [PATCH 2/6] Clippy --- src/zend/globals.rs | 25 +++++++++++-------------- src/zend/ini_entry_def.rs | 4 ++-- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 236a88837f..c093b9c7fb 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -53,23 +53,20 @@ impl ExecutorGlobals { /// Retrieves the ini values for all ini directives in the current executor context.. pub fn ini_values(&self) -> HashMap> { - let hash_table = unsafe { self.ini_directives.as_ref().unwrap() }; + let hash_table = unsafe { &*self.ini_directives }; let mut ini_hash_map: HashMap> = HashMap::new(); for (_index, key, value) in hash_table.iter() { - match key { - Some(key) => { - ini_hash_map.insert(key, unsafe { - let ini_entry = &*value.ptr::().unwrap(); - if ini_entry.value.is_null() { - None - } else { - Some((&*ini_entry.value).as_str().unwrap().to_owned()) - } - }); - () - } - None => (), + if let Some(key) = key { + ini_hash_map.insert(key, unsafe { + let ini_entry = &*value.ptr::().expect("Invalid ini entry"); + if ini_entry.value.is_null() { + None + } else { + Some((*ini_entry.value).as_str().expect("Ini value is not a string").to_owned()) + } + }); } + } ini_hash_map } diff --git a/src/zend/ini_entry_def.rs b/src/zend/ini_entry_def.rs index 367ef2ce35..88850fdb91 100644 --- a/src/zend/ini_entry_def.rs +++ b/src/zend/ini_entry_def.rs @@ -15,8 +15,8 @@ impl IniEntryDef { /// Returns an empty ini entry, signifying the end of a ini list. pub fn new(name: String, default_value: String, permission: IniEntryPermission) -> Self { let mut template = Self::end(); - let name = CString::new(name).unwrap(); - let value = CString::new(default_value).unwrap(); + let name = CString::new(name).expect("Unable to create CString from name"); + let value = CString::new(default_value).expect("Unable to create CString from value"); template.name_length = name.as_bytes().len() as _; template.name = name.into_raw(); template.value_length = value.as_bytes().len() as _; From 7d40ca544fd5836ec9330663d448ea6b7fe8edda Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Thu, 13 Jul 2023 15:39:44 +0200 Subject: [PATCH 3/6] Formatting --- src/zend/globals.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/zend/globals.rs b/src/zend/globals.rs index c093b9c7fb..f4db898672 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -51,7 +51,8 @@ impl ExecutorGlobals { unsafe { self.class_table.as_ref() } } - /// Retrieves the ini values for all ini directives in the current executor context.. + /// Retrieves the ini values for all ini directives in the current executor + /// context.. pub fn ini_values(&self) -> HashMap> { let hash_table = unsafe { &*self.ini_directives }; let mut ini_hash_map: HashMap> = HashMap::new(); @@ -62,11 +63,15 @@ impl ExecutorGlobals { if ini_entry.value.is_null() { None } else { - Some((*ini_entry.value).as_str().expect("Ini value is not a string").to_owned()) + Some( + (*ini_entry.value) + .as_str() + .expect("Ini value is not a string") + .to_owned(), + ) } }); } - } ini_hash_map } From b451d604df8da59b5b7c15e7b1611367fdfa15ae Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Thu, 13 Jul 2023 16:24:00 +0200 Subject: [PATCH 4/6] URL formatting --- src/zend/ini_entry_def.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zend/ini_entry_def.rs b/src/zend/ini_entry_def.rs index 88850fdb91..f5f156dbc9 100644 --- a/src/zend/ini_entry_def.rs +++ b/src/zend/ini_entry_def.rs @@ -1,5 +1,5 @@ //! Builder for creating inis and methods in PHP. -//! See https://www.phpinternalsbook.com/php7/extensions_design/ini_settings.html for details. +//! See for details. use std::{ffi::CString, os::raw::c_char, ptr}; From 0f4f6f731556c8e788dbabe425fddae8f11fc742 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Thu, 13 Jul 2023 17:16:30 +0200 Subject: [PATCH 5/6] Add docs / guide --- guide/src/SUMMARY.md | 1 + guide/src/ini-settings.md | 48 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 guide/src/ini-settings.md diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index f725964069..ed4507c732 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -32,3 +32,4 @@ - [Constants](./macros/constant.md) - [`ZvalConvert`](./macros/zval_convert.md) - [Exceptions](./exceptions.md) +- [INI Settings](./ini-settings.md) diff --git a/guide/src/ini-settings.md b/guide/src/ini-settings.md new file mode 100644 index 0000000000..22820c5e2e --- /dev/null +++ b/guide/src/ini-settings.md @@ -0,0 +1,48 @@ +# INI Settings + +Your PHP Extension may want to provide it's own PHP INI settings to configure behaviour. This can be done in the `#[php_startup]` annotated startup function. + +## Registering INI Settings + +All PHP INI definitions must be registered with PHP to get / set their values via the `php.ini` file or `ini_get() / ini_set()`. + + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; +# use ext_php_rs::zend::IniEntryDef; +# use ext_php_rs::flags::IniEntryPermission; + +#[php_startup] +pub fn startup_function(ty: i32, module_number: i32) { + let ini_entries: Vec = vec![ + IniEntryDef::new( + "my_extension.display_emoji".to_owned(), + "yes".to_owned(), + IniEntryPermission::All, + ), + ]; + IniEntryDef::register(ini_entries, module_number); +} +# fn main() {} +``` + +## Getting INI Settings + +The INI values are stored as part of the `GlobalExecutor`, and can be accessed via the `ini_values()` function. To retrieve the value for a registered INI setting + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; +# use ext_php_rs::zend::ExecutorGlobals; + +#[php_startup] +pub fn startup_function(ty: i32, module_number: i32) { + // Get all INI values + let ini_values = ExecutorGlobals::get().ini_values(); // HashMap> + let my_ini_value = ini_values.get("my_extension.display_emoji"); // Option> +} +# fn main() {} +``` From 6db91acae39df359ed3b25a54b63a68ef5192340 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Thu, 13 Jul 2023 20:29:48 +0200 Subject: [PATCH 6/6] formatting --- src/zend/ini_entry_def.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zend/ini_entry_def.rs b/src/zend/ini_entry_def.rs index f5f156dbc9..8817300316 100644 --- a/src/zend/ini_entry_def.rs +++ b/src/zend/ini_entry_def.rs @@ -8,7 +8,7 @@ use crate::{ffi::zend_ini_entry_def, ffi::zend_register_ini_entries, flags::IniE /// A Zend ini entry definition. /// /// To register ini definitions for extensions, the IniEntryDef builder should be used. Ini -/// entries should be registered in your module's startup_function via IniEntryDef::register(Vec). +/// entries should be registered in your module's startup_function via `IniEntryDef::register(Vec)`. pub type IniEntryDef = zend_ini_entry_def; impl IniEntryDef {