retrocl_trim.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. /** BEGIN COPYRIGHT BLOCK
  2. * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
  3. * Copyright (C) 2005 Red Hat, Inc.
  4. * All rights reserved.
  5. *
  6. * License: GPL (version 3 or any later version).
  7. * See LICENSE for details.
  8. * END COPYRIGHT BLOCK **/
  9. #ifdef HAVE_CONFIG_H
  10. #include <config.h>
  11. #endif
  12. #include "retrocl.h"
  13. typedef struct _trim_status
  14. {
  15. time_t ts_c_max_age; /* Constraint - max age of a changelog entry */
  16. time_t ts_s_last_trim; /* Status - last time we trimmed */
  17. int ts_s_initialized; /* Status - non-zero if initialized */
  18. int ts_s_trimming; /* non-zero if trimming in progress */
  19. PRLock *ts_s_trim_mutex; /* protects ts_s_trimming */
  20. } trim_status;
  21. static trim_status ts = {0L, 0L, 0, 0, NULL};
  22. static int trim_interval = DEFAULT_CHANGELOGDB_TRIM_INTERVAL; /* in second */
  23. /*
  24. * All standard changeLogEntry attributes (initialized in get_cleattrs)
  25. */
  26. static const char *cleattrs[10] = {NULL, NULL, NULL, NULL, NULL, NULL,
  27. NULL, NULL, NULL};
  28. static int retrocl_trimming = 0;
  29. static Slapi_Eq_Context retrocl_trim_ctx = NULL;
  30. /*
  31. * Function: get_cleattrs
  32. *
  33. * Returns: an array of pointers to attribute names.
  34. *
  35. * Arguments: None.
  36. *
  37. * Description: Initializes, if necessary, and returns an array of char *s
  38. * with attribute names used for retrieving changeLogEntry
  39. * entries from the directory.
  40. */
  41. static const char **
  42. get_cleattrs(void)
  43. {
  44. if (cleattrs[0] == NULL) {
  45. cleattrs[0] = attr_objectclass;
  46. cleattrs[1] = attr_changenumber;
  47. cleattrs[2] = attr_targetdn;
  48. cleattrs[3] = attr_changetype;
  49. cleattrs[4] = attr_newrdn;
  50. cleattrs[5] = attr_deleteoldrdn;
  51. cleattrs[6] = attr_changes;
  52. cleattrs[7] = attr_newsuperior;
  53. cleattrs[8] = attr_changetime;
  54. cleattrs[9] = NULL;
  55. }
  56. return cleattrs;
  57. }
  58. /*
  59. * Function: delete_changerecord
  60. *
  61. * Returns: LDAP_ error code
  62. *
  63. * Arguments: the number of the change to delete
  64. *
  65. * Description:
  66. *
  67. */
  68. static int
  69. delete_changerecord(changeNumber cnum)
  70. {
  71. Slapi_PBlock *pb;
  72. char *dnbuf;
  73. int delrc;
  74. dnbuf = slapi_ch_smprintf("%s=%ld, %s", attr_changenumber, cnum,
  75. RETROCL_CHANGELOG_DN);
  76. pb = slapi_pblock_new();
  77. slapi_delete_internal_set_pb(pb, dnbuf, NULL /*controls*/, NULL /* uniqueid */,
  78. g_plg_identity[PLUGIN_RETROCL], 0 /* actions */);
  79. slapi_delete_internal_pb(pb);
  80. slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &delrc);
  81. slapi_pblock_destroy(pb);
  82. if (delrc != LDAP_SUCCESS) {
  83. slapi_log_err(SLAPI_LOG_ERR, RETROCL_PLUGIN_NAME,
  84. "delete_changerecord: could not delete change record %lu (rc: %d)\n",
  85. cnum, delrc);
  86. } else {
  87. slapi_log_err(SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
  88. "delete_changerecord: deleted changelog entry \"%s\"\n", dnbuf);
  89. }
  90. slapi_ch_free((void **)&dnbuf);
  91. return delrc;
  92. }
  93. /*
  94. * Function: handle_getchangetime_result
  95. * Arguments: op - pointer to Operation struct for this operation
  96. * err - error code returned from search
  97. * Returns: nothing
  98. * Description: result handler for get_changetime(). Sets the crt_err
  99. * field of the cnum_result_t struct to the error returned
  100. * from the backend.
  101. */
  102. static void
  103. handle_getchangetime_result(int err, void *callback_data)
  104. {
  105. cnum_result_t *crt = callback_data;
  106. if (crt == NULL) {
  107. slapi_log_err(SLAPI_LOG_ERR, RETROCL_PLUGIN_NAME,
  108. "handle_getchangetime_result: callback_data NULL\n");
  109. } else {
  110. crt->crt_err = err;
  111. }
  112. }
  113. /*
  114. * Function: handle_getchangetime_search
  115. * Arguments: op - pointer to Operation struct for this operation
  116. * e - entry returned by backend
  117. * Returns: 0 in all cases
  118. * Description: Search result operation handler for get_changetime().
  119. * Sets fields in the cnum_result_t struct pointed to by
  120. * op->o_handler_data.
  121. */
  122. static int
  123. handle_getchangetime_search(Slapi_Entry *e, void *callback_data)
  124. {
  125. cnum_result_t *crt = callback_data;
  126. int rc;
  127. Slapi_Attr *attr;
  128. if (crt == NULL) {
  129. slapi_log_err(SLAPI_LOG_ERR, RETROCL_PLUGIN_NAME,
  130. "handle_getchangetime_search: op->o_handler_data NULL\n");
  131. } else if (crt->crt_nentries > 0) {
  132. /* only return the first entry, I guess */
  133. slapi_log_err(SLAPI_LOG_ERR, RETROCL_PLUGIN_NAME,
  134. "handle_getchangetime_search: multiple entries returned\n");
  135. } else {
  136. crt->crt_nentries++;
  137. crt->crt_time = 0;
  138. if (NULL != e) {
  139. Slapi_Value *sval = NULL;
  140. const struct berval *val = NULL;
  141. rc = slapi_entry_attr_find(e, attr_changetime, &attr);
  142. /* Bug 624442: Logic checking for lack of timestamp was
  143. reversed. */
  144. if (0 != rc || slapi_attr_first_value(attr, &sval) == -1 ||
  145. (val = slapi_value_get_berval(sval)) == NULL ||
  146. NULL == val->bv_val) {
  147. crt->crt_time = 0;
  148. } else {
  149. crt->crt_time = parse_localTime(val->bv_val);
  150. }
  151. }
  152. }
  153. return 0;
  154. }
  155. /*
  156. * Function: get_changetime
  157. * Arguments: cnum - number of change record to retrieve
  158. * Returns: Taking the attr_changetime of the 'cnum' entry,
  159. * it converts it into time_t (parse_localTime) and returns this time value.
  160. * It returns 0 in the following cases:
  161. * - changerecord entry has not attr_changetime
  162. * - attr_changetime attribute has no value
  163. * - attr_changetime attribute value is empty
  164. *
  165. * Description: Retrieve attr_changetime ("changetime") from a changerecord whose number is "cnum".
  166. */
  167. static time_t
  168. get_changetime(changeNumber cnum, int *err)
  169. {
  170. cnum_result_t crt, *crtp = &crt;
  171. char fstr[16 + CNUMSTR_LEN + 2];
  172. Slapi_PBlock *pb;
  173. if (cnum == 0UL) {
  174. if (err != NULL) {
  175. *err = LDAP_PARAM_ERROR;
  176. }
  177. return 0;
  178. }
  179. crtp->crt_nentries = crtp->crt_err = 0;
  180. crtp->crt_time = 0;
  181. PR_snprintf(fstr, sizeof(fstr), "%s=%ld", attr_changenumber, cnum);
  182. pb = slapi_pblock_new();
  183. slapi_search_internal_set_pb(pb, RETROCL_CHANGELOG_DN,
  184. LDAP_SCOPE_SUBTREE, fstr,
  185. (char **)get_cleattrs(), /* cast const */
  186. 0 /* attrsonly */,
  187. NULL /* controls */, NULL /* uniqueid */,
  188. g_plg_identity[PLUGIN_RETROCL],
  189. 0 /* actions */);
  190. slapi_search_internal_callback_pb(pb, crtp,
  191. handle_getchangetime_result,
  192. handle_getchangetime_search, NULL);
  193. if (err != NULL) {
  194. *err = crtp->crt_err;
  195. }
  196. slapi_pblock_destroy(pb);
  197. return (crtp->crt_time);
  198. }
  199. /*
  200. * Function: trim_changelog
  201. *
  202. * Arguments: none
  203. *
  204. * Returns: 0 on success, -1 on failure
  205. *
  206. * Description: Trims the changelog, according to the constraints
  207. * described by the ts structure.
  208. */
  209. static int
  210. trim_changelog(void)
  211. {
  212. int rc = 0, ldrc, done;
  213. time_t now;
  214. changeNumber first_in_log = 0, last_in_log = 0;
  215. int num_deleted = 0;
  216. int me, lt;
  217. now = slapi_current_utc_time();
  218. PR_Lock(ts.ts_s_trim_mutex);
  219. me = ts.ts_c_max_age;
  220. lt = ts.ts_s_last_trim;
  221. PR_Unlock(ts.ts_s_trim_mutex);
  222. if (now - lt >= trim_interval) {
  223. /*
  224. * Trim the changelog. Read sequentially through all the
  225. * entries, deleting any which do not meet the criteria
  226. * described in the ts structure.
  227. */
  228. done = 0;
  229. while (!done && retrocl_trimming == 1) {
  230. int did_delete;
  231. did_delete = 0;
  232. first_in_log = retrocl_get_first_changenumber();
  233. if (0UL == first_in_log) {
  234. slapi_log_err(SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
  235. "trim_changelog: no changelog records "
  236. "to trim\n");
  237. /* Bail out - we can't do any useful work */
  238. break;
  239. }
  240. last_in_log = retrocl_get_last_changenumber();
  241. if (last_in_log == first_in_log) {
  242. /* Always leave at least one entry in the change log */
  243. break;
  244. }
  245. if (me > 0L) {
  246. time_t change_time = get_changetime(first_in_log, &ldrc);
  247. if (change_time) {
  248. if ((change_time + me) < now) {
  249. retrocl_set_first_changenumber(first_in_log + 1);
  250. ldrc = delete_changerecord(first_in_log);
  251. num_deleted++;
  252. did_delete = 1;
  253. }
  254. } else {
  255. /* What to do if there's no timestamp? Just delete it. */
  256. retrocl_set_first_changenumber(first_in_log + 1);
  257. ldrc = delete_changerecord(first_in_log);
  258. num_deleted++;
  259. did_delete = 1;
  260. }
  261. }
  262. if (!did_delete) {
  263. done = 1;
  264. }
  265. }
  266. } else {
  267. slapi_log_err(SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME, "Not yet time to trim: %ld < (%d+%d)\n",
  268. now, lt, trim_interval);
  269. }
  270. PR_Lock(ts.ts_s_trim_mutex);
  271. ts.ts_s_trimming = 0;
  272. ts.ts_s_last_trim = now;
  273. PR_Unlock(ts.ts_s_trim_mutex);
  274. if (num_deleted > 0) {
  275. slapi_log_err(SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
  276. "trim_changelog: removed %d change records\n",
  277. num_deleted);
  278. }
  279. return rc;
  280. }
  281. static int retrocl_active_threads;
  282. /*
  283. * Function: changelog_trim_thread_fn
  284. *
  285. * Returns: nothing
  286. *
  287. * Arguments: none
  288. *
  289. * Description: the thread creation callback. retrocl_active_threads is
  290. * provided for debugging purposes.
  291. *
  292. */
  293. static void
  294. changelog_trim_thread_fn(void *arg __attribute__((unused)))
  295. {
  296. PR_AtomicIncrement(&retrocl_active_threads);
  297. trim_changelog();
  298. PR_AtomicDecrement(&retrocl_active_threads);
  299. }
  300. /*
  301. * Function: retrocl_housekeeping
  302. * Arguments: cur_time - the current time
  303. * Returns: nothing
  304. * Description: Determines if it is time to trim the changelog database,
  305. * and if so, determines if the changelog database needs to
  306. * be trimmed. If so, a thread is started which will trim
  307. * the database.
  308. */
  309. void
  310. retrocl_housekeeping(time_t cur_time, void *noarg __attribute__((unused)))
  311. {
  312. int ldrc;
  313. if (retrocl_be_changelog == NULL) {
  314. slapi_log_err(SLAPI_LOG_TRACE, RETROCL_PLUGIN_NAME, "retrocl_housekeeping - not housekeeping if no cl be\n");
  315. return;
  316. }
  317. if (!ts.ts_s_initialized) {
  318. slapi_log_err(SLAPI_LOG_ERR, RETROCL_PLUGIN_NAME, "retrocl_housekeeping - called before "
  319. "trimming constraints set\n");
  320. return;
  321. }
  322. PR_Lock(ts.ts_s_trim_mutex);
  323. if (!ts.ts_s_trimming) {
  324. int must_trim = 0;
  325. /* See if we need to trim */
  326. /* Has enough time elapsed since our last check? */
  327. if (cur_time - ts.ts_s_last_trim >= (ts.ts_c_max_age)) {
  328. /* Is the first entry too old? */
  329. time_t first_time;
  330. /*
  331. * good we could avoid going to the database to retrieve
  332. * this time information if we cached the last value we'd read.
  333. * But a client might have deleted it over protocol.
  334. */
  335. first_time = retrocl_getchangetime(SLAPI_SEQ_FIRST, &ldrc);
  336. slapi_log_err(SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
  337. "cltrim: ldrc=%d, first_time=%ld, cur_time=%ld\n",
  338. ldrc, first_time, cur_time);
  339. if (LDAP_SUCCESS == ldrc && first_time > (time_t)0L &&
  340. first_time + ts.ts_c_max_age < cur_time) {
  341. must_trim = 1;
  342. }
  343. }
  344. if (must_trim) {
  345. slapi_log_err(SLAPI_LOG_TRACE, RETROCL_PLUGIN_NAME, "retrocl_housekeeping - changelog about to create thread\n");
  346. /* Start a thread to trim the changelog */
  347. ts.ts_s_trimming = 1;
  348. if (PR_CreateThread(PR_USER_THREAD,
  349. changelog_trim_thread_fn, NULL,
  350. PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD,
  351. RETROCL_DLL_DEFAULT_THREAD_STACKSIZE) == NULL) {
  352. slapi_log_err(SLAPI_LOG_ERR, RETROCL_PLUGIN_NAME, "retrocl_housekeeping - "
  353. "Unable to create changelog trimming thread\n");
  354. }
  355. } else {
  356. slapi_log_err(SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
  357. "retrocl_housekeeping - changelog does not need to be trimmed\n");
  358. }
  359. }
  360. PR_Unlock(ts.ts_s_trim_mutex);
  361. }
  362. /*
  363. * Function: retrocl_init_trimming
  364. *
  365. * Returns: none, exits on fatal error
  366. *
  367. * Arguments: none
  368. *
  369. * Description: called during startup
  370. *
  371. */
  372. void
  373. retrocl_init_trimming(void)
  374. {
  375. const char *cl_maxage;
  376. time_t ageval = 0; /* Don't trim, by default */
  377. const char *cl_trim_interval;
  378. cl_maxage = retrocl_get_config_str(CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE);
  379. if (cl_maxage) {
  380. if (slapi_is_duration_valid(cl_maxage)) {
  381. ageval = slapi_parse_duration(cl_maxage);
  382. slapi_ch_free_string((char **)&cl_maxage);
  383. } else {
  384. slapi_log_err(SLAPI_LOG_ERR, RETROCL_PLUGIN_NAME,
  385. "retrocl_init_trimming: ignoring invalid %s value %s; "
  386. "not trimming retro changelog.\n",
  387. CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE, cl_maxage);
  388. slapi_ch_free_string((char **)&cl_maxage);
  389. return;
  390. }
  391. }
  392. cl_trim_interval = retrocl_get_config_str(CONFIG_CHANGELOG_TRIM_INTERVAL);
  393. if (cl_trim_interval) {
  394. trim_interval = strtol(cl_trim_interval, (char **)NULL, 10);
  395. if (0 == trim_interval) {
  396. slapi_log_err(SLAPI_LOG_ERR, RETROCL_PLUGIN_NAME,
  397. "retrocl_init_trimming: ignoring invalid %s value %s; "
  398. "resetting the default %d\n",
  399. CONFIG_CHANGELOG_TRIM_INTERVAL, cl_trim_interval,
  400. DEFAULT_CHANGELOGDB_TRIM_INTERVAL);
  401. trim_interval = DEFAULT_CHANGELOGDB_TRIM_INTERVAL;
  402. }
  403. slapi_ch_free_string((char **)&cl_trim_interval);
  404. }
  405. ts.ts_c_max_age = ageval;
  406. ts.ts_s_last_trim = (time_t)0L;
  407. ts.ts_s_trimming = 0;
  408. if ((ts.ts_s_trim_mutex = PR_NewLock()) == NULL) {
  409. slapi_log_err(SLAPI_LOG_ERR, RETROCL_PLUGIN_NAME, "set_changelog_trim_constraints: "
  410. "cannot create new lock.\n");
  411. exit(1);
  412. }
  413. ts.ts_s_initialized = 1;
  414. retrocl_trimming = 1;
  415. retrocl_trim_ctx = slapi_eq_repeat(retrocl_housekeeping,
  416. NULL, (time_t)0,
  417. /* in milliseconds */
  418. trim_interval * 1000);
  419. }
  420. /*
  421. * Function: retrocl_stop_trimming
  422. *
  423. * Returns: none
  424. *
  425. * Arguments: none
  426. *
  427. * Description: called when server is shutting down to ensure trimming stops
  428. * eventually.
  429. *
  430. */
  431. void
  432. retrocl_stop_trimming(void)
  433. {
  434. retrocl_trimming = 0;
  435. if (retrocl_trim_ctx) {
  436. slapi_eq_cancel(retrocl_trim_ctx);
  437. retrocl_trim_ctx = NULL;
  438. }
  439. PR_DestroyLock(ts.ts_s_trim_mutex);
  440. ts.ts_s_trim_mutex = NULL;
  441. }