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.

435 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, size, alloc_len = 0;
  192. if (!XG(coverages)) {
  193. return;
  194. }
  195. if (XG(coveragedumper)) {
  196. dumpdir_len = strlen(xc_coveragedump_dir);
  197. alloc_len = dumpdir_len + 1 + 128;
  198. outfilename = emalloc(alloc_len);
  199. strcpy(outfilename, xc_coveragedump_dir);
  200. zend_hash_internal_pointer_reset(XG(coverages));
  201. while (zend_hash_get_current_data(XG(coverages), (void **) &pcov) == SUCCESS) {
  202. zend_hash_get_current_key_ex(XG(coverages), &s, &size, NULL, 0, NULL);
  203. outfilelen = dumpdir_len + size + 5;
  204. if (alloc_len < outfilelen) {
  205. alloc_len = outfilelen + 128;
  206. outfilename = erealloc(outfilename, alloc_len);
  207. }
  208. strcpy(outfilename + dumpdir_len, ZSTR_S(s));
  209. strcpy(outfilename + dumpdir_len + size - 1, ".pcov");
  210. #ifdef DEBUG
  211. fprintf(stderr, "outfilename %s\n", outfilename);
  212. #endif
  213. xc_coverager_save_cov(ZSTR_S(s), outfilename, *pcov TSRMLS_CC);
  214. zend_hash_move_forward(XG(coverages));
  215. }
  216. efree(outfilename);
  217. }
  218. zend_hash_destroy(XG(coverages));
  219. efree(XG(coverages));
  220. XG(coverages) = NULL;
  221. }
  222. /* }}} */
  223. /* helper func to store hits into coverages */
  224. static coverager_t xc_coverager_get(char *filename TSRMLS_DC) /* {{{ */
  225. {
  226. int len = strlen(filename) + 1;
  227. coverager_t cov, *pcov;
  228. if (zend_hash_find(XG(coverages), filename, len, (void **) &pcov) == SUCCESS) {
  229. #ifdef DEBUG
  230. fprintf(stderr, "got coverage %s %p\n", filename, *pcov);
  231. #endif
  232. return *pcov;
  233. }
  234. else {
  235. cov = emalloc(sizeof(HashTable));
  236. zend_hash_init(cov, 0, NULL, NULL, 0);
  237. zend_hash_add(XG(coverages), filename, len, (void **) &cov, sizeof(cov), NULL);
  238. #ifdef DEBUG
  239. fprintf(stderr, "new coverage %s %p\n", filename, cov);
  240. #endif
  241. return cov;
  242. }
  243. }
  244. /* }}} */
  245. static void xc_coverager_add_hits(HashTable *cov, long line, long hits TSRMLS_DC) /* {{{ */
  246. {
  247. long *poldhits;
  248. if (line == 0) {
  249. return;
  250. }
  251. if (zend_hash_index_find(cov, line, (void**)&poldhits) == SUCCESS) {
  252. if (hits == -1) {
  253. /* already marked */
  254. return;
  255. }
  256. if (*poldhits != -1) {
  257. hits += *poldhits;
  258. }
  259. }
  260. zend_hash_index_update(cov, line, &hits, sizeof(hits), NULL);
  261. }
  262. /* }}} */
  263. static int xc_coverager_get_op_array_size_no_tail(zend_op_array *op_array) /* {{{ */
  264. {
  265. zend_uint size;
  266. size = op_array->size;
  267. #ifdef ZEND_ENGINE_2
  268. if (op_array->opcodes[size - 1].opcode == ZEND_HANDLE_EXCEPTION) {
  269. size --;
  270. #endif
  271. if (op_array->opcodes[size - 1].opcode == ZEND_RETURN) {
  272. size --;
  273. /* it's not real php statement */
  274. if (op_array->opcodes[size - 1].opcode == ZEND_EXT_STMT) {
  275. size --;
  276. }
  277. }
  278. #ifdef ZEND_ENGINE_2
  279. }
  280. #endif
  281. return size;
  282. }
  283. /* }}} */
  284. /* prefill */
  285. static int xc_coverager_init_op_array(zend_op_array *op_array TSRMLS_DC) /* {{{ */
  286. {
  287. zend_uint size;
  288. coverager_t cov;
  289. zend_uint i;
  290. if (op_array->type != ZEND_USER_FUNCTION) {
  291. return 0;
  292. }
  293. size = xc_coverager_get_op_array_size_no_tail(op_array);
  294. cov = xc_coverager_get(op_array->filename TSRMLS_CC);
  295. for (i = 0; i < size; i ++) {
  296. switch (op_array->opcodes[i].opcode) {
  297. case ZEND_EXT_STMT:
  298. #if 0
  299. case ZEND_EXT_FCALL_BEGIN:
  300. case ZEND_EXT_FCALL_END:
  301. #endif
  302. xc_coverager_add_hits(cov, op_array->opcodes[i].lineno, -1 TSRMLS_CC);
  303. break;
  304. }
  305. }
  306. return 0;
  307. }
  308. /* }}} */
  309. static void xc_coverager_init_compile_result(zend_op_array *op_array TSRMLS_DC) /* {{{ */
  310. {
  311. xc_compile_result_t cr;
  312. xc_compile_result_init_cur(&cr, op_array TSRMLS_CC);
  313. xc_apply_op_array(&cr, (apply_func_t) xc_coverager_init_op_array TSRMLS_CC);
  314. xc_compile_result_free(&cr);
  315. }
  316. /* }}} */
  317. static zend_op_array *xc_compile_file_for_coverage(zend_file_handle *h, int type TSRMLS_DC) /* {{{ */
  318. {
  319. zend_op_array *op_array;
  320. op_array = origin_compile_file(h, type TSRMLS_CC);
  321. if (XG(coveragedumper) && XG(coverages)) {
  322. xc_coverager_init_compile_result(op_array TSRMLS_CC);
  323. }
  324. return op_array;
  325. }
  326. /* }}} */
  327. /* hits */
  328. void xc_coverager_handle_ext_stmt(zend_op_array *op_array, zend_uchar op) /* {{{ */
  329. {
  330. TSRMLS_FETCH();
  331. if (XG(coveragedumper) && XG(coverages)) {
  332. int size = xc_coverager_get_op_array_size_no_tail(op_array);
  333. int oplineno = (*EG(opline_ptr)) - op_array->opcodes;
  334. if (oplineno < size) {
  335. xc_coverager_add_hits(xc_coverager_get(op_array->filename TSRMLS_CC), (*EG(opline_ptr))->lineno, 1 TSRMLS_CC);
  336. }
  337. }
  338. }
  339. /* }}} */
  340. /* init/destroy */
  341. int xc_coverager_init(int module_number TSRMLS_DC) /* {{{ */
  342. {
  343. if (xc_coveragedump_dir) {
  344. int len = strlen(xc_coveragedump_dir);
  345. if (len) {
  346. if (xc_coveragedump_dir[len - 1] == '/') {
  347. xc_coveragedump_dir[len - 1] = '\0';
  348. }
  349. }
  350. }
  351. if (xc_coveragedump_dir && xc_coveragedump_dir[0]) {
  352. origin_compile_file = zend_compile_file;
  353. zend_compile_file = xc_compile_file_for_coverage;
  354. CG(extended_info) = 1;
  355. }
  356. return SUCCESS;
  357. }
  358. /* }}} */
  359. void xc_coverager_destroy() /* {{{ */
  360. {
  361. if (origin_compile_file == xc_compile_file_for_coverage) {
  362. zend_compile_file = origin_compile_file;
  363. }
  364. if (xc_coveragedump_dir) {
  365. pefree(xc_coveragedump_dir, 1);
  366. xc_coveragedump_dir = NULL;
  367. }
  368. }
  369. /* }}} */
  370. /* user api */
  371. PHP_FUNCTION(xcache_coverager_decode) /* {{{ */
  372. {
  373. char *str;
  374. int len;
  375. long *p;
  376. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &len) == FAILURE) {
  377. return;
  378. }
  379. array_init(return_value);
  380. p = (long*) str;
  381. len -= sizeof(long);
  382. if (len < 0) {
  383. return;
  384. }
  385. if (*p++ != PCOV_HEADER_MAGIC) {
  386. #ifdef DEBUG
  387. fprintf(stderr, "wrong magic in xcache_coverager_decode");
  388. #endif
  389. return;
  390. }
  391. for (; len >= sizeof(long) * 2; len -= sizeof(long) * 2, p += 2) {
  392. add_index_long(return_value, p[0], p[1]);
  393. }
  394. }
  395. /* }}} */