2026-08-05 11:38:19 -07:00
|
|
|
/*
|
|
|
|
|
* cac_path_parity — $_SERVER filesystem-path parity for the shared-ols tier.
|
|
|
|
|
*
|
|
|
|
|
* WHAT PROBLEM THIS SOLVES
|
|
|
|
|
* ------------------------
|
|
|
|
|
* A site on the standalone tiers (cac / cac-fpm / cac-litespeed) sees:
|
|
|
|
|
*
|
|
|
|
|
* $_SERVER['DOCUMENT_ROOT'] = /home/<user>/public_html
|
|
|
|
|
* $_SERVER['SCRIPT_FILENAME'] = /home/<user>/public_html/index.php
|
|
|
|
|
*
|
|
|
|
|
* On the shared-ols tier the webserver is a SHARED OpenLiteSpeed container that
|
|
|
|
|
* serves every tenant out of one bulk `/docker/users -> /mnt/users:ro` mount, so
|
|
|
|
|
* its vhost docRoot is /mnt/users/<user>/<domain>/public_html. OLS has no
|
|
|
|
|
* ProxyFCGISetEnvIf-style remap (unlike shared-httpd -> cac-fpm): it hands lsphp
|
|
|
|
|
* exactly that path. The cac-lsphp sidecar symlinks /mnt/users/<user>/<domain>
|
|
|
|
|
* -> /home/<user>, so every file OPERATION resolves and PHP's own __FILE__ /
|
|
|
|
|
* __DIR__ / realpath() / getcwd() already report /home/<user>/... — but the RAW
|
|
|
|
|
* strings OLS put in $_SERVER still read /mnt/users. Moving a site from cac-fpm
|
|
|
|
|
* to cac-lsphp therefore changed two $_SERVER values, which is exactly the kind
|
|
|
|
|
* of difference that surfaces later as a broken plugin path or a mismatched
|
|
|
|
|
* absolute path stored in the database.
|
|
|
|
|
*
|
|
|
|
|
* WHY THIS IS AN EXTENSION AND NOT AN auto_prepend_file
|
|
|
|
|
* -----------------------------------------------------
|
|
|
|
|
* The first fix was an `auto_prepend_file` drop-in that realpath()'d the two
|
|
|
|
|
* keys. `auto_prepend_file` is PHP_INI_PERDIR, so ANY site with its own
|
|
|
|
|
* .user.ini auto_prepend_file silently displaces it and the normaliser never
|
|
|
|
|
* runs — the state 7 live shared_ols sites are actually in today (Wordfence and
|
|
|
|
|
* cPanel imports). PHP resolves a single winning value for auto_prepend_file
|
|
|
|
|
* after the .user.ini chain is parsed, so there is no way to "chain" from the
|
|
|
|
|
* losing side either.
|
|
|
|
|
*
|
|
|
|
|
* The obvious hardening — `php_admin_value auto_prepend_file` — is WORSE, not
|
|
|
|
|
* better: making our prepend un-overridable makes the customer's prepend
|
|
|
|
|
* un-runnable, which would disable those same 7 Wordfence WAFs. The two goals
|
|
|
|
|
* are irreconcilable as long as the mechanism IS the prepend hook.
|
|
|
|
|
*
|
|
|
|
|
* An extension sidesteps that entirely. RINIT runs before any userland code and
|
|
|
|
|
* cannot be displaced by .user.ini, and it consumes no userland hook — so the
|
|
|
|
|
* customer's auto_prepend_file remains the only prepend in play and keeps
|
|
|
|
|
* working untouched. Both constraints are satisfied at once.
|
|
|
|
|
*
|
|
|
|
|
* The mapping comes from two PHP_INI_SYSTEM entries. PHP_INI_SYSTEM is NOT
|
|
|
|
|
* settable from .user.ini (which honours only PHP_INI_PERDIR/PHP_INI_USER), nor
|
|
|
|
|
* from ini_set(), nor from .htaccess — so a customer cannot point the rewrite
|
|
|
|
|
* somewhere else or switch it off. The cac-lsphp entrypoint writes them from the
|
|
|
|
|
* same `user`/`domain` env the symlink is built from, so the two can't drift.
|
|
|
|
|
*
|
|
|
|
|
* WHY A STRING PREFIX SWAP AND NOT realpath()
|
|
|
|
|
* -------------------------------------------
|
|
|
|
|
* The old normaliser called realpath(), which worked only because the sidecar
|
|
|
|
|
* symlinks /mnt/users/<user>/<domain> -> /home/<user>. A plain prefix swap is
|
|
|
|
|
* better on every axis that matters here:
|
|
|
|
|
*
|
|
|
|
|
* - It is byte-identical to cac-fpm BY CONSTRUCTION. realpath() resolves ALL
|
|
|
|
|
* symlinks, so a customer who makes public_html itself a symlink would get
|
|
|
|
|
* some third path — cac-fpm reports the literal /home/<user>/public_html.
|
|
|
|
|
* - It cannot fail. realpath() returns false for a path that does not exist
|
|
|
|
|
* (and is constrained by open_basedir), leaving the value half-normalised.
|
|
|
|
|
* - It costs no syscall. realpath() is an lstat chain on every request.
|
|
|
|
|
*
|
|
|
|
|
* The prefix is this site's FULL mount path (/mnt/users/<user>/<domain>), not
|
|
|
|
|
* the bare bulk-mount root, and it only matches on a path-component boundary —
|
|
|
|
|
* so a value pointing at another tenant, or one that is already canonical, is
|
|
|
|
|
* never touched.
|
|
|
|
|
*
|
|
|
|
|
* FAILURE MODES — every one of them leaves $_SERVER untouched and lets the
|
|
|
|
|
* request proceed. Nothing here can warn, throw, or 500 a customer site:
|
|
|
|
|
* - mapping unset/empty (any tier that is not shared-ols) -> RINIT returns
|
|
|
|
|
* immediately, extension is inert.
|
2026-08-05 13:06:34 -07:00
|
|
|
* - either side of the mapping not an ABSOLUTE path -> inert. Nothing
|
|
|
|
|
* the entrypoint writes is anything else, and a relative prefix cannot
|
|
|
|
|
* usefully match a SAPI-supplied path, so a malformed mapping is treated
|
|
|
|
|
* exactly like an absent one.
|
2026-08-05 11:38:19 -07:00
|
|
|
* - $_SERVER absent or not an array -> return.
|
|
|
|
|
* - key absent from $_SERVER -> skip that key.
|
|
|
|
|
* - key present but not a string -> skip that key.
|
|
|
|
|
* - value shorter than the prefix / prefix mismatch -> skip that key.
|
|
|
|
|
* - value matches the prefix mid-component -> skip that key.
|
|
|
|
|
* There is no error path, no userland-visible diagnostic, and no dependency on
|
|
|
|
|
* the filesystem being readable.
|
|
|
|
|
*
|
2026-08-05 13:06:34 -07:00
|
|
|
* The one thing this CANNOT detect is a well-formed but WRONG mapping: it will
|
|
|
|
|
* confidently rewrite to a wrong path and say nothing. That is accepted by
|
|
|
|
|
* construction rather than overlooked — entrypoint-lsphp.sh derives from/to from
|
|
|
|
|
* the same two variables it builds the compatibility symlink from, so a wrong
|
|
|
|
|
* mapping means the symlink is wrong too and the site is already broken in a far
|
|
|
|
|
* louder way. The only runtime signal is `lsphp -i`, which prints
|
|
|
|
|
* "Rewriting => active" alongside the live from/to values.
|
|
|
|
|
*
|
2026-08-05 13:46:52 -07:00
|
|
|
* That row is what the post-deploy fleet canary greps, so MINFO's "active" test
|
|
|
|
|
* must stay a mirror of the conditions RINIT actually rewrites under — see
|
|
|
|
|
* PHP_MINFO_FUNCTION below, which shares cacpp_mapping_active() with RINIT
|
|
|
|
|
* precisely so the two cannot drift. A MINFO that reported "active" for a
|
|
|
|
|
* mapping RINIT treats as inert would mask exactly the failure the canary
|
|
|
|
|
* exists to catch.
|
|
|
|
|
*
|
2026-08-05 11:38:19 -07:00
|
|
|
* SCOPE / KNOWN LIMITS
|
|
|
|
|
* --------------------
|
|
|
|
|
* Only $_SERVER is rewritten. LSAPI also answers getenv('DOCUMENT_ROOT') from
|
|
|
|
|
* the request environment, and that still returns the /mnt/users string. That is
|
|
|
|
|
* a deliberate limit: touching the SAPI environment risks the request env lsphp
|
|
|
|
|
* itself reads. Real-world PHP (WordPress and its plugin ecosystem) reads
|
|
|
|
|
* $_SERVER, not getenv(), for these.
|
|
|
|
|
*
|
|
|
|
|
* The sidecar's compatibility symlink is still REQUIRED and is not replaced by
|
|
|
|
|
* this extension: it is what makes the path OLS sends actually resolve on disk.
|
|
|
|
|
* This extension only corrects the strings.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
|
#include "config.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include "php.h"
|
|
|
|
|
#include "php_ini.h"
|
|
|
|
|
#include "ext/standard/info.h"
|
|
|
|
|
#include "SAPI.h"
|
|
|
|
|
#include "zend_compile.h" /* zend_is_auto_global_str() */
|
|
|
|
|
#include "php_cac_path_parity.h"
|
|
|
|
|
|
|
|
|
|
ZEND_DECLARE_MODULE_GLOBALS(cac_path_parity)
|
|
|
|
|
|
|
|
|
|
#define CACPP_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(cac_path_parity, v)
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The $_SERVER keys that carry a FILESYSTEM path. URI-derived keys (PHP_SELF,
|
|
|
|
|
* SCRIPT_NAME, REQUEST_URI) are already identical across tiers and are left
|
|
|
|
|
* alone. PATH_TRANSLATED and CONTEXT_DOCUMENT_ROOT are usually absent under
|
|
|
|
|
* OLS; rewriting them is a no-op when they are, and correct when they are not.
|
|
|
|
|
*/
|
|
|
|
|
static const struct {
|
|
|
|
|
const char *name;
|
|
|
|
|
size_t len;
|
|
|
|
|
} cacpp_keys[] = {
|
|
|
|
|
{ ZEND_STRL("DOCUMENT_ROOT") },
|
|
|
|
|
{ ZEND_STRL("SCRIPT_FILENAME") },
|
|
|
|
|
{ ZEND_STRL("PATH_TRANSLATED") },
|
|
|
|
|
{ ZEND_STRL("CONTEXT_DOCUMENT_ROOT") },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* clang-format off */
|
|
|
|
|
PHP_INI_BEGIN()
|
|
|
|
|
/*
|
|
|
|
|
* PHP_INI_SYSTEM is load-bearing: it is the reason a customer's .user.ini
|
|
|
|
|
* cannot reach these. Do not relax to PERDIR.
|
|
|
|
|
*/
|
|
|
|
|
STD_PHP_INI_ENTRY("cac_path_parity.from", "", PHP_INI_SYSTEM, OnUpdateString,
|
|
|
|
|
from, zend_cac_path_parity_globals, cac_path_parity_globals)
|
|
|
|
|
STD_PHP_INI_ENTRY("cac_path_parity.to", "", PHP_INI_SYSTEM, OnUpdateString,
|
|
|
|
|
to, zend_cac_path_parity_globals, cac_path_parity_globals)
|
|
|
|
|
PHP_INI_END()
|
|
|
|
|
/* clang-format on */
|
|
|
|
|
|
|
|
|
|
static PHP_GINIT_FUNCTION(cac_path_parity)
|
|
|
|
|
{
|
|
|
|
|
#if defined(COMPILE_DL_CAC_PATH_PARITY) && defined(ZTS)
|
|
|
|
|
ZEND_TSRMLS_CACHE_UPDATE();
|
|
|
|
|
#endif
|
|
|
|
|
cac_path_parity_globals->from = NULL;
|
|
|
|
|
cac_path_parity_globals->to = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 13:46:52 -07:00
|
|
|
/*
|
|
|
|
|
* THE MAPPING PREDICATE — one definition, two callers.
|
|
|
|
|
*
|
|
|
|
|
* RINIT uses it to decide whether to rewrite; MINFO uses it to REPORT whether
|
|
|
|
|
* rewriting is live. Those two tests were written out longhand in two places
|
|
|
|
|
* and promptly drifted: the absolute-path guard was added to RINIT only, so
|
|
|
|
|
* `lsphp -i` went on printing "Rewriting => active" for a mapping RINIT had
|
|
|
|
|
* already decided to ignore. That row is the fleet canary's signal, so the lie
|
|
|
|
|
* masked precisely the failure the canary looks for. Keep them sharing this.
|
|
|
|
|
*
|
|
|
|
|
* Pure predicates over two NUL-terminated strings: no allocation, no side
|
|
|
|
|
* effect, no way to fail — MINFO gains no error path by calling them, and the
|
|
|
|
|
* fail-open invariant is untouched.
|
|
|
|
|
*/
|
|
|
|
|
static int cacpp_mapping_configured(const char *from, const char *to)
|
|
|
|
|
{
|
|
|
|
|
return from != NULL && *from != '\0' && to != NULL && *to != '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int cacpp_mapping_active(const char *from, const char *to)
|
|
|
|
|
{
|
|
|
|
|
return cacpp_mapping_configured(from, to) && *from == '/' && *to == '/';
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 11:38:19 -07:00
|
|
|
/* Trailing slashes would defeat the component-boundary test below. */
|
|
|
|
|
static size_t cacpp_trim(const char *s, size_t len)
|
|
|
|
|
{
|
|
|
|
|
while (len > 1 && s[len - 1] == '/') {
|
|
|
|
|
len--;
|
|
|
|
|
}
|
|
|
|
|
return len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void cacpp_rewrite_key(zval *server, const char *key, size_t key_len,
|
|
|
|
|
const char *from, size_t from_len,
|
|
|
|
|
const char *to, size_t to_len)
|
|
|
|
|
{
|
|
|
|
|
zval *val = zend_hash_str_find(Z_ARRVAL_P(server), key, key_len);
|
|
|
|
|
if (val == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
ZVAL_DEREF(val);
|
|
|
|
|
if (Z_TYPE_P(val) != IS_STRING) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *s = Z_STRVAL_P(val);
|
|
|
|
|
size_t len = Z_STRLEN_P(val);
|
|
|
|
|
|
|
|
|
|
if (len < from_len || memcmp(s, from, from_len) != 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
* Only replace on a PATH-COMPONENT boundary. Without this,
|
|
|
|
|
* from=/mnt/users/bob/site.com would also match a sibling directory
|
|
|
|
|
* /mnt/users/bob/site.com.bak and silently rewrite another site's path
|
|
|
|
|
* into this site's /home.
|
|
|
|
|
*/
|
|
|
|
|
if (len != from_len && s[from_len] != '/') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 13:06:34 -07:00
|
|
|
size_t tail_len = len - from_len;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* to="/" is the one absolute prefix that survives cacpp_trim() as a bare
|
|
|
|
|
* separator, and the tail always starts with one — splicing both would give
|
|
|
|
|
* "//public_html". Drop it when there IS a tail; keep it when there is not
|
|
|
|
|
* (value == from exactly, where "/" is the correct answer). Unreachable from
|
|
|
|
|
* the entrypoint, which always writes to=/home/<user>.
|
|
|
|
|
*/
|
|
|
|
|
size_t eff_to_len = to_len;
|
|
|
|
|
if (tail_len > 0 && eff_to_len == 1 && to[0] == '/') {
|
|
|
|
|
eff_to_len = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
zend_string *out = zend_string_alloc(eff_to_len + tail_len, 0);
|
2026-08-05 11:38:19 -07:00
|
|
|
|
2026-08-05 13:06:34 -07:00
|
|
|
memcpy(ZSTR_VAL(out), to, eff_to_len);
|
|
|
|
|
memcpy(ZSTR_VAL(out) + eff_to_len, s + from_len, tail_len);
|
|
|
|
|
ZSTR_VAL(out)[eff_to_len + tail_len] = '\0';
|
2026-08-05 11:38:19 -07:00
|
|
|
|
|
|
|
|
zval nv;
|
|
|
|
|
ZVAL_STR(&nv, out);
|
|
|
|
|
/*
|
|
|
|
|
* Update the track_vars array IN PLACE. $_SERVER in the symbol table and
|
|
|
|
|
* PG(http_globals)[TRACK_VARS_SERVER] are two references to the SAME
|
|
|
|
|
* zend_array, which is why this is visible to userland. Do NOT
|
|
|
|
|
* SEPARATE_ARRAY() here: that would copy the array and leave the symbol
|
|
|
|
|
* table pointing at the original, i.e. silently do nothing. This is the
|
|
|
|
|
* same in-place pattern php_register_variable_ex() uses.
|
|
|
|
|
*/
|
|
|
|
|
zend_hash_str_update(Z_ARRVAL_P(server), key, key_len, &nv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PHP_RINIT_FUNCTION(cac_path_parity)
|
|
|
|
|
{
|
|
|
|
|
#if defined(ZTS) && defined(COMPILE_DL_CAC_PATH_PARITY)
|
|
|
|
|
ZEND_TSRMLS_CACHE_UPDATE();
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
const char *from = CACPP_G(from);
|
|
|
|
|
const char *to = CACPP_G(to);
|
|
|
|
|
|
|
|
|
|
/* Unconfigured (any tier that isn't shared-ols) => completely inert. */
|
2026-08-05 13:46:52 -07:00
|
|
|
if (!cacpp_mapping_configured(from, to)) {
|
2026-08-05 11:38:19 -07:00
|
|
|
return SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 13:06:34 -07:00
|
|
|
/*
|
|
|
|
|
* Both sides must be ABSOLUTE. The entrypoint only ever writes absolute
|
|
|
|
|
* paths; a relative prefix would be a typo or a mangled ini, and matching it
|
|
|
|
|
* against a SAPI-supplied path could only ever produce nonsense. Treat it
|
|
|
|
|
* like an absent mapping — inert, no diagnostic, request proceeds.
|
|
|
|
|
*/
|
2026-08-05 13:46:52 -07:00
|
|
|
if (!cacpp_mapping_active(from, to)) {
|
2026-08-05 13:06:34 -07:00
|
|
|
return SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 11:38:19 -07:00
|
|
|
/*
|
|
|
|
|
* With auto_globals_jit=On (the default) $_SERVER is not built yet at
|
|
|
|
|
* RINIT — php_hash_environment() only MARKED it for lazy creation. Reading
|
|
|
|
|
* PG(http_globals)[TRACK_VARS_SERVER] here without this call finds IS_UNDEF
|
|
|
|
|
* and the extension silently does nothing, which is precisely the failure
|
|
|
|
|
* the auto_prepend approach had. Forcing the auto-global now builds it (via
|
|
|
|
|
* the SAPI's register_server_variables) so there is something to rewrite,
|
|
|
|
|
* and the later userland access gets the corrected array.
|
|
|
|
|
*/
|
|
|
|
|
zend_is_auto_global_str(ZEND_STRL("_SERVER"));
|
|
|
|
|
|
|
|
|
|
zval *server = &PG(http_globals)[TRACK_VARS_SERVER];
|
|
|
|
|
if (Z_TYPE_P(server) != IS_ARRAY) {
|
|
|
|
|
return SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t from_len = cacpp_trim(from, strlen(from));
|
|
|
|
|
size_t to_len = cacpp_trim(to, strlen(to));
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < sizeof(cacpp_keys) / sizeof(cacpp_keys[0]); i++) {
|
|
|
|
|
cacpp_rewrite_key(server, cacpp_keys[i].name, cacpp_keys[i].len,
|
|
|
|
|
from, from_len, to, to_len);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PHP_MINIT_FUNCTION(cac_path_parity)
|
|
|
|
|
{
|
|
|
|
|
REGISTER_INI_ENTRIES();
|
|
|
|
|
return SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PHP_MSHUTDOWN_FUNCTION(cac_path_parity)
|
|
|
|
|
{
|
|
|
|
|
UNREGISTER_INI_ENTRIES();
|
|
|
|
|
return SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PHP_MINFO_FUNCTION(cac_path_parity)
|
|
|
|
|
{
|
|
|
|
|
const char *from = CACPP_G(from);
|
|
|
|
|
const char *to = CACPP_G(to);
|
2026-08-05 13:46:52 -07:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Report what RINIT would ACTUALLY do, by asking the same predicates RINIT
|
|
|
|
|
* asks — never a longhand copy of them (see cacpp_mapping_active above for
|
|
|
|
|
* what that cost last time). Three distinct answers, because "configured but
|
|
|
|
|
* ignored" is a different operational problem from "not configured" and the
|
|
|
|
|
* canary must be able to tell them apart.
|
|
|
|
|
*/
|
|
|
|
|
const char *state;
|
|
|
|
|
if (cacpp_mapping_active(from, to)) {
|
|
|
|
|
state = "active";
|
|
|
|
|
} else if (cacpp_mapping_configured(from, to)) {
|
|
|
|
|
state = "inactive (mapping not absolute)";
|
|
|
|
|
} else {
|
|
|
|
|
state = "inactive (unconfigured)";
|
|
|
|
|
}
|
2026-08-05 11:38:19 -07:00
|
|
|
|
|
|
|
|
php_info_print_table_start();
|
|
|
|
|
php_info_print_table_header(2, "cac_path_parity support", "enabled");
|
|
|
|
|
php_info_print_table_row(2, "Version", PHP_CAC_PATH_PARITY_VERSION);
|
2026-08-05 13:46:52 -07:00
|
|
|
/*
|
|
|
|
|
* The canary greps for this row: "active" proves the mapping is live — and,
|
|
|
|
|
* since the predicate is shared with RINIT, proves the request path agrees.
|
|
|
|
|
*/
|
|
|
|
|
php_info_print_table_row(2, "Rewriting", state);
|
2026-08-05 11:38:19 -07:00
|
|
|
php_info_print_table_end();
|
|
|
|
|
|
|
|
|
|
DISPLAY_INI_ENTRIES();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
zend_module_entry cac_path_parity_module_entry = {
|
|
|
|
|
STANDARD_MODULE_HEADER,
|
|
|
|
|
"cac_path_parity",
|
|
|
|
|
NULL, /* no userland functions — by design */
|
|
|
|
|
PHP_MINIT(cac_path_parity),
|
|
|
|
|
PHP_MSHUTDOWN(cac_path_parity),
|
|
|
|
|
PHP_RINIT(cac_path_parity),
|
|
|
|
|
NULL, /* RSHUTDOWN */
|
|
|
|
|
PHP_MINFO(cac_path_parity),
|
|
|
|
|
PHP_CAC_PATH_PARITY_VERSION,
|
|
|
|
|
PHP_MODULE_GLOBALS(cac_path_parity),
|
|
|
|
|
PHP_GINIT(cac_path_parity),
|
|
|
|
|
NULL, /* GSHUTDOWN */
|
|
|
|
|
NULL, /* post-deactivate */
|
|
|
|
|
STANDARD_MODULE_PROPERTIES_EX
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#ifdef COMPILE_DL_CAC_PATH_PARITY
|
|
|
|
|
#if defined(ZTS)
|
|
|
|
|
ZEND_TSRMLS_CACHE_DEFINE()
|
|
|
|
|
#endif
|
|
|
|
|
ZEND_GET_MODULE(cac_path_parity)
|
|
|
|
|
#endif
|