folk-runtime-embed 0.1.16

Embedded PHP runtime for Folk — PHP interpreter runs in-process via FFI
Documentation
//! Raw FFI bindings to PHP embed SAPI and our C helper functions.
//!
//! We use manual declarations rather than bindgen for the POC — only the
//! functions we actually call are declared here.

#![allow(non_camel_case_types, clippy::missing_safety_doc, unsafe_code)]

use std::ffi::c_char;
use std::os::raw::{c_int, c_uint};

/// Opaque PHP zval (Zend value). 16 bytes on 64-bit.
/// We never access fields directly — only through our C helper functions.
#[repr(C)]
pub struct zval {
    // zval is 16 bytes: value union (8) + type info (8)
    _data: [u8; 16],
}

impl zval {
    /// Create an uninitialized zval (will be set to `IS_UNDEF` by `folk_zval_undef`).
    pub fn new_undef() -> Self {
        let mut z = Self { _data: [0; 16] };
        unsafe { folk_zval_undef(&mut z) };
        z
    }
}

/// Per-request context passed from Rust to our SAPI callbacks.
/// Must match `folk_request_context_t` in `php_embed_helper.c`.
#[repr(C)]
pub struct folk_request_context {
    pub request_method: *const c_char,
    pub request_uri: *const c_char,
    pub query_string: *const c_char,
    pub content_type: *const c_char,
    pub content_length: usize,
    pub path_translated: *const c_char,

    pub post_data: *const c_char,
    pub post_data_len: usize,
    pub post_data_read: usize,

    pub cookie_data: *const c_char,

    pub header_names: *const *const c_char,
    pub header_values: *const *const c_char,
    pub header_count: usize,

    pub server_name: *const c_char,
    pub server_port: c_int,
    pub protocol: *const c_char,
}

// ── PHP embed SAPI functions (kept for backward compat with POC tests) ──

unsafe extern "C" {
    pub fn php_embed_init(argc: c_int, argv: *mut *mut c_char) -> c_int;
    pub fn php_embed_shutdown();
}

// ── Custom Folk SAPI ─────────────────────────────────────────────

unsafe extern "C" {
    /// Initialize PHP with our custom Folk SAPI (replaces `php_embed_init`).
    pub fn folk_sapi_init() -> c_int;

    /// Initialize PHP with custom INI overrides (double-NUL terminated).
    pub fn folk_sapi_init_with_ini(ini_overrides: *const c_char) -> c_int;

    /// Shutdown our custom SAPI (replaces `php_embed_shutdown`).
    pub fn folk_sapi_shutdown();

    /// Set the current request context (before `request_startup`).
    pub fn folk_request_context_set(ctx: *mut folk_request_context);

    /// Clear the current request context (after `request_shutdown`).
    pub fn folk_request_context_clear();
}

// ── Response context ─────────────────────────────────────────────

unsafe extern "C" {
    /// Get the response HTTP status code.
    pub fn folk_response_status_code() -> c_int;

    /// Get the number of captured response headers.
    pub fn folk_response_header_count() -> usize;

    /// Get a response header by index. Sets `*out_len`.
    pub fn folk_response_header_get(index: usize, out_len: *mut usize) -> *const c_char;

    /// Clear response state (between requests).
    pub fn folk_response_clear();

    /// Free all response resources (thread shutdown).
    pub fn folk_response_free();
}

// ── Our C helper functions (from php_embed_helper.c) ─────────────

unsafe extern "C" {
    pub fn folk_eval_string_safe(code: *const c_char, retval: *mut zval) -> c_int;
    pub fn folk_execute_script_safe(filename: *const c_char) -> c_int;

    pub fn folk_call_function_safe(
        func_name: *const c_char,
        retval: *mut zval,
        param_count: c_uint,
        params: *mut zval,
    ) -> c_int;

    pub fn folk_request_startup_safe() -> c_int;
    pub fn folk_request_shutdown_safe() -> c_int;

    pub fn folk_get_output(out_len: *mut usize) -> *const c_char;
    pub fn folk_clear_output();
    pub fn folk_free_output();

    pub fn folk_zval_get_string(val: *mut zval, out_len: *mut usize) -> *const c_char;
    pub fn folk_zval_get_long(val: *mut zval) -> i64;
    pub fn folk_zval_type(val: *mut zval) -> c_int;
    pub fn folk_zval_dtor(val: *mut zval);
    pub fn folk_zval_undef(val: *mut zval);
    pub fn folk_zval_set_string(val: *mut zval, str: *const c_char, len: usize);

    /// Install our output capture handler into the embed SAPI module.
    /// Only needed for legacy embed SAPI (`boot()`), not for custom SAPI.
    pub fn folk_install_output_handler();
}

// ── Signal management ────────────────────────────────────────────

unsafe extern "C" {
    /// Save current signal handlers (call before `folk_sapi_init`).
    pub fn folk_signals_save();

    /// Restore saved signal handlers (call after `folk_sapi_init`).
    pub fn folk_signals_restore();

    /// Install SIGSEGV handler for worker thread protection.
    pub fn folk_sigsegv_handler_install();

    /// Eval with SIGSEGV protection. Returns -3 on SIGSEGV.
    pub fn folk_eval_string_protected(code: *const c_char, retval: *mut zval) -> c_int;

    /// Call function with SIGSEGV protection. Returns -3 on SIGSEGV.
    pub fn folk_call_function_protected(
        func_name: *const c_char,
        retval: *mut zval,
        param_count: c_uint,
        params: *mut zval,
    ) -> c_int;
}

// ── Direct data passing (Phase A+B) ──────────────────────────────

unsafe extern "C" {
    /// Call PHP function with raw binary args (no base64). Returns -3 on SIGSEGV.
    pub fn folk_call_with_binary(
        func_name: *const c_char,
        method_name: *const c_char,
        method_name_len: usize,
        params: *const c_char,
        params_len: usize,
        response_buf: *mut *mut c_char,
        response_len: *mut usize,
    ) -> c_int;

    /// Free a buffer allocated by `folk_call_with_binary`.
    pub fn folk_free_buffer(buf: *mut c_char);

    /// Create a new PHP array zval.
    pub fn folk_array_init(arr: *mut zval);

    /// Add a string to a PHP array by key.
    pub fn folk_array_add_string(
        arr: *mut zval,
        key: *const c_char,
        key_len: usize,
        val: *const c_char,
        val_len: usize,
    );

    /// Add a long to a PHP array by key.
    pub fn folk_array_add_long(arr: *mut zval, key: *const c_char, key_len: usize, val: i64);

    /// Add a sub-array to a PHP array by key.
    pub fn folk_array_add_array(
        arr: *mut zval,
        key: *const c_char,
        key_len: usize,
        sub_arr: *mut zval,
    );

    /// Append a string to a PHP array (numeric index).
    pub fn folk_array_append_string(arr: *mut zval, val: *const c_char, val_len: usize);

    /// Call PHP function with single array argument. Returns -3 on SIGSEGV.
    pub fn folk_call_with_array(
        func_name: *const c_char,
        request_arr: *mut zval,
        retval: *mut zval,
    ) -> c_int;

    /// Read string from PHP array by key.
    pub fn folk_array_get_string(
        arr: *mut zval,
        key: *const c_char,
        key_len: usize,
        out_len: *mut usize,
    ) -> *const c_char;

    /// Read long from PHP array by key.
    pub fn folk_array_get_long(arr: *mut zval, key: *const c_char, key_len: usize) -> i64;

    /// Get array element count.
    pub fn folk_array_count(arr: *mut zval) -> usize;

    /// Get string from array by numeric index.
    pub fn folk_array_index_string(
        arr: *mut zval,
        index: usize,
        out_len: *mut usize,
    ) -> *const c_char;
}

// ── TSRM thread context (ZTS only) ───────────────────────────────

unsafe extern "C" {
    /// Allocate TSRM context for current thread (ZTS). Returns NULL on NTS.
    pub fn folk_thread_init() -> *mut std::ffi::c_void;

    /// Set TSRM context for current thread.
    pub fn folk_thread_set_ctx(ctx: *mut std::ffi::c_void);

    /// Free TSRM context.
    pub fn folk_thread_shutdown(ctx: *mut std::ffi::c_void);

    /// Returns 1 if ZTS build, 0 if NTS.
    pub fn folk_is_zts() -> c_int;
}

// ── PHP zval type constants ──────────────────────────────────────

pub const IS_UNDEF: c_int = 0;
pub const IS_NULL: c_int = 1;
pub const IS_FALSE: c_int = 2;
pub const IS_TRUE: c_int = 3;
pub const IS_LONG: c_int = 4;
pub const IS_DOUBLE: c_int = 5;
pub const IS_STRING: c_int = 6;
pub const IS_ARRAY: c_int = 7;
pub const IS_OBJECT: c_int = 8;