retrocl_trim.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. /** BEGIN COPYRIGHT BLOCK
  2. * This Program is free software; you can redistribute it and/or modify it under
  3. * the terms of the GNU General Public License as published by the Free Software
  4. * Foundation; version 2 of the License.
  5. *
  6. * This Program is distributed in the hope that it will be useful, but WITHOUT
  7. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  8. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  9. *
  10. * You should have received a copy of the GNU General Public License along with
  11. * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
  12. * Place, Suite 330, Boston, MA 02111-1307 USA.
  13. *
  14. * In addition, as a special exception, Red Hat, Inc. gives You the additional
  15. * right to link the code of this Program with code not covered under the GNU
  16. * General Public License ("Non-GPL Code") and to distribute linked combinations
  17. * including the two, subject to the limitations in this paragraph. Non-GPL Code
  18. * permitted under this exception must only link to the code of this Program
  19. * through those well defined interfaces identified in the file named EXCEPTION
  20. * found in the source code files (the "Approved Interfaces"). The files of
  21. * Non-GPL Code may instantiate templates or use macros or inline functions from
  22. * the Approved Interfaces without causing the resulting work to be covered by
  23. * the GNU General Public License. Only Red Hat, Inc. may make changes or
  24. * additions to the list of Approved Interfaces. You must obey the GNU General
  25. * Public License in all respects for all of the Program code and other code used
  26. * in conjunction with the Program except the Non-GPL Code covered by this
  27. * exception. If you modify this file, you may extend this exception to your
  28. * version of the file, but you are not obligated to do so. If you do not wish to
  29. * provide this exception without modification, you must delete this exception
  30. * statement from your version and license this file solely under the GPL without
  31. * exception.
  32. *
  33. *
  34. * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
  35. * Copyright (C) 2005 Red Hat, Inc.
  36. * All rights reserved.
  37. * END COPYRIGHT BLOCK **/
  38. #ifdef HAVE_CONFIG_H
  39. # include <config.h>
  40. #endif
  41. #include "retrocl.h"
  42. typedef struct _trim_status {
  43. time_t ts_c_max_age; /* Constraint - max age of a changelog entry */
  44. time_t ts_s_last_trim; /* Status - last time we trimmed */
  45. int ts_s_initialized; /* Status - non-zero if initialized */
  46. int ts_s_trimming; /* non-zero if trimming in progress */
  47. PRLock *ts_s_trim_mutex; /* protects ts_s_trimming */
  48. } trim_status;
  49. static trim_status ts = {0L, 0L, 0, 0, NULL};
  50. static int trim_interval = DEFAULT_CHANGELOGDB_TRIM_INTERVAL; /* in second */
  51. /*
  52. * All standard changeLogEntry attributes (initialized in get_cleattrs)
  53. */
  54. static const char *cleattrs[ 10 ] = { NULL, NULL, NULL, NULL, NULL, NULL,
  55. NULL, NULL, NULL };
  56. static int retrocl_trimming = 0;
  57. static Slapi_Eq_Context retrocl_trim_ctx = NULL;
  58. /*
  59. * Function: get_cleattrs
  60. *
  61. * Returns: an array of pointers to attribute names.
  62. *
  63. * Arguments: None.
  64. *
  65. * Description: Initializes, if necessary, and returns an array of char *s
  66. * with attribute names used for retrieving changeLogEntry
  67. * entries from the directory.
  68. */
  69. static const char **get_cleattrs(void)
  70. {
  71. if ( cleattrs[ 0 ] == NULL ) {
  72. cleattrs[ 0 ] = attr_objectclass;
  73. cleattrs[ 1 ] = attr_changenumber;
  74. cleattrs[ 2 ] = attr_targetdn;
  75. cleattrs[ 3 ] = attr_changetype;
  76. cleattrs[ 4 ] = attr_newrdn;
  77. cleattrs[ 5 ] = attr_deleteoldrdn;
  78. cleattrs[ 6 ] = attr_changes;
  79. cleattrs[ 7 ] = attr_newsuperior;
  80. cleattrs[ 8 ] = attr_changetime;
  81. cleattrs[ 9 ] = NULL;
  82. }
  83. return cleattrs;
  84. }
  85. /*
  86. * Function: delete_changerecord
  87. *
  88. * Returns: LDAP_ error code
  89. *
  90. * Arguments: the number of the change to delete
  91. *
  92. * Description:
  93. *
  94. */
  95. static int
  96. delete_changerecord( changeNumber cnum )
  97. {
  98. Slapi_PBlock *pb;
  99. char *dnbuf;
  100. int delrc;
  101. dnbuf = slapi_ch_smprintf("%s=%ld, %s", attr_changenumber, cnum,
  102. RETROCL_CHANGELOG_DN);
  103. pb = slapi_pblock_new ();
  104. slapi_delete_internal_set_pb ( pb, dnbuf, NULL /*controls*/, NULL /* uniqueid */,
  105. g_plg_identity[PLUGIN_RETROCL], 0 /* actions */ );
  106. slapi_delete_internal_pb (pb);
  107. slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &delrc );
  108. slapi_pblock_destroy( pb );
  109. if ( delrc != LDAP_SUCCESS ) {
  110. slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME,
  111. "delete_changerecord: could not delete change record %lu (rc: %d)\n",
  112. cnum, delrc );
  113. } else {
  114. slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
  115. "delete_changerecord: deleted changelog entry \"%s\"\n", dnbuf);
  116. }
  117. slapi_ch_free((void **) &dnbuf );
  118. return delrc;
  119. }
  120. /*
  121. * Function: handle_getchangetime_result
  122. * Arguments: op - pointer to Operation struct for this operation
  123. * err - error code returned from search
  124. * Returns: nothing
  125. * Description: result handler for get_changetime(). Sets the crt_err
  126. * field of the cnum_result_t struct to the error returned
  127. * from the backend.
  128. */
  129. static void
  130. handle_getchangetime_result( int err, void *callback_data )
  131. {
  132. cnum_result_t *crt = callback_data;
  133. if ( crt == NULL ) {
  134. slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME,
  135. "handle_getchangetime_result: callback_data NULL\n" );
  136. } else {
  137. crt->crt_err = err;
  138. }
  139. }
  140. /*
  141. * Function: handle_getchangetime_search
  142. * Arguments: op - pointer to Operation struct for this operation
  143. * e - entry returned by backend
  144. * Returns: 0 in all cases
  145. * Description: Search result operation handler for get_changetime().
  146. * Sets fields in the cnum_result_t struct pointed to by
  147. * op->o_handler_data.
  148. */
  149. static int
  150. handle_getchangetime_search( Slapi_Entry *e, void *callback_data)
  151. {
  152. cnum_result_t *crt = callback_data;
  153. int rc;
  154. Slapi_Attr *attr;
  155. if (crt == NULL) {
  156. slapi_log_error(SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME,
  157. "handle_getchangetime_search: op->o_handler_data NULL\n");
  158. } else if (crt->crt_nentries > 0) {
  159. /* only return the first entry, I guess */
  160. slapi_log_error(SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME,
  161. "handle_getchangetime_search: multiple entries returned\n");
  162. } else {
  163. crt->crt_nentries++;
  164. crt->crt_time = 0;
  165. if (NULL != e) {
  166. Slapi_Value *sval = NULL;
  167. const struct berval *val = NULL;
  168. rc = slapi_entry_attr_find(e, attr_changetime, &attr);
  169. /* Bug 624442: Logic checking for lack of timestamp was
  170. reversed. */
  171. if (0 != rc || slapi_attr_first_value(attr, &sval) == -1 ||
  172. (val = slapi_value_get_berval(sval)) == NULL ||
  173. NULL == val->bv_val) {
  174. crt->crt_time = 0;
  175. } else {
  176. crt->crt_time = parse_localTime(val->bv_val);
  177. }
  178. }
  179. }
  180. return 0;
  181. }
  182. /*
  183. * Function: get_changetime
  184. * Arguments: cnum - number of change record to retrieve
  185. * Returns: Taking the attr_changetime of the 'cnum' entry,
  186. * it converts it into time_t (parse_localTime) and returns this time value.
  187. * It returns 0 in the following cases:
  188. * - changerecord entry has not attr_changetime
  189. * - attr_changetime attribute has no value
  190. * - attr_changetime attribute value is empty
  191. *
  192. * Description: Retrieve attr_changetime ("changetime") from a changerecord whose number is "cnum".
  193. */
  194. static time_t get_changetime( changeNumber cnum, int *err )
  195. {
  196. cnum_result_t crt, *crtp = &crt;
  197. char fstr[ 16 + CNUMSTR_LEN + 2 ];
  198. Slapi_PBlock *pb;
  199. if (cnum == 0UL) {
  200. if (err != NULL) {
  201. *err = LDAP_PARAM_ERROR;
  202. }
  203. return 0;
  204. }
  205. crtp->crt_nentries = crtp->crt_err = 0;
  206. crtp->crt_time = 0;
  207. PR_snprintf(fstr, sizeof (fstr), "%s=%ld", attr_changenumber, cnum);
  208. pb = slapi_pblock_new();
  209. slapi_search_internal_set_pb(pb, RETROCL_CHANGELOG_DN,
  210. LDAP_SCOPE_SUBTREE, fstr,
  211. (char **) get_cleattrs(), /* cast const */
  212. 0 /* attrsonly */,
  213. NULL /* controls */, NULL /* uniqueid */,
  214. g_plg_identity[PLUGIN_RETROCL],
  215. 0 /* actions */);
  216. slapi_search_internal_callback_pb(pb, crtp,
  217. handle_getchangetime_result,
  218. handle_getchangetime_search, NULL);
  219. if (err != NULL) {
  220. *err = crtp->crt_err;
  221. }
  222. slapi_pblock_destroy(pb);
  223. return ( crtp->crt_time);
  224. }
  225. /*
  226. * Function: trim_changelog
  227. *
  228. * Arguments: none
  229. *
  230. * Returns: 0 on success, -1 on failure
  231. *
  232. * Description: Trims the changelog, according to the constraints
  233. * described by the ts structure.
  234. */
  235. static int trim_changelog(void)
  236. {
  237. int rc = 0, ldrc, done;
  238. time_t now;
  239. changeNumber first_in_log = 0, last_in_log = 0;
  240. int num_deleted = 0;
  241. int me,lt;
  242. now = current_time();
  243. PR_Lock( ts.ts_s_trim_mutex );
  244. me = ts.ts_c_max_age;
  245. lt = ts.ts_s_last_trim;
  246. PR_Unlock( ts.ts_s_trim_mutex );
  247. if ( now - lt >= trim_interval ) {
  248. /*
  249. * Trim the changelog. Read sequentially through all the
  250. * entries, deleting any which do not meet the criteria
  251. * described in the ts structure.
  252. */
  253. done = 0;
  254. while ( !done && retrocl_trimming == 1 ) {
  255. int did_delete;
  256. did_delete = 0;
  257. first_in_log = retrocl_get_first_changenumber();
  258. if ( 0UL == first_in_log ) {
  259. slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
  260. "trim_changelog: no changelog records "
  261. "to trim\n" );
  262. /* Bail out - we can't do any useful work */
  263. break;
  264. }
  265. last_in_log = retrocl_get_last_changenumber();
  266. if ( last_in_log == first_in_log ) {
  267. /* Always leave at least one entry in the change log */
  268. break;
  269. }
  270. if ( me > 0L ) {
  271. time_t change_time = get_changetime(first_in_log, &ldrc);
  272. if (change_time) {
  273. if ((change_time + me) < now) {
  274. retrocl_set_first_changenumber(first_in_log + 1);
  275. ldrc = delete_changerecord(first_in_log);
  276. num_deleted++;
  277. did_delete = 1;
  278. }
  279. } else {
  280. /* What to do if there's no timestamp? Just delete it. */
  281. retrocl_set_first_changenumber(first_in_log + 1);
  282. ldrc = delete_changerecord(first_in_log);
  283. num_deleted++;
  284. did_delete = 1;
  285. }
  286. }
  287. if ( !did_delete ) {
  288. done = 1;
  289. }
  290. }
  291. } else {
  292. LDAPDebug(LDAP_DEBUG_PLUGIN, "not yet time to trim: %ld < (%d+%d)\n",
  293. now, lt, trim_interval);
  294. }
  295. PR_Lock( ts.ts_s_trim_mutex );
  296. ts.ts_s_trimming = 0;
  297. ts.ts_s_last_trim = now;
  298. PR_Unlock( ts.ts_s_trim_mutex );
  299. if ( num_deleted > 0 ) {
  300. slapi_log_error( SLAPI_LOG_PLUGIN, RETROCL_PLUGIN_NAME,
  301. "trim_changelog: removed %d change records\n",
  302. num_deleted );
  303. }
  304. return rc;
  305. }
  306. static int retrocl_active_threads;
  307. /*
  308. * Function: changelog_trim_thread_fn
  309. *
  310. * Returns: nothing
  311. *
  312. * Arguments: none
  313. *
  314. * Description: the thread creation callback. retrocl_active_threads is
  315. * provided for debugging purposes.
  316. *
  317. */
  318. static void
  319. changelog_trim_thread_fn( void *arg )
  320. {
  321. PR_AtomicIncrement(&retrocl_active_threads);
  322. trim_changelog();
  323. PR_AtomicDecrement(&retrocl_active_threads);
  324. }
  325. /*
  326. * Function: retrocl_housekeeping
  327. * Arguments: cur_time - the current time
  328. * Returns: nothing
  329. * Description: Determines if it is time to trim the changelog database,
  330. * and if so, determines if the changelog database needs to
  331. * be trimmed. If so, a thread is started which will trim
  332. * the database.
  333. */
  334. void retrocl_housekeeping ( time_t cur_time, void *noarg )
  335. {
  336. int ldrc;
  337. if (retrocl_be_changelog == NULL) {
  338. LDAPDebug0Args(LDAP_DEBUG_TRACE,"not housekeeping if no cl be\n");
  339. return;
  340. }
  341. if ( !ts.ts_s_initialized ) {
  342. slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "changelog_housekeeping called before "
  343. "trimming constraints set\n" );
  344. return;
  345. }
  346. PR_Lock( ts.ts_s_trim_mutex );
  347. if ( !ts.ts_s_trimming ) {
  348. int must_trim = 0;
  349. /* See if we need to trim */
  350. /* Has enough time elapsed since our last check? */
  351. if ( cur_time - ts.ts_s_last_trim >= (ts.ts_c_max_age) ) {
  352. /* Is the first entry too old? */
  353. time_t first_time;
  354. /*
  355. * good we could avoid going to the database to retrieve
  356. * this time information if we cached the last value we'd read.
  357. * But a client might have deleted it over protocol.
  358. */
  359. first_time = retrocl_getchangetime( SLAPI_SEQ_FIRST, &ldrc );
  360. LDAPDebug(LDAP_DEBUG_PLUGIN,
  361. "cltrim: ldrc=%d, first_time=%ld, cur_time=%ld\n",
  362. ldrc,first_time,cur_time);
  363. if ( LDAP_SUCCESS == ldrc && first_time > (time_t) 0L &&
  364. first_time + ts.ts_c_max_age < cur_time ) {
  365. must_trim = 1;
  366. }
  367. }
  368. if ( must_trim ) {
  369. LDAPDebug0Args(LDAP_DEBUG_TRACE,"changelog about to create thread\n");
  370. /* Start a thread to trim the changelog */
  371. ts.ts_s_trimming = 1;
  372. if ( PR_CreateThread( PR_USER_THREAD,
  373. changelog_trim_thread_fn, NULL,
  374. PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD,
  375. RETROCL_DLL_DEFAULT_THREAD_STACKSIZE ) == NULL ) {
  376. slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "unable to create changelog trimming thread\n" );
  377. }
  378. } else {
  379. LDAPDebug0Args(LDAP_DEBUG_PLUGIN,
  380. "changelog does not need to be trimmed\n");
  381. }
  382. }
  383. PR_Unlock( ts.ts_s_trim_mutex );
  384. }
  385. /*
  386. * Function: age_str2time
  387. *
  388. * Returns: time_t
  389. *
  390. * Arguments: string representation of age (digits and unit s,m,h,d or w)
  391. *
  392. * Description:
  393. * convert time from string like 1h (1 hour) to corresponding time in seconds
  394. *
  395. */
  396. static time_t
  397. age_str2time (const char *age)
  398. {
  399. char *maxage;
  400. char unit;
  401. time_t ageval;
  402. if (age == NULL || age[0] == '\0' || strcmp (age, "0") == 0) {
  403. return 0;
  404. }
  405. maxage = slapi_ch_strdup ( age );
  406. if (!maxage) {
  407. slapi_log_error( SLAPI_LOG_PLUGIN, "retrocl",
  408. "age_str2time: Out of memory\n" );
  409. ageval = -1;
  410. goto done;
  411. }
  412. unit = maxage[ strlen( maxage ) - 1 ];
  413. maxage[ strlen( maxage ) - 1 ] = '\0';
  414. ageval = strntoul( maxage, strlen( maxage ), 10 );
  415. switch ( unit ) {
  416. case 's':
  417. break;
  418. case 'm':
  419. ageval *= 60;
  420. break;
  421. case 'h':
  422. ageval *= ( 60 * 60 );
  423. break;
  424. case 'd':
  425. ageval *= ( 24 * 60 * 60 );
  426. break;
  427. case 'w':
  428. ageval *= ( 7 * 24 * 60 * 60 );
  429. break;
  430. default:
  431. slapi_log_error( SLAPI_LOG_PLUGIN, "retrocl",
  432. "age_str2time: unknown unit \"%c\" "
  433. "for maxiumum changelog age\n", unit );
  434. ageval = -1;
  435. }
  436. done:
  437. if ( maxage) {
  438. slapi_ch_free ( (void **) &maxage );
  439. }
  440. return ageval;
  441. }
  442. /*
  443. * Function: retrocl_init_trimming
  444. *
  445. * Returns: none, exits on fatal error
  446. *
  447. * Arguments: none
  448. *
  449. * Description: called during startup
  450. *
  451. */
  452. void retrocl_init_trimming (void)
  453. {
  454. const char *cl_maxage;
  455. time_t ageval;
  456. const char *cl_trim_interval;
  457. cl_maxage = retrocl_get_config_str(CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE);
  458. if (cl_maxage == NULL) {
  459. LDAPDebug0Args(LDAP_DEBUG_TRACE,"No maxage, not trimming retro changelog.\n");
  460. return;
  461. }
  462. ageval = age_str2time (cl_maxage);
  463. slapi_ch_free_string((char **)&cl_maxage);
  464. cl_trim_interval = retrocl_get_config_str(CONFIG_CHANGELOG_TRIM_INTERVAL);
  465. if (cl_trim_interval) {
  466. trim_interval = strtol(cl_trim_interval, (char **)NULL, 10);
  467. if (0 == trim_interval) {
  468. slapi_log_error(SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME,
  469. "retrocl_init_trimming: ignoring invalid %s value %s; "
  470. "resetting the default %d\n",
  471. CONFIG_CHANGELOG_TRIM_INTERVAL, cl_trim_interval,
  472. DEFAULT_CHANGELOGDB_TRIM_INTERVAL);
  473. trim_interval = DEFAULT_CHANGELOGDB_TRIM_INTERVAL;
  474. }
  475. slapi_ch_free_string((char **)&cl_trim_interval);
  476. }
  477. ts.ts_c_max_age = ageval;
  478. ts.ts_s_last_trim = (time_t) 0L;
  479. ts.ts_s_trimming = 0;
  480. if (( ts.ts_s_trim_mutex = PR_NewLock()) == NULL ) {
  481. slapi_log_error( SLAPI_LOG_FATAL, RETROCL_PLUGIN_NAME, "set_changelog_trim_constraints: "
  482. "cannot create new lock.\n" );
  483. exit( 1 );
  484. }
  485. ts.ts_s_initialized = 1;
  486. retrocl_trimming = 1;
  487. retrocl_trim_ctx = slapi_eq_repeat(retrocl_housekeeping,
  488. NULL, (time_t)0,
  489. /* in milliseconds */
  490. trim_interval * 1000);
  491. }
  492. /*
  493. * Function: retrocl_stop_trimming
  494. *
  495. * Returns: none
  496. *
  497. * Arguments: none
  498. *
  499. * Description: called when server is shutting down to ensure trimming stops
  500. * eventually.
  501. *
  502. */
  503. void retrocl_stop_trimming(void)
  504. {
  505. retrocl_trimming = 0;
  506. if (retrocl_trim_ctx) {
  507. slapi_eq_cancel(retrocl_trim_ctx);
  508. retrocl_trim_ctx = NULL;
  509. }
  510. }