diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 7252beaa63..4f9eed0616 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -99,6 +99,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, @@ -157,6 +159,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 1ba4175991..6ef4218389 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -101,6 +101,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; @@ -1376,6 +1380,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< @@ -1401,6 +1431,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/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() {} +``` diff --git a/src/flags.rs b/src/flags.rs index d2fbc37ba3..60897a0654 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -10,11 +10,12 @@ use crate::ffi::{ E_RECOVERABLE_ERROR, E_STRICT, E_USER_DEPRECATED, E_USER_ERROR, E_USER_NOTICE, E_USER_WARNING, E_WARNING, 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, ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE, + IS_TYPE_REFCOUNTED, 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, ZEND_ACC_PROMOTED, ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, ZEND_ACC_RESOLVED_INTERFACES, @@ -174,6 +175,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; + } +} + bitflags! { /// Represents error types when used via php_error_docref for example. pub struct ErrorType: u32 { @@ -194,6 +205,7 @@ bitflags! { const UserDeprecated = E_USER_DEPRECATED; } } + #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)] pub enum FunctionType { Internal, 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 c9a7a0d91c..79eaeb2256 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -1,5 +1,6 @@ //! Types related to the PHP executor globals. +use std::collections::HashMap; use std::ops::{Deref, DerefMut}; use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -7,7 +8,7 @@ use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::boxed::ZBox; #[cfg(php82)] use crate::ffi::zend_atomic_bool_store; -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}; /// Stores global variables used in the PHP executor. @@ -51,6 +52,31 @@ 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 }; + let mut ini_hash_map: HashMap> = HashMap::new(); + for (_index, key, value) in hash_table.iter() { + 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 + } + /// Attempts to retrieve the global constants table. pub fn constants(&self) -> Option<&ZendHashTable> { unsafe { self.zend_constants.as_ref() } diff --git a/src/zend/ini_entry_def.rs b/src/zend/ini_entry_def.rs new file mode 100644 index 0000000000..8817300316 --- /dev/null +++ b/src/zend/ini_entry_def.rs @@ -0,0 +1,57 @@ +//! Builder for creating inis and methods in PHP. +//! See 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).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 _; + 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`.