XCache is a fast, stable PHP opcode cacher that has been proven and is now running on production servers under high load. https://xcache.lighttpd.net/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4046 lines
118 KiB

  1. #if 0
  2. #define SHOW_DPRINT
  3. #endif
  4. /* {{{ macros */
  5. #include "xc_cacher.h"
  6. #include "xc_cache.h"
  7. #include "xcache.h"
  8. #include "xc_processor.h"
  9. #include "xcache_globals.h"
  10. #include "xcache/xc_extension.h"
  11. #include "xcache/xc_ini.h"
  12. #include "xcache/xc_utils.h"
  13. #include "xcache/xc_sandbox.h"
  14. #include "util/xc_trace.h"
  15. #include "util/xc_vector.h"
  16. #include "util/xc_align.h"
  17. #include "php.h"
  18. #include "ext/standard/info.h"
  19. #include "ext/standard/md5.h"
  20. #ifdef ZEND_ENGINE_2_1
  21. # include "ext/date/php_date.h"
  22. #endif
  23. #ifdef ZEND_WIN32
  24. # include <process.h>
  25. #endif
  26. #include "ext/standard/php_math.h"
  27. #include "SAPI.h"
  28. #define ECALLOC_N(x, n) ((x) = ecalloc(n, sizeof((x)[0])))
  29. #define ECALLOC_ONE(x) ECALLOC_N(x, 1)
  30. #define VAR_ENTRY_EXPIRED(pentry) ((pentry)->ttl && XG(request_time) > (pentry)->ctime + (time_t) (pentry)->ttl)
  31. #define CHECK(x, e) do { if ((x) == NULL) { zend_error(E_ERROR, "XCache: " e); goto err; } } while (0)
  32. #define LOCK(x) xc_mutex_lock((x)->mutex)
  33. #define UNLOCK(x) xc_mutex_unlock((x)->mutex)
  34. #define ENTER_LOCK_EX(x) \
  35. LOCK((x)); \
  36. zend_try { \
  37. do
  38. #define LEAVE_LOCK_EX(x) \
  39. while (0); \
  40. } zend_catch { \
  41. catched = 1; \
  42. } zend_end_try(); \
  43. UNLOCK((x))
  44. #define ENTER_LOCK(x) do { \
  45. int catched = 0; \
  46. ENTER_LOCK_EX(x)
  47. #define LEAVE_LOCK(x) \
  48. LEAVE_LOCK_EX(x); \
  49. if (catched) { \
  50. zend_bailout(); \
  51. } \
  52. } while(0)
  53. /* }}} */
  54. struct _xc_hash_t { /* {{{ */
  55. size_t bits;
  56. size_t size;
  57. xc_hash_value_t mask;
  58. };
  59. /* }}} */
  60. struct _xc_cached_t { /* {{{ stored in shm */
  61. int cacheid;
  62. time_t compiling;
  63. time_t disabled;
  64. zend_ulong updates;
  65. zend_ulong hits;
  66. zend_ulong skips;
  67. zend_ulong ooms;
  68. zend_ulong errors;
  69. xc_entry_t **entries;
  70. int entries_count;
  71. xc_entry_data_php_t **phps;
  72. int phps_count;
  73. xc_entry_t *deletes;
  74. int deletes_count;
  75. time_t last_gc_deletes;
  76. time_t last_gc_expires;
  77. time_t hits_by_hour_cur_time;
  78. zend_uint hits_by_hour_cur_slot;
  79. zend_ulong hits_by_hour[24];
  80. time_t hits_by_second_cur_time;
  81. zend_uint hits_by_second_cur_slot;
  82. zend_ulong hits_by_second[5];
  83. };
  84. /* }}} */
  85. typedef struct { /* {{{ xc_cache_t: only cache info, not in shm */
  86. int cacheid;
  87. xc_hash_t *hcache; /* hash to cacheid */
  88. xc_mutex_t *mutex;
  89. xc_shm_t *shm; /* which shm contains us */
  90. xc_allocator_t *allocator;
  91. xc_hash_t *hentry; /* hash settings to entry */
  92. xc_hash_t *hphp; /* hash settings to php */
  93. xc_cached_t *cached;
  94. } xc_cache_t;
  95. /* }}} */
  96. /* {{{ globals */
  97. static char *xc_shm_scheme = NULL;
  98. static char *xc_mmap_path = NULL;
  99. static zend_bool xc_admin_enable_auth = 1;
  100. static xc_hash_t xc_php_hcache = { 0, 0, 0 };
  101. static xc_hash_t xc_php_hentry = { 0, 0, 0 };
  102. static xc_hash_t xc_var_hcache = { 0, 0, 0 };
  103. static xc_hash_t xc_var_hentry = { 0, 0, 0 };
  104. static zend_ulong xc_php_ttl = 0;
  105. static zend_ulong xc_var_maxttl = 0;
  106. enum { xc_deletes_gc_interval = 120 };
  107. static zend_ulong xc_php_gc_interval = 0;
  108. static zend_ulong xc_var_gc_interval = 0;
  109. static char *xc_php_allocator = NULL;
  110. static char *xc_var_allocator = NULL;
  111. /* total size */
  112. static zend_ulong xc_php_size = 0;
  113. static zend_ulong xc_var_size = 0;
  114. static xc_cache_t *xc_php_caches = NULL;
  115. static xc_cache_t *xc_var_caches = NULL;
  116. static zend_bool xc_initized = 0;
  117. static time_t xc_init_time = 0;
  118. static long unsigned xc_init_instance_id = 0;
  119. #ifdef ZTS
  120. static long unsigned xc_init_instance_subid = 0;
  121. #endif
  122. static zend_compile_file_t *old_compile_file = NULL;
  123. static zend_bool xc_readonly_protection = 0;
  124. static zend_ulong xc_var_namespace_mode = 0;
  125. static char *xc_var_namespace = NULL;
  126. zend_bool xc_have_op_array_ctor = 0;
  127. /* }}} */
  128. typedef enum { XC_TYPE_PHP, XC_TYPE_VAR } xc_entry_type_t;
  129. static void xc_holds_init(TSRMLS_D);
  130. static void xc_holds_destroy(TSRMLS_D);
  131. static void *xc_cache_storage(void *data, size_t size) /* {{{ */
  132. {
  133. xc_allocator_t *allocator = (xc_allocator_t *) data;
  134. return allocator->vtable->malloc(allocator, size);
  135. }
  136. /* }}} */
  137. /* any function in *_unlocked is only safe be called within locked (single thread access) area */
  138. static void xc_php_add_unlocked(xc_cached_t *cached, xc_entry_data_php_t *php) /* {{{ */
  139. {
  140. xc_entry_data_php_t **head = &(cached->phps[php->hvalue]);
  141. php->next = *head;
  142. *head = php;
  143. cached->phps_count ++;
  144. }
  145. /* }}} */
  146. static xc_entry_data_php_t *xc_php_store_unlocked(xc_cache_t *cache, xc_entry_data_php_t *php TSRMLS_DC) /* {{{ */
  147. {
  148. xc_entry_data_php_t *stored_php;
  149. xc_processor_storage_t storage;
  150. storage.allocator = &xc_cache_storage;
  151. storage.allocator_data = (void *) cache->allocator;
  152. storage.relocatediff = cache->shm->readonlydiff;
  153. php->hits = 0;
  154. php->refcount = 0;
  155. stored_php = xc_processor_store_xc_entry_data_php_t(&storage, php TSRMLS_CC);
  156. #if 0
  157. {
  158. xc_entry_data_php_t *p = malloc(stored_php->size);
  159. xc_entry_data_php_t *backup = malloc(stored_php->size);
  160. fprintf(stderr, "%lu\n", stored_php->size);
  161. memcpy(backup, stored_php, stored_php->size);
  162. {
  163. memcpy(p, stored_php, stored_php->size);
  164. xc_processor_relocate_xc_entry_data_php_t(p, p , stored_php, stored_php TSRMLS_CC);
  165. assert(memcmp(stored_php, backup, stored_php->size) == 0);
  166. memcpy(stored_php, p, p->size);
  167. xc_processor_relocate_xc_entry_data_php_t(stored_php, stored_php, p, p TSRMLS_CC);
  168. }
  169. {
  170. memcpy(p, stored_php, stored_php->size);
  171. xc_processor_relocate_xc_entry_data_php_t(p, 0, stored_php, stored_php TSRMLS_CC);
  172. assert(memcmp(stored_php, backup, stored_php->size) == 0);
  173. memcpy(stored_php, p, p->size);
  174. xc_processor_relocate_xc_entry_data_php_t(stored_php, stored_php, p, 0 TSRMLS_CC);
  175. }
  176. }
  177. #endif
  178. if (stored_php) {
  179. xc_php_add_unlocked(cache->cached, stored_php);
  180. return stored_php;
  181. }
  182. else {
  183. cache->cached->ooms ++;
  184. return NULL;
  185. }
  186. }
  187. /* }}} */
  188. static xc_entry_data_php_t *xc_php_find_unlocked(xc_cached_t *cached, xc_entry_data_php_t *php TSRMLS_DC) /* {{{ */
  189. {
  190. xc_entry_data_php_t *p;
  191. for (p = cached->phps[php->hvalue]; p; p = (xc_entry_data_php_t *) p->next) {
  192. if (memcmp(&php->md5.digest, &p->md5.digest, sizeof(php->md5.digest)) == 0) {
  193. p->hits ++;
  194. return p;
  195. }
  196. }
  197. return NULL;
  198. }
  199. /* }}} */
  200. static void xc_php_free_unlocked(xc_cache_t *cache, xc_entry_data_php_t *php) /* {{{ */
  201. {
  202. cache->allocator->vtable->free(cache->allocator, (xc_entry_data_php_t *)php);
  203. }
  204. /* }}} */
  205. static void xc_php_addref_unlocked(xc_entry_data_php_t *php) /* {{{ */
  206. {
  207. php->refcount ++;
  208. }
  209. /* }}} */
  210. static void xc_php_release_unlocked(xc_cache_t *cache, xc_entry_data_php_t *php) /* {{{ */
  211. {
  212. if (-- php->refcount == 0) {
  213. xc_entry_data_php_t **pp = &(cache->cached->phps[php->hvalue]);
  214. xc_entry_data_php_t *p;
  215. for (p = *pp; p; pp = &(p->next), p = p->next) {
  216. if (memcmp(&php->md5.digest, &p->md5.digest, sizeof(php->md5.digest)) == 0) {
  217. /* unlink */
  218. *pp = p->next;
  219. xc_php_free_unlocked(cache, php);
  220. return;
  221. }
  222. }
  223. assert(0);
  224. }
  225. }
  226. /* }}} */
  227. static inline zend_bool xc_entry_equal_unlocked(xc_entry_type_t type, const xc_entry_t *entry1, const xc_entry_t *entry2 TSRMLS_DC) /* {{{ */
  228. {
  229. /* this function isn't required but can be in unlocked */
  230. switch (type) {
  231. case XC_TYPE_PHP:
  232. {
  233. const xc_entry_php_t *php_entry1 = (const xc_entry_php_t *) entry1;
  234. const xc_entry_php_t *php_entry2 = (const xc_entry_php_t *) entry2;
  235. if (php_entry1->file_inode && php_entry2->file_inode) {
  236. zend_bool inodeIsSame = php_entry1->file_inode == php_entry2->file_inode
  237. && php_entry1->file_device == php_entry2->file_device;
  238. if (!inodeIsSame) {
  239. return 0;
  240. }
  241. }
  242. }
  243. assert(strstr(entry1->name.str.val, "://") != NULL || IS_ABSOLUTE_PATH(entry1->name.str.val, entry1->name.str.len));
  244. assert(strstr(entry1->name.str.val, "://") != NULL || IS_ABSOLUTE_PATH(entry2->name.str.val, entry2->name.str.len));
  245. return entry1->name.str.len == entry2->name.str.len
  246. && memcmp(entry1->name.str.val, entry2->name.str.val, entry1->name.str.len + 1) == 0;
  247. case XC_TYPE_VAR:
  248. #ifdef IS_UNICODE
  249. if (entry1->name_type != entry2->name_type) {
  250. return 0;
  251. }
  252. if (entry1->name_type == IS_UNICODE) {
  253. return entry1->name.ustr.len == entry2->name.ustr.len
  254. && memcmp(entry1->name.ustr.val, entry2->name.ustr.val, (entry1->name.ustr.len + 1) * sizeof(entry1->name.ustr.val[0])) == 0;
  255. }
  256. #endif
  257. return entry1->name.str.len == entry2->name.str.len
  258. && memcmp(entry1->name.str.val, entry2->name.str.val, entry1->name.str.len + 1) == 0;
  259. break;
  260. default:
  261. assert(0);
  262. }
  263. return 0;
  264. }
  265. /* }}} */
  266. static void xc_entry_add_unlocked(xc_cached_t *cached, xc_hash_value_t entryslotid, xc_entry_t *entry) /* {{{ */
  267. {
  268. xc_entry_t **head = &(cached->entries[entryslotid]);
  269. entry->next = *head;
  270. *head = entry;
  271. cached->entries_count ++;
  272. }
  273. /* }}} */
  274. static xc_entry_t *xc_entry_store_unlocked(xc_entry_type_t type, xc_cache_t *cache, xc_hash_value_t entryslotid, xc_entry_t *entry TSRMLS_DC) /* {{{ */
  275. {
  276. xc_entry_t *stored_entry;
  277. xc_processor_storage_t storage;
  278. storage.allocator = &xc_cache_storage;
  279. storage.allocator_data = (void *) cache->allocator;
  280. storage.relocatediff = cache->shm->readonlydiff;
  281. entry->hits = 0;
  282. entry->ctime = XG(request_time);
  283. entry->atime = XG(request_time);
  284. stored_entry = type == XC_TYPE_PHP
  285. ? (xc_entry_t *) xc_processor_store_xc_entry_php_t(&storage, (xc_entry_php_t *) entry TSRMLS_CC)
  286. : (xc_entry_t *) xc_processor_store_xc_entry_var_t(&storage, (xc_entry_var_t *) entry TSRMLS_CC);
  287. if (stored_entry) {
  288. xc_entry_add_unlocked(cache->cached, entryslotid, stored_entry);
  289. ++cache->cached->updates;
  290. return stored_entry;
  291. }
  292. else {
  293. cache->cached->ooms ++;
  294. return NULL;
  295. }
  296. }
  297. /* }}} */
  298. static xc_entry_php_t *xc_entry_php_store_unlocked(xc_cache_t *cache, xc_hash_value_t entryslotid, xc_entry_php_t *entry_php TSRMLS_DC) /* {{{ */
  299. {
  300. return (xc_entry_php_t *) xc_entry_store_unlocked(XC_TYPE_PHP, cache, entryslotid, (xc_entry_t *) entry_php TSRMLS_CC);
  301. }
  302. /* }}} */
  303. static xc_entry_var_t *xc_entry_var_store_unlocked(xc_cache_t *cache, xc_hash_value_t entryslotid, xc_entry_var_t *entry_var TSRMLS_DC) /* {{{ */
  304. {
  305. return (xc_entry_var_t *) xc_entry_store_unlocked(XC_TYPE_VAR, cache, entryslotid, (xc_entry_t *) entry_var TSRMLS_CC);
  306. }
  307. /* }}} */
  308. static void xc_entry_free_real_unlocked(xc_entry_type_t type, xc_cache_t *cache, volatile xc_entry_t *entry) /* {{{ */
  309. {
  310. if (type == XC_TYPE_PHP) {
  311. xc_php_release_unlocked(cache, ((xc_entry_php_t *) entry)->php);
  312. }
  313. cache->allocator->vtable->free(cache->allocator, (xc_entry_t *)entry);
  314. }
  315. /* }}} */
  316. static void xc_entry_free_unlocked(xc_entry_type_t type, xc_cache_t *cache, xc_entry_t *entry TSRMLS_DC) /* {{{ */
  317. {
  318. cache->cached->entries_count --;
  319. if ((type == XC_TYPE_PHP ? ((xc_entry_php_t *) entry)->refcount : 0) == 0) {
  320. xc_entry_free_real_unlocked(type, cache, entry);
  321. }
  322. else {
  323. entry->next = cache->cached->deletes;
  324. cache->cached->deletes = entry;
  325. entry->dtime = XG(request_time);
  326. cache->cached->deletes_count ++;
  327. }
  328. return;
  329. }
  330. /* }}} */
  331. static void xc_entry_remove_unlocked(xc_entry_type_t type, xc_cache_t *cache, xc_hash_value_t entryslotid, xc_entry_t *entry TSRMLS_DC) /* {{{ */
  332. {
  333. xc_entry_t **pp = &(cache->cach