retrocl_trim.c 15 KB

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