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.

436 lines
9.7 KiB

  1. #include <stdio.h>
  2. #include "xcache.h"
  3. #include "ext/standard/flock_compat.h"
  4. #ifdef HAVE_SYS_FILE_H
  5. # include <sys/file.h>
  6. #endif
  7. #include <sys/types.h>
  8. #include <sys/stat.h>
  9. #include <fcntl.h>
  10. #include "stack.h"
  11. #include "xcache_globals.h"
  12. #include "coverager.h"
  13. #include "utils.h"
  14. typedef HashTable *coverager_t;
  15. #define PCOV_HEADER_MAGIC 0x564f4350
  16. char *xc_coveragedump_dir = NULL;
  17. static zend_compile_file_t *origin_compile_file;
  18. #undef DEBUG
  19. /* dumper */
  20. static void xc_destroy_coverage(void *pDest) /* {{{ */
  21. {
  22. coverager_t cov = *(coverager_t*) pDest;
  23. #ifdef DEBUG
  24. fprintf(stderr, "destroy %p\n", cov);
  25. #endif
  26. zend_hash_destroy(cov);
  27. efree(cov);
  28. }
  29. /* }}} */
  30. void xcache_mkdirs_ex(char *root, int rootlen, char *path, int pathlen TSRMLS_DC) /* {{{ */
  31. {
  32. char *fullpath;
  33. struct stat st;
  34. #ifdef DEBUG
  35. fprintf(stderr, "mkdirs %s %d %s %d\n", root, rootlen, path, pathlen);
  36. #endif
  37. fullpath = do_alloca(rootlen + pathlen + 1);
  38. memcpy(fullpath, root, rootlen);
  39. memcpy(fullpath + rootlen, path, pathlen);
  40. fullpath[rootlen + pathlen] = '\0';
  41. if (stat(fullpath, &st) != 0) {
  42. char *chr;
  43. chr = strrchr(path, PHP_DIR_SEPARATOR);
  44. if (chr && chr != path) {
  45. *chr = '\0';
  46. xcache_mkdirs_ex(root, rootlen, path, chr - path TSRMLS_CC);
  47. *chr = PHP_DIR_SEPARATOR;
  48. }
  49. #ifdef DEBUG
  50. fprintf(stderr, "mkdir %s\n", fullpath);
  51. #endif
  52. #if PHP_MAJOR_VERSION > 5
  53. php_stream_mkdir(fullpath, 0700, REPORT_ERRORS, NULL);
  54. #else
  55. mkdir(fullpath, 0700);
  56. #endif
  57. }
  58. free_alloca(fullpath);
  59. }
  60. /* }}} */
  61. static void xc_coverager_save_cov(char *srcfile, char *outfilename, coverager_t cov TSRMLS_DC) /* {{{ */
  62. {
  63. long *buf = NULL, *p;
  64. long covlines, *phits;
  65. int fd = -1;
  66. int size;
  67. int newfile;
  68. struct stat srcstat, outstat;
  69. HashPosition pos;
  70. char *contents = NULL;
  71. long len;
  72. if (stat(srcfile, &srcstat) != 0) {
  73. return;
  74. }
  75. newfile = 0;
  76. if (stat(outfilename, &outstat) != 0) {
  77. newfile = 1;
  78. }
  79. else {
  80. if (srcstat.st_mtime > outstat.st_mtime) {
  81. newfile = 1;
  82. }
  83. }
  84. fd = open(outfilename, O_RDWR | O_CREAT, 0600);
  85. if (fd < 0) {
  86. char *chr;
  87. chr = strrchr(srcfile, PHP_DIR_SEPARATOR);
  88. if (chr) {
  89. *chr = '\0';
  90. xcache_mkdirs_ex(xc_coveragedump_dir, strlen(xc_coveragedump_dir), srcfile, chr - srcfile TSRMLS_CC);
  91. *chr = PHP_DIR_SEPARATOR;
  92. }
  93. fd = open(outfilename, O_RDWR | O_CREAT, 0600);
  94. if (fd < 0) {
  95. goto bailout;
  96. }
  97. }
  98. if (flock(fd, LOCK_EX) != SUCCESS) {
  99. goto bailout;
  100. }
  101. if (newfile) {
  102. #ifdef DEBUG
  103. fprintf(stderr, "new file\n");
  104. #endif
  105. }
  106. else if (outstat.st_size) {
  107. len = outstat.st_size;
  108. contents = emalloc(len);
  109. if (read(fd, (void *) contents, len) != len) {
  110. goto bailout;
  111. }
  112. #ifdef DEBUG
  113. fprintf(stderr, "oldsize %d\n", (int) len);
  114. #endif
  115. do {
  116. p = (long *) contents;
  117. len -= sizeof(long);
  118. if (len < 0) {
  119. break;
  120. }
  121. if (*p++ != PCOV_HEADER_MAGIC) {
  122. #ifdef DEBUG
  123. fprintf(stderr, "wrong magic in file %s\n", outfilename);
  124. #endif
  125. break;
  126. }
  127. p += 2; /* skip covliens */
  128. len -= sizeof(long) * 2;
  129. if (len < 0) {
  130. break;
  131. }
  132. for (; len >= sizeof(long) * 2; len -= sizeof(long) * 2, p += 2) {
  133. if (zend_hash_index_find(cov, p[0], (void**)&phits) == SUCCESS) {
  134. if (p[1] <= 0) {
  135. /* already marked */
  136. continue;
  137. }
  138. if (*phits > 0) {
  139. p[1] += *phits;
  140. }
  141. }
  142. zend_hash_index_update(cov, p[0], &p[1], sizeof(p[1]), NULL);
  143. }
  144. } while (0);
  145. efree(contents);
  146. contents = NULL;
  147. }
  148. /* serialize */
  149. size = (zend_hash_num_elements(cov) + 1) * sizeof(long) * 2 + sizeof(long);
  150. p = buf = emalloc(size);
  151. *p++ = PCOV_HEADER_MAGIC;
  152. p += 2; /* for covlines */
  153. covlines = 0;
  154. zend_hash_internal_pointer_reset_ex(cov, &pos);
  155. while (zend_hash_get_current_data_ex(cov, (void**)&phits, &pos) == SUCCESS) {
  156. *p++ = pos->h;
  157. if (*phits <= 0) {
  158. *p++ = 0;
  159. }
  160. else {
  161. *p++ = *phits;
  162. covlines ++;
  163. }
  164. zend_hash_move_forward_ex(cov, &pos);
  165. }
  166. p = buf + 1;
  167. p[0] = 0;
  168. p[1] = covlines;
  169. ftruncate(fd, 0);
  170. lseek(fd, 0, SEEK_SET);
  171. write(fd, (char *) buf, size);
  172. bailout:
  173. if (contents) efree(contents);
  174. if (fd >= 0) close(fd);
  175. if (buf) efree(buf);
  176. }
  177. /* }}} */
  178. void xc_coverager_request_init(TSRMLS_D) /* {{{ */
  179. {
  180. if (XG(coveragedumper)) {
  181. XG(coverages) = emalloc(sizeof(HashTable));
  182. zend_hash_init(XG(coverages), 0, NULL, xc_destroy_coverage, 0);
  183. }
  184. }
  185. /* }}} */
  186. void xc_coverager_request_shutdown(TSRMLS_D) /* {{{ */
  187. {
  188. coverager_t *pcov;
  189. zstr s;
  190. char *outfilename;
  191. int dumpdir_len, outfilelen, alloc_len = 0;
  192. uint size;
  193. if (!XG(coverages)) {
  194. return;
  195. }
  196. if (XG(coveragedumper)) {
  197. dumpdir_len = strlen(xc_coveragedump_dir);
  198. alloc_len = dumpdir_len + 1 + 128;
  199. outfilename = emalloc(alloc_len);
  200. strcpy(outfilename, xc_coveragedump_dir);
  201. zend_hash_internal_pointer_reset(XG(coverages));
  202. while (zend_hash_get_current_data(XG(coverages), (void **) &pcov) == SUCCESS) {
  203. zend_hash_get_current_key_ex(XG(coverages), &s, &size, NULL, 0, NULL);
  204. outfilelen = dumpdir_len + size + 5;
  205. if (alloc_len < outfilelen) {
  206. alloc_len = outfilelen + 128;
  207. outfilename = erealloc(outfilename, alloc_len);
  208. }
  209. strcpy(outfilename + dumpdir_len, ZSTR_S(s));
  210. strcpy(outfilename + dumpdir_len + size - 1, ".pcov");
  211. #ifdef DEBUG
  212. fprintf(stderr, "outfilename %s\n", outfilename);
  213. #endif
  214. xc_coverager_save_cov(ZSTR_S(s), outfilename, *pcov TSRMLS_CC);
  215. zend_hash_move_forward(XG(coverages));
  216. }
  217. efree(outfilename);
  218. }
  219. zend_hash_destroy(XG(coverages));
  220. efree(XG(coverages));
  221. XG(coverages) = NULL;
  222. }
  223. /* }}} */
  224. /* helper func to store hits into coverages */
  225. static coverager_t xc_coverager_get(char *filename TSRMLS_DC) /* {{{ */
  226. {
  227. int len = strlen(filename) + 1;
  228. coverager_t cov, *pcov;
  229. if (zend_hash_find(XG(coverages), filename, len, (void **) &pcov) == SUCCESS) {
  230. #ifdef DEBUG
  231. fprintf(stderr, "got coverage %s %p\n", filename, *pcov);
  232. #endif
  233. return *pcov;
  234. }
  235. else {
  236. cov = emalloc(sizeof(HashTable));
  237. zend_hash_init(cov, 0, NULL, NULL, 0);
  238. zend_hash_add(XG(coverages), filename, len, (void **) &cov, sizeof(cov), NULL);
  239. #ifdef DEBUG
  240. fprintf(stderr, "new coverage %s %p\n", filename, cov);
  241. #endif
  242. return cov;
  243. }
  244. }
  245. /* }}} */
  246. static void xc_coverager_add_hits(HashTable *cov, long line, long hits TSRMLS_DC) /* {{{ */
  247. {
  248. long *poldhits;
  249. if (line == 0) {
  250. return;
  251. }
  252. if (zend_hash_index_find(cov, line, (void**)&poldhits) == SUCCESS) {
  253. if (hits == -1) {
  254. /* already marked */
  255. return;
  256. }
  257. if (*poldhits != -1) {
  258. hits += *poldhits;
  259. }
  260. }
  261. zend_hash_index_update(cov, line, &hits, sizeof(hits), NULL);
  262. }
  263. /* }}} */
  264. static int xc_coverager_get_op_array_size_no_tail(zend_op_array *op_array) /* {{{ */
  265. {
  266. zend_uint size;
  267. size = op_array->size;
  268. #ifdef ZEND_ENGINE_2
  269. if (op_array->opcodes[size - 1].opcode == ZEND_HANDLE_EXCEPTION) {
  270. size --;
  271. #endif
  272. if (op_array->opcodes[size - 1].opcode == ZEND_RETURN) {
  273. size --;
  274. /* it's not real php statement */
  275. if (op_array->opcodes[size - 1].opcode == ZEND_EXT_STMT) {
  276. size --;
  277. }
  278. }
  279. #ifdef ZEND_ENGINE_2
  280. }
  281. #endif
  282. return size;
  283. }
  284. /* }}} */
  285. /* prefill */
  286. static int xc_coverager_init_op_array(zend_op_array *op_array TSRMLS_DC) /* {{{ */
  287. {
  288. zend_uint size;
  289. coverager_t cov;
  290. zend_uint i;
  291. if (op_array->type != ZEND_USER_FUNCTION) {
  292. return 0;
  293. }
  294. size = xc_coverager_get_op_array_size_no_tail(op_array);
  295. cov = xc_coverager_get(op_array->filename TSRMLS_CC);
  296. for (i = 0; i < size; i ++) {
  297. switch (op_array->opcodes[i].opcode) {
  298. case ZEND_EXT_STMT:
  299. #if 0
  300. case ZEND_EXT_FCALL_BEGIN:
  301. case ZEND_EXT_FCALL_END:
  302. #endif
  303. xc_coverager_add_hits(cov, op_array->opcodes[i].lineno, -1 TSRMLS_CC);
  304. break;
  305. }
  306. }
  307. return 0;
  308. }
  309. /* }}} */
  310. static void xc_coverager_init_compile_result(zend_op_array *op_array TSRMLS_DC) /* {{{ */
  311. {
  312. xc_compile_result_t cr;
  313. xc_compile_result_init_cur(&cr, op_array TSRMLS_CC);
  314. xc_apply_op_array(&cr, (apply_func_t) xc_coverager_init_op_array TSRMLS_CC);
  315. xc_compile_result_free(&cr);
  316. }
  317. /* }}} */
  318. static zend_op_array *xc_compile_file_for_coverage(zend_file_handle *h, int type TSRMLS_DC) /* {{{ */
  319. {
  320. zend_op_array *op_array;
  321. op_array = origin_compile_file(h, type TSRMLS_CC);
  322. if (XG(coveragedumper) && XG(coverages) && op_array) {
  323. xc_coverager_init_compile_result(op_array TSRMLS_CC);
  324. }
  325. return op_array;
  326. }
  327. /* }}} */
  328. /* hits */
  329. void xc_coverager_handle_ext_stmt(zend_op_array *op_array, zend_uchar op) /* {{{ */
  330. {
  331. TSRMLS_FETCH();
  332. if (XG(coveragedumper) && XG(coverages)) {
  333. int size = xc_coverager_get_op_array_size_no_tail(op_array);
  334. int oplineno = (*EG(opline_ptr)) - op_array->opcodes;
  335. if (oplineno < size) {
  336. xc_coverager_add_hits(xc_coverager_get(op_array->filename TSRMLS_CC), (*EG(opline_ptr))->lineno, 1 TSRMLS_CC);
  337. }
  338. }
  339. }
  340. /* }}} */
  341. /* init/destroy */
  342. int xc_coverager_init(int module_number TSRMLS_DC) /* {{{ */
  343. {
  344. if (xc_coveragedump_dir) {
  345. int len = strlen(xc_coveragedump_dir);
  346. if (len) {
  347. if (xc_coveragedump_dir[len - 1] == '/') {
  348. xc_coveragedump_dir[len - 1] = '\0';
  349. }
  350. }
  351. }
  352. if (xc_coveragedump_dir && xc_coveragedump_dir[0]) {
  353. origin_compile_file = zend_compile_file;
  354. zend_compile_file = xc_compile_file_for_coverage;
  355. CG(extended_info) = 1;
  356. }
  357. return SUCCESS;
  358. }
  359. /* }}} */
  360. void xc_coverager_destroy() /* {{{ */
  361. {
  362. if (origin_compile_file == xc_compile_file_for_coverage) {
  363. zend_compile_file = origin_compile_file;
  364. }
  365. if (xc_coveragedump_dir) {
  366. pefree(xc_coveragedump_dir, 1);
  367. xc_coveragedump_dir = NULL;
  368. }
  369. }
  370. /* }}} */
  371. /* user api */
  372. PHP_FUNCTION(xcache_coverager_decode) /* {{{ */
  373. {
  374. char *str;
  375. int len;
  376. long *p;
  377. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &len) == FAILURE) {
  378. return;
  379. }
  380. array_init(return_value);
  381. p = (long*) str;
  382. len -= sizeof(long);
  383. if (len < 0) {
  384. return;
  385. }
  386. if (*p++ != PCOV_HEADER_MAGIC) {
  387. #ifdef DEBUG
  388. fprintf(stderr, "wrong magic in xcache_coverager_decode");
  389. #endif
  390. return;
  391. }
  392. for (; len >= sizeof(long) * 2; len -= sizeof(long) * 2, p += 2) {
  393. add_index_long(return_value, p[0], p[1]);
  394. }
  395. }
  396. /* }}} */