From 7c11b620a8d1caa985450bba223cd6e43044f878 Mon Sep 17 00:00:00 2001 From: Dusan Malusev Date: Sun, 3 Dec 2023 19:48:01 +0100 Subject: [PATCH 1/2] WIP -> Initial implementation for INI OnChange Callback Signed-off-by: Dusan Malusev --- README.md | 2 +- phper-alloc/src/lib.rs | 4 +- phper-macros/src/inner.rs | 17 +-- phper-sys/build.rs | 13 ++ phper-sys/php_wrapper.c | 11 +- phper/src/ini.rs | 250 ++++++++++++++++++++++++++++---------- phper/src/modules.rs | 52 ++++---- phper/src/strings.rs | 1 + 8 files changed, 234 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index a57a517c..14889df1 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The framework that allows us to write PHP extensions using pure and safe Rust wh ### Necessary - **rust** 1.74 or later -- **libclang** 9.0 or later +- **libclang** 14.0 or later - **php** 8.1 or later ### Tested Support diff --git a/phper-alloc/src/lib.rs b/phper-alloc/src/lib.rs index 9addecf2..c5100316 100644 --- a/phper-alloc/src/lib.rs +++ b/phper-alloc/src/lib.rs @@ -18,7 +18,6 @@ mod macros; use phper_sys::*; use std::{ borrow::Borrow, - convert::TryInto, mem::{size_of, ManuallyDrop}, ops::{Deref, DerefMut}, }; @@ -37,11 +36,10 @@ impl EBox { /// # Panic /// /// Panic if `size_of::()` equals zero. - #[allow(clippy::useless_conversion)] pub fn new(x: T) -> Self { unsafe { assert_ne!(size_of::(), 0); - let ptr: *mut T = phper_emalloc(size_of::().try_into().unwrap()).cast(); + let ptr: *mut T = phper_emalloc(size_of::()).cast(); // TODO Deal with ptr is zero, when memory limit is reached. ptr.write(x); Self { ptr } diff --git a/phper-macros/src/inner.rs b/phper-macros/src/inner.rs index 29ecca7e..52133d60 100644 --- a/phper-macros/src/inner.rs +++ b/phper-macros/src/inner.rs @@ -10,35 +10,26 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, ItemFn, Visibility}; +use syn::{parse_macro_input, ItemFn}; pub(crate) fn php_get_module(_attr: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as ItemFn); - let vis = &input.vis; let ret = &input.sig.output; let inputs = &input.sig.inputs; let name = &input.sig.ident; let body = &input.block; let attrs = &input.attrs; - if name != "get_module" { - return quote! { compile_error!("function name with attribute `php_get_module` must be `get_module`") }.into(); - } - - if !matches!(vis, Visibility::Public(..)) { - return quote! { compile_error!("function `get_module` must be public"); }.into(); - } - let result = quote! { #[no_mangle] #[doc(hidden)] #(#attrs)* - #vis extern "C" fn #name() -> *const ::phper::sys::zend_module_entry { - fn internal(#inputs) #ret { + pub extern "C" fn get_module() -> *const ::phper::sys::zend_module_entry { + fn #name(#inputs) #ret { #body } - let internal: fn() -> ::phper::modules::Module = internal; + let internal: fn() -> ::phper::modules::Module = #name; unsafe { internal().module_entry() } } }; diff --git a/phper-sys/build.rs b/phper-sys/build.rs index 597549c2..da193f4b 100644 --- a/phper-sys/build.rs +++ b/phper-sys/build.rs @@ -45,6 +45,19 @@ fn main() { // Block the `zend_ini_parse_quantity` because it's document causes the doc test to fail. .blocklist_function("zend_ini_parse_quantity") .clang_args(&includes) + .clang_args(&[ + "-falign-functions", + "-flto=auto", + "-std=c17", + "-pedantic", + "-Wextra", + ]) + .derive_hash(true) + .derive_copy(true) + .derive_eq(true) + .derive_ord(true) + .derive_partialeq(true) + .derive_partialord(true) .derive_default(true); // iterate over the php include directories, and update the builder diff --git a/phper-sys/php_wrapper.c b/phper-sys/php_wrapper.c index 2eb53afa..662609e7 100644 --- a/phper-sys/php_wrapper.c +++ b/phper-sys/php_wrapper.c @@ -17,12 +17,8 @@ #include
#include #include - -#include - -#if PHP_MAJOR_VERSION >= 8 #include -#endif +#include typedef ZEND_INI_MH(phper_zend_ini_mh); @@ -275,7 +271,7 @@ ZEND_FASTCALL void phper_smart_str_0(smart_str *str) { } ZEND_FASTCALL size_t phper_smart_str_get_len(const smart_str *str) { - return smart_str_get_len(str); + return smart_str_get_len((smart_str *)str); } ZEND_FASTCALL zend_string *phper_smart_str_extract(smart_str *str) { @@ -556,7 +552,8 @@ ZEND_FASTCALL zend_internal_arg_info phper_zend_arg_info(bool pass_by_ref, ZEND_FASTCALL zend_resource * phper_register_persistent_resource(const zend_string *id, const void *ptr, int le_id) { - return zend_register_persistent_resource_ex(id, ptr, le_id); + return zend_register_persistent_resource_ex((zend_string *)id, (void *)ptr, + le_id); } ZEND_FASTCALL int phper_zend_register_persistent_list_destructors( diff --git a/phper/src/ini.rs b/phper/src/ini.rs index 53bbeff0..a265cece 100644 --- a/phper/src/ini.rs +++ b/phper/src/ini.rs @@ -10,7 +10,9 @@ //! Apis relate to [zend_ini_entry_def]. +use crate::strings::ZString; use crate::{c_str, sys::*}; +use std::ffi::{c_uchar, c_void}; use std::{ ffi::{c_int, CStr}, mem::zeroed, @@ -19,6 +21,8 @@ use std::{ str, }; +use phper_sys::*; + /// Get the global registered configuration value. /// /// # Examples @@ -49,12 +53,63 @@ pub enum Policy { System = PHP_INI_SYSTEM, } +/// Configuration for INI Display Options. +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum Display { + Original = ZEND_INI_DISPLAY_ORIG, + Active = ZEND_INI_DISPLAY_ACTIVE, +} + +/// Configuration for INI Stage. +#[repr(i32)] +#[derive(Copy, Clone)] +pub enum Stage { + Startup = ZEND_INI_STAGE_STARTUP as i32, + Shutdown = ZEND_INI_STAGE_SHUTDOWN as i32, + Activate = ZEND_INI_STAGE_ACTIVATE as i32, + Deactivate = ZEND_INI_STAGE_DEACTIVATE as i32, + Runtime = ZEND_INI_STAGE_RUNTIME as i32, + Htacces = ZEND_INI_STAGE_HTACCESS as i32, +} + /// The Type which can transform to an ini value. pub trait IntoIniValue { /// transform to an ini value. fn into_ini_value(self) -> String; } +enum PHPIniFunction { + Exists(unsafe extern "C" fn(*const c_char, usize, c_int, *mut bool) -> T), + + DefaultValue(unsafe extern "C" fn(*const c_char, usize, c_int) -> T), +} + +impl TryFrom for Stage { + type Error = Box; + + fn try_from(value: i32) -> Result { + let (startup, shutdown, activate, deactivate, runtime, htaccess) = ( + ZEND_INI_STAGE_STARTUP as i32, + ZEND_INI_STAGE_SHUTDOWN as i32, + ZEND_INI_STAGE_ACTIVATE as i32, + ZEND_INI_STAGE_DEACTIVATE as i32, + ZEND_INI_STAGE_RUNTIME as i32, + ZEND_INI_STAGE_HTACCESS as i32, + ); + + match value { + startup => Ok(Stage::Startup), + shutdown => Ok(Stage::Shutdown), + activate => Ok(Stage::Activate), + deactivate => Ok(Self::Deactivate), + runtime => Ok(Stage::Runtime), + htaccess => Ok(Stage::Htacces), + _ => Err("Invalid Zend Stage for INI values".into()), + } + } +} + impl IntoIniValue for bool { #[inline] fn into_ini_value(self) -> String { @@ -97,7 +152,6 @@ pub trait FromIniValue { } impl FromIniValue for bool { - #[allow(clippy::useless_conversion)] fn from_ini_value(name: &str) -> Self { let s = >::from_ini_value(name); [ @@ -111,107 +165,169 @@ impl FromIniValue for bool { } impl FromIniValue for i64 { - #[allow(clippy::useless_conversion)] fn from_ini_value(name: &str) -> Self { - unsafe { - let name_ptr = name.as_ptr() as *mut u8 as *mut c_char; - zend_ini_long(name_ptr, name.len().try_into().unwrap(), 0) - } + extract_ini_value(name, PHPIniFunction::DefaultValue(zend_ini_long)).unwrap_or_default() } } impl FromIniValue for f64 { - #[allow(clippy::useless_conversion)] fn from_ini_value(name: &str) -> Self { unsafe { - let name_ptr = name.as_ptr() as *mut u8 as *mut c_char; - zend_ini_double(name_ptr, name.len().try_into().unwrap(), 0) + let name_ptr = name.as_ptr() as *mut c_char; + zend_ini_double(name_ptr, name.len(), 0) } } } impl FromIniValue for Option<&CStr> { - #[allow(clippy::useless_conversion)] fn from_ini_value(name: &str) -> Self { - unsafe { - let name_ptr = name.as_ptr() as *mut u8 as *mut c_char; - let ptr = zend_ini_string_ex(name_ptr, name.len().try_into().unwrap(), 0, null_mut()); - (!ptr.is_null()).then(|| CStr::from_ptr(ptr)) + let ptr = extract_ini_value(name, PHPIniFunction::Exists(zend_ini_string_ex)); + ptr.map(|ptr| unsafe { CStr::from_ptr(ptr) }) + } +} + +fn extract_ini_value(name: &str, func: PHPIniFunction) -> Option { + let name_ptr = name.as_ptr() as *const c_char; + + match func { + PHPIniFunction::Exists(f) => { + let mut exists = false; + let ptr = unsafe { f(name_ptr, name.len(), 0, &mut exists as *mut bool) }; + + if exists { + Some(ptr) + } else { + None + } } + PHPIniFunction::DefaultValue(f) => Some(unsafe { f(name_ptr, name.len(), 0) }), } } -pub(crate) struct IniEntity { - name: String, - default_value: String, - policy: Policy, +impl FromIniValue for Option { + fn from_ini_value(name: &str) -> Self { + let ptr = extract_ini_value(name, PHPIniFunction::Exists(zend_ini_string_ex)); + + ptr.map(|ptr| unsafe { CStr::from_ptr(ptr) }.to_string_lossy().to_string()) + } } -impl IniEntity { - pub(crate) fn new( - name: impl Into, default_value: T, policy: Policy, - ) -> Self { - Self { - name: name.into(), - default_value: default_value.into_ini_value(), - policy, - } +impl FromIniValue for String { + fn from_ini_value(name: &str) -> Self { + let ptr = extract_ini_value(name, PHPIniFunction::Exists(zend_ini_string_ex)); + + ptr.map(|ptr| unsafe { CStr::from_ptr(ptr) }.to_string_lossy().to_string()) + .unwrap_or_default() } +} - #[inline] - pub(crate) fn entry(&self) -> zend_ini_entry_def { - create_ini_entry_ex(&self.name, &self.default_value, self.policy as u32) - } -} - -fn create_ini_entry_ex(name: &str, default_value: &str, modifiable: u32) -> zend_ini_entry_def { - #[cfg(any( - phper_major_version = "8", - all( - phper_major_version = "7", - any(phper_minor_version = "4", phper_minor_version = "3") - ) - ))] - let (modifiable, name_length) = (modifiable as std::os::raw::c_uchar, name.len() as u16); - - #[cfg(all( - phper_major_version = "7", - any( - phper_minor_version = "2", - phper_minor_version = "1", - phper_minor_version = "0", - ) - ))] - let (modifiable, name_length) = (modifiable as std::os::raw::c_int, name.len() as u32); +impl FromIniValue for Option<&str> { + fn from_ini_value(name: &str) -> Self { + let ptr = extract_ini_value(name, PHPIniFunction::Exists(zend_ini_string_ex)); + ptr.map(|ptr| unsafe { CStr::from_ptr(ptr) }.to_str().unwrap()) // Totally OK to crash here + } +} + +impl FromIniValue for &str { + fn from_ini_value(name: &str) -> Self { + let ptr = extract_ini_value(name, PHPIniFunction::Exists(zend_ini_string_ex)); + ptr.map(|ptr| unsafe { CStr::from_ptr(ptr) }.to_str().unwrap()) + .unwrap_or_default() // Totally OK to crash here + } +} + +unsafe extern "C" fn on_modify( + entry: *mut _zend_ini_entry, new_value: *mut _zend_string, arg1: *mut c_void, + _arg2: *mut c_void, _arg3: *mut c_void, stage: i32, +) -> i32 { + let stage = match Stage::try_from(stage) { + Ok(val) => val, + Err(_) => return ZEND_RESULT_CODE_FAILURE, + }; + + let on_modify_item = &mut *(arg1 as *mut OnModifyCarry); + + let modify = &mut on_modify_item.on_modify; + + modify + .on_modify(ZString::from_raw(new_value), stage) + .map(|_| ZEND_RESULT_CODE_SUCCESS) + .map_err(|_| ZEND_RESULT_CODE_FAILURE) + .unwrap() +} + +pub trait OnModify { + fn on_modify( + &mut self, new_value: ZString, stage: Stage, + ) -> Result<(), Box>; +} + +impl OnModify for () { + fn on_modify( + &mut self, new_value: ZString, stage: Stage, + ) -> Result<(), Box> { + Ok(()) + } +} + +struct OnModifyCarry +where + T: OnModify, +{ + on_modify: T, +} + +type ZendOnModify = unsafe extern "C" fn( + entry: *mut zend_ini_entry, + new_value: *mut zend_string, + mh_arg1: *mut c_void, + mh_arg2: *mut c_void, + mh_arg3: *mut c_void, + stage: c_int, +) -> c_int; + +pub(crate) fn create_ini_entry_ex( + name: impl AsRef, default_value: impl AsRef, modifiable: u32, + on_modify_impl: Option, +) -> zend_ini_entry_def +where + T: OnModify, +{ + let name = name.as_ref(); + let default_value = default_value.as_ref(); + let (modifiable, name_length) = (modifiable as c_uchar, name.len() as u16); + + let (callback, arg): (Option, *mut OnModifyCarry) = match on_modify_impl { + Some(callback) => ( + Some(on_modify:: as ZendOnModify), + Box::into_raw(Box::new(OnModifyCarry { + on_modify: callback, + })), + ), + None => (None, null_mut()), + }; zend_ini_entry_def { name: name.as_ptr().cast(), - on_modify: None, - mh_arg1: null_mut(), + name_length, + on_modify: callback, + mh_arg1: arg as *mut c_void, mh_arg2: null_mut(), mh_arg3: null_mut(), value: default_value.as_ptr().cast(), + value_length: default_value.len() as u32, displayer: None, modifiable, - name_length, - value_length: default_value.len() as u32, } } -unsafe fn entries(ini_entries: &[IniEntity]) -> *const zend_ini_entry_def { - let mut entries = Vec::with_capacity(ini_entries.len() + 1); - - ini_entries.iter().for_each(|entity| { - // Ini entity will exist throughout the whole application life cycle. - entries.push(entity.entry()); - }); - - entries.push(zeroed::()); +unsafe fn entries(mut ini_entries: Vec) -> *const zend_ini_entry_def { + ini_entries.push(zeroed::()); - Box::into_raw(entries.into_boxed_slice()).cast() + Box::into_raw(ini_entries.into_boxed_slice()).cast() } -pub(crate) fn register(ini_entries: &[IniEntity], module_number: c_int) { +pub(crate) fn register(ini_entries: Vec, module_number: c_int) { unsafe { zend_register_ini_entries(entries(ini_entries), module_number); } diff --git a/phper/src/modules.rs b/phper/src/modules.rs index c8e71768..4f541ca4 100644 --- a/phper/src/modules.rs +++ b/phper/src/modules.rs @@ -33,7 +33,7 @@ use std::{ /// Global pointer hold the Module builder. /// Because PHP is single threaded, so there is no lock here. -static mut GLOBAL_MODULE: *mut Module = null_mut(); +static mut GLOBAL_MODULE: Option> = None; pub(crate) static mut GLOBAL_MODULE_NUMBER: i32 = 0; static mut GLOBAL_MODULE_ENTRY: *mut zend_module_entry = null_mut(); @@ -51,7 +51,7 @@ unsafe extern "C" fn module_startup(_type: c_int, module_number: c_int) -> c_int let module = GLOBAL_MODULE.as_mut().unwrap(); GLOBAL_MODULE_NUMBER = module_number; - ini::register(&module.ini_entities, module_number); + ini::register(take(&mut module.ini_entities), module_number); for constant in &module.constants { constant.register(module_number); @@ -77,17 +77,21 @@ unsafe extern "C" fn module_startup(_type: c_int, module_number: c_int) -> c_int } unsafe extern "C" fn module_shutdown(_type: c_int, module_number: c_int) -> c_int { - let module = GLOBAL_MODULE.as_mut().unwrap(); + { + let module = GLOBAL_MODULE.as_mut().unwrap(); - ini::unregister(module_number); + ini::unregister(module_number); - if let Some(f) = take(&mut module.module_shutdown) { - f(ModuleInfo { - ty: _type, - number: module_number, - }); + if let Some(f) = take(&mut module.module_shutdown) { + f(ModuleInfo { + ty: _type, + number: module_number, + }); + } } + // std::mem::replace(GLOBAL_MODULE.as_mut().unwrap(), null_mut()); + ZEND_RESULT_CODE_SUCCESS } @@ -149,16 +153,14 @@ pub struct Module { class_entities: Vec>, interface_entities: Vec, constants: Vec, - ini_entities: Vec, + ini_entities: Vec, infos: HashMap, } impl Module { /// Construct the `Module` with base metadata. pub fn new( - name: impl Into, - version: impl Into, - author: impl Into, + name: impl Into, version: impl Into, author: impl Into, ) -> Self { Self { name: ensure_end_with_zero(name), @@ -199,9 +201,7 @@ impl Module { /// Register function to module. pub fn add_function( - &mut self, - name: impl Into, - handler: F, + &mut self, name: impl Into, handler: F, ) -> &mut FunctionEntity where F: Fn(&mut [ZVal]) -> Result + 'static, @@ -230,13 +230,16 @@ impl Module { /// Register ini configuration to module. pub fn add_ini( - &mut self, - name: impl Into, - default_value: impl ini::IntoIniValue, + &mut self, name: impl AsRef, default_value: impl ini::IntoIniValue, policy: ini::Policy, ) { - self.ini_entities - .push(ini::IniEntity::new(name, default_value, policy)); + let ini = ini::create_ini_entry_ex( + name.as_ref(), + default_value.into_ini_value(), + policy as u32, + Option::<()>::None, + ); + self.ini_entities.push(ini); } /// Register info item. @@ -250,7 +253,6 @@ impl Module { self.infos.insert(key, value); } - /// Leak memory to generate `zend_module_entry` pointer. #[doc(hidden)] pub unsafe fn module_entry(self) -> *const zend_module_entry { if !GLOBAL_MODULE_ENTRY.is_null() { @@ -263,9 +265,9 @@ impl Module { "module version must be set" ); - let module = Box::new(self); + let module: Box = Box::new(self); - let entry: Box = Box::new(zend_module_entry { + let entry = Box::new(zend_module_entry { size: size_of::() as c_ushort, zend_api: ZEND_MODULE_API_NO as c_uint, zend_debug: ZEND_DEBUG as c_uchar, @@ -295,7 +297,7 @@ impl Module { build_id: phper_get_zend_module_build_id(), }); - GLOBAL_MODULE = Box::into_raw(module); + GLOBAL_MODULE = Some(module); GLOBAL_MODULE_ENTRY = Box::into_raw(entry); GLOBAL_MODULE_ENTRY diff --git a/phper/src/strings.rs b/phper/src/strings.rs index 0a00e8c4..a33fdf63 100644 --- a/phper/src/strings.rs +++ b/phper/src/strings.rs @@ -25,6 +25,7 @@ use std::{ slice::from_raw_parts, }; +/// Unsafe implementation for &'a str conversion from T pub unsafe trait IntoStr<'a> { fn into_str(&'a self) -> &'a str; } From 868f74c313759fc6adce67eeb3651d9a93736954 Mon Sep 17 00:00:00 2001 From: Dusan Malusev Date: Sun, 3 Dec 2023 21:40:37 +0100 Subject: [PATCH 2/2] Missing docs Signed-off-by: Dusan Malusev --- phper/src/ini.rs | 115 +++++++++++++++++++++++++++-------------- phper/src/resources.rs | 2 + phper/src/smart_str.rs | 20 +++---- phper/src/strings.rs | 20 ++++--- 4 files changed, 99 insertions(+), 58 deletions(-) diff --git a/phper/src/ini.rs b/phper/src/ini.rs index a265cece..59adcddf 100644 --- a/phper/src/ini.rs +++ b/phper/src/ini.rs @@ -10,8 +10,8 @@ //! Apis relate to [zend_ini_entry_def]. +use crate::c_str; use crate::strings::ZString; -use crate::{c_str, sys::*}; use std::ffi::{c_uchar, c_void}; use std::{ ffi::{c_int, CStr}, @@ -40,7 +40,7 @@ pub fn ini_get(name: &str) -> T { /// Configuration changeable policy. #[repr(u32)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Policy { /// Entry can be set anywhere. All = PHP_INI_ALL, @@ -53,23 +53,21 @@ pub enum Policy { System = PHP_INI_SYSTEM, } -/// Configuration for INI Display Options. -#[repr(u32)] -#[derive(Copy, Clone)] -pub enum Display { - Original = ZEND_INI_DISPLAY_ORIG, - Active = ZEND_INI_DISPLAY_ACTIVE, -} - /// Configuration for INI Stage. #[repr(i32)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Stage { + /// INI Load Event -> Startup -> PHP Started Startup = ZEND_INI_STAGE_STARTUP as i32, + /// INI Event -> PHP Shutting down Shutdown = ZEND_INI_STAGE_SHUTDOWN as i32, + /// INI Event -> PHP Module Activated Activate = ZEND_INI_STAGE_ACTIVATE as i32, + /// INI Event -> PHP Module Deactivated Deactivate = ZEND_INI_STAGE_DEACTIVATE as i32, + /// INI Event -> Value changed with ini_set from PHP Runtime = ZEND_INI_STAGE_RUNTIME as i32, + /// INI Event -> Value changed from .htaccess file with php_ini directive Htacces = ZEND_INI_STAGE_HTACCESS as i32, } @@ -85,31 +83,37 @@ enum PHPIniFunction { DefaultValue(unsafe extern "C" fn(*const c_char, usize, c_int) -> T), } -impl TryFrom for Stage { - type Error = Box; - - fn try_from(value: i32) -> Result { - let (startup, shutdown, activate, deactivate, runtime, htaccess) = ( - ZEND_INI_STAGE_STARTUP as i32, - ZEND_INI_STAGE_SHUTDOWN as i32, - ZEND_INI_STAGE_ACTIVATE as i32, - ZEND_INI_STAGE_DEACTIVATE as i32, - ZEND_INI_STAGE_RUNTIME as i32, - ZEND_INI_STAGE_HTACCESS as i32, - ); - - match value { - startup => Ok(Stage::Startup), - shutdown => Ok(Stage::Shutdown), - activate => Ok(Stage::Activate), - deactivate => Ok(Self::Deactivate), - runtime => Ok(Stage::Runtime), - htaccess => Ok(Stage::Htacces), - _ => Err("Invalid Zend Stage for INI values".into()), +macro_rules! try_from_stage_int { + ($arg: ty) => { + impl TryFrom<$arg> for Stage { + type Error = Box; + + fn try_from(value: $arg) -> Result { + match value as u32 { + ZEND_INI_STAGE_STARTUP => Ok(Stage::Startup), + ZEND_INI_STAGE_SHUTDOWN => Ok(Stage::Shutdown), + ZEND_INI_STAGE_ACTIVATE => Ok(Stage::Activate), + ZEND_INI_STAGE_DEACTIVATE => Ok(Self::Deactivate), + ZEND_INI_STAGE_RUNTIME => Ok(Stage::Runtime), + ZEND_INI_STAGE_HTACCESS => Ok(Stage::Htacces), + _ => Err("Invalid Zend Stage for INI values".into()), + } + } } - } + }; } +try_from_stage_int!(i8); +try_from_stage_int!(i16); +try_from_stage_int!(i32); +try_from_stage_int!(i64); +try_from_stage_int!(isize); +try_from_stage_int!(u8); +try_from_stage_int!(u16); +try_from_stage_int!(u32); +try_from_stage_int!(u64); +try_from_stage_int!(usize); + impl IntoIniValue for bool { #[inline] fn into_ini_value(self) -> String { @@ -236,6 +240,28 @@ impl FromIniValue for &str { } } +/// Zend INI Entry +pub struct Entry { + /// Has Entry been modified + pub modified: bool, + /// Name of the INI Entry + pub name: ZString, + /// Current value before change + pub value: ZString, +} + +impl From<&_zend_ini_entry> for Entry { + fn from(value: &_zend_ini_entry) -> Self { + unsafe { + Self { + modified: value.modified > 0, + name: ZString::from_raw(value.name), + value: ZString::from_raw(value.value), + } + } + } +} + unsafe extern "C" fn on_modify( entry: *mut _zend_ini_entry, new_value: *mut _zend_string, arg1: *mut c_void, _arg2: *mut c_void, _arg3: *mut c_void, stage: i32, @@ -249,22 +275,35 @@ unsafe extern "C" fn on_modify( let modify = &mut on_modify_item.on_modify; - modify - .on_modify(ZString::from_raw(new_value), stage) + let result = modify + .on_modify( + Entry::from(&*(entry as *const _zend_ini_entry)), + ZString::from_raw(new_value), + stage, + ) .map(|_| ZEND_RESULT_CODE_SUCCESS) .map_err(|_| ZEND_RESULT_CODE_FAILURE) - .unwrap() + .unwrap(); + + // Prevent memory leaks + if stage == Stage::Shutdown || stage == Stage::Deactivate { + let _item = Box::from_raw(on_modify_item); + } + + result } +/// On INI Change Trait pub trait OnModify { + /// Called whenever INI has chaged fn on_modify( - &mut self, new_value: ZString, stage: Stage, + &mut self, entry: Entry, new_value: ZString, stage: Stage, ) -> Result<(), Box>; } impl OnModify for () { fn on_modify( - &mut self, new_value: ZString, stage: Stage, + &mut self, _entry: Entry, _new_value: ZString, _stage: Stage, ) -> Result<(), Box> { Ok(()) } diff --git a/phper/src/resources.rs b/phper/src/resources.rs index d0ffec6d..cfab2bd9 100644 --- a/phper/src/resources.rs +++ b/phper/src/resources.rs @@ -22,6 +22,8 @@ pub struct ZRes { _data: PhantomData, } +/// Zend Persistent Resource +/// Usecase: Database Persistent Connections, HTTP Connections etc #[repr(transparent)] pub struct ZPersistentResource { inner: *const zend_resource, diff --git a/phper/src/smart_str.rs b/phper/src/smart_str.rs index e609ae05..71910a01 100644 --- a/phper/src/smart_str.rs +++ b/phper/src/smart_str.rs @@ -173,9 +173,7 @@ impl ZSmartStr { #[inline] pub fn append_string_escaped( - &mut self, - str: impl Into<*const c_char>, - len: usize, + &mut self, str: impl Into<*const c_char>, len: usize, ) -> &mut Self { unsafe { smart_str_append_escaped(&mut self.inner, str.into(), len); @@ -187,9 +185,7 @@ impl ZSmartStr { /// Appends zend_string up to the supplied length and escapes it. #[inline] pub fn append_string_escaped_truncated( - &mut self, - str: impl Into<*mut zend_string>, - len: usize, + &mut self, str: impl Into<*mut zend_string>, len: usize, ) -> &mut Self { unsafe { smart_str_append_escaped_truncated(&mut self.inner, str.into(), len); @@ -239,10 +235,7 @@ impl ZSmartStr { /// Appends float to the smart str. #[inline] pub fn append_float( - &mut self, - num: impl Into, - precision: i32, - zero_fraction: bool, + &mut self, num: impl Into, precision: i32, zero_fraction: bool, ) -> &mut Self { unsafe { phper_smart_str_append_double( @@ -270,10 +263,7 @@ impl ZSmartStr { #[inline] pub fn append_double( - &mut self, - num: impl Into, - precision: i32, - zero_fraction: bool, + &mut self, num: impl Into, precision: i32, zero_fraction: bool, ) -> &mut Self { unsafe { phper_smart_str_append_double(&mut self.inner, num.into(), precision, zero_fraction) @@ -306,12 +296,14 @@ impl ZSmartStr { } } +#[allow(clippy::from_over_into)] impl Into<*const smart_str> for &ZSmartStr { fn into(self) -> *const smart_str { &self.inner } } +#[allow(clippy::from_over_into)] impl Into<*mut smart_str> for &mut ZSmartStr { fn into(self) -> *mut smart_str { &mut self.inner diff --git a/phper/src/strings.rs b/phper/src/strings.rs index a33fdf63..8cb37d3b 100644 --- a/phper/src/strings.rs +++ b/phper/src/strings.rs @@ -26,8 +26,13 @@ use std::{ }; /// Unsafe implementation for &'a str conversion from T -pub unsafe trait IntoStr<'a> { - fn into_str(&'a self) -> &'a str; +/// +/// # Safety +/// Coverting to str from ZString or ZStr when you are sure UTF-8 Is the only possible impl +pub unsafe trait ToStr<'a> { + /// Converts a Type to &str + #[allow(clippy::wrong_self_convention)] + fn to_unsafe_str(&'a self) -> &'a str; } /// Like str, CStr for [zend_string]. @@ -138,24 +143,27 @@ impl ZStr { } } -unsafe impl<'a> IntoStr<'a> for ZStr { - fn into_str(&'a self) -> &'a str { +unsafe impl<'a> ToStr<'a> for ZStr { + fn to_unsafe_str(&'a self) -> &'a str { unsafe { std::str::from_utf8_unchecked(self.to_bytes()) } } } +#[allow(clippy::from_over_into)] impl Into<*mut zend_string> for &mut ZStr { fn into(self) -> *mut zend_string { &mut self.inner } } +#[allow(clippy::from_over_into)] impl Into<*const zend_string> for &mut ZStr { fn into(self) -> *const zend_string { &mut self.inner } } +#[allow(clippy::from_over_into)] impl Into<*const c_char> for &ZStr { fn into(self) -> *const c_char { self.as_c_str_ptr() @@ -244,8 +252,8 @@ impl ZString { } } -unsafe impl<'a> IntoStr<'a> for ZString { - fn into_str(&'a self) -> &'a str { +unsafe impl<'a> ToStr<'a> for ZString { + fn to_unsafe_str(&'a self) -> &'a str { unsafe { std::str::from_utf8_unchecked(self.to_bytes()) } } }