1#![allow(unsafe_code)]
6
7use std::ffi::{CString, c_int};
8
9use ext_php_rs::types::Zval;
10
11unsafe extern "C" {
12 fn folk_zts_thread_init();
13 fn folk_zts_thread_shutdown();
14 fn folk_zts_request_startup() -> c_int;
15 fn folk_zts_request_shutdown();
16 fn folk_zts_execute_script(filename: *const std::ffi::c_char) -> c_int;
17 fn folk_zts_call_dispatch(
18 func_name: *const std::ffi::c_char,
19 method_zval: *mut Zval,
20 params_zval: *mut Zval,
21 retval: *mut Zval,
22 ) -> c_int;
23 fn folk_zts_eval_string(code: *const std::ffi::c_char) -> c_int;
24 fn folk_zts_chdir(path: *const std::ffi::c_char) -> c_int;
25 fn folk_zts_is_enabled() -> c_int;
26}
27
28pub fn chdir(path: &str) -> anyhow::Result<()> {
33 let c_path = CString::new(path).map_err(|_| anyhow::anyhow!("path contains null byte"))?;
34 let ret = unsafe { folk_zts_chdir(c_path.as_ptr()) };
35 if ret == 0 {
36 Ok(())
37 } else {
38 anyhow::bail!("VCWD_CHDIR failed for {path}")
39 }
40}
41
42pub fn is_zts() -> bool {
44 unsafe { folk_zts_is_enabled() != 0 }
45}
46
47pub struct ZtsThreadGuard {
52 _private: (),
53}
54
55impl ZtsThreadGuard {
56 pub fn new() -> Self {
58 unsafe {
59 folk_zts_thread_init();
60 }
61 Self { _private: () }
62 }
63}
64
65impl Default for ZtsThreadGuard {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71impl Drop for ZtsThreadGuard {
72 fn drop(&mut self) {
73 unsafe {
74 folk_zts_thread_shutdown();
75 }
76 }
77}
78
79pub fn request_startup() -> anyhow::Result<()> {
84 let ret = unsafe { folk_zts_request_startup() };
85 if ret == 0 {
86 Ok(())
87 } else {
88 anyhow::bail!("php_request_startup failed (code {ret})")
89 }
90}
91
92pub fn request_shutdown() {
94 unsafe {
95 folk_zts_request_shutdown();
96 }
97}
98
99pub fn eval_string(code: &str) -> anyhow::Result<()> {
106 let c_code = CString::new(code).map_err(|_| anyhow::anyhow!("code contains null byte"))?;
107 let ret = unsafe { folk_zts_eval_string(c_code.as_ptr()) };
108 if ret != 0 {
109 Ok(())
110 } else {
111 anyhow::bail!("zend_eval_string failed")
112 }
113}
114
115pub fn execute_script(filename: &str) -> anyhow::Result<()> {
120 let c_filename =
121 CString::new(filename).map_err(|_| anyhow::anyhow!("filename contains null byte"))?;
122 let ret = unsafe { folk_zts_execute_script(c_filename.as_ptr()) };
123 if ret != 0 {
124 Ok(())
125 } else {
126 anyhow::bail!("php_execute_script failed for {filename}")
127 }
128}
129
130pub fn call_dispatch(
138 func_name: &str,
139 method: &str,
140 params: &serde_json::Value,
141) -> anyhow::Result<serde_json::Value> {
142 let c_func =
143 CString::new(func_name).map_err(|_| anyhow::anyhow!("func_name contains null byte"))?;
144
145 let mut method_zval = Zval::new();
147 method_zval
148 .set_string(method, false)
149 .map_err(|e| anyhow::anyhow!("set_string failed: {e}"))?;
150
151 let mut params_zval = crate::zval_convert::value_to_zval(params);
152 let mut retval = Zval::new();
153
154 let ret = unsafe {
155 folk_zts_call_dispatch(
156 c_func.as_ptr(),
157 &raw mut method_zval,
158 &raw mut params_zval,
159 &raw mut retval,
160 )
161 };
162
163 if ret != 0 {
164 anyhow::bail!("call_user_function({func_name}) failed (code {ret})");
165 }
166
167 Ok(crate::zval_convert::zval_to_value(&retval))
168}