retrocl_trim.c 14 KB

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