syncprov.c 100 KB
Newer Older
Howard Chu's avatar
Howard Chu committed
1
/* $OpenLDAP$ */
Howard Chu's avatar
Howard Chu committed
2
3
4
/* syncprov.c - syncrepl provider */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
Quanah Gibson-Mount's avatar
Quanah Gibson-Mount committed
5
 * Copyright 2004-2020 The OpenLDAP Foundation.
Howard Chu's avatar
Howard Chu committed
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in the file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * <http://www.OpenLDAP.org/license.html>.
 */
/* ACKNOWLEDGEMENTS:
 * This work was initially developed by Howard Chu for inclusion in
 * OpenLDAP Software.
 */

#include "portable.h"

#ifdef SLAPD_OVER_SYNCPROV

Howard Chu's avatar
Howard Chu committed
25
26
#include <ac/string.h>
#include "lutil.h"
Howard Chu's avatar
Howard Chu committed
27
#include "slap.h"
Howard Chu's avatar
Howard Chu committed
28
#include "config.h"
29
#include "ldap_rq.h"
Howard Chu's avatar
Howard Chu committed
30

31
#ifdef LDAP_DEVEL
32
#define	CHECK_CSN	1
33
#endif
34

35
36
37
38
39
40
41
42
43
/* A modify request on a particular entry */
typedef struct modinst {
	struct modinst *mi_next;
	Operation *mi_op;
} modinst;

typedef struct modtarget {
	struct modinst *mt_mods;
	struct modinst *mt_tail;
44
	struct berval mt_dn;
45
46
47
	ldap_pvt_thread_mutex_t mt_mutex;
} modtarget;

Howard Chu's avatar
Howard Chu committed
48
49
50
51
52
53
54
55
56
57
/* All the info of a psearch result that's shared between
 * multiple queues
 */
typedef struct resinfo {
	struct syncres *ri_list;
	Entry *ri_e;
	struct berval ri_dn;
	struct berval ri_ndn;
	struct berval ri_uuid;
	struct berval ri_csn;
Howard Chu's avatar
Howard Chu committed
58
	struct berval ri_cookie;
Howard Chu's avatar
Howard Chu committed
59
60
61
62
	char ri_isref;
	ldap_pvt_thread_mutex_t ri_mutex;
} resinfo;

Howard Chu's avatar
Howard Chu committed
63
64
/* A queued result of a persistent search */
typedef struct syncres {
Howard Chu's avatar
Howard Chu committed
65
66
67
	struct syncres *s_next;	/* list of results on this psearch queue */
	struct syncres *s_rilist;	/* list of psearches using this result */
	resinfo *s_info;
68
	char s_mode;
Howard Chu's avatar
Howard Chu committed
69
70
} syncres;

Howard Chu's avatar
Howard Chu committed
71
72
73
/* Record of a persistent search */
typedef struct syncops {
	struct syncops *s_next;
74
	struct syncprov_info_t *s_si;
Howard Chu's avatar
Howard Chu committed
75
76
77
	struct berval	s_base;		/* ndn of search base */
	ID		s_eid;		/* entryID of search base */
	Operation	*s_op;		/* search op */
78
	int		s_rid;
79
	int		s_sid;
Howard Chu's avatar
Howard Chu committed
80
	struct berval s_filterstr;
Howard Chu's avatar
Howard Chu committed
81
	int		s_flags;	/* search status */
Howard Chu's avatar
Howard Chu committed
82
83
84
85
#define	PS_IS_REFRESHING	0x01
#define	PS_IS_DETACHED		0x02
#define	PS_WROTE_BASE		0x04
#define	PS_FIND_BASE		0x08
86
#define	PS_FIX_FILTER		0x10
87
#define	PS_TASK_QUEUED		0x20
Howard Chu's avatar
Howard Chu committed
88

Howard Chu's avatar
Howard Chu committed
89
	int		s_inuse;	/* reference count */
Howard Chu's avatar
Howard Chu committed
90
91
	struct syncres *s_res;
	struct syncres *s_restail;
92
	void *s_pool_cookie;
Howard Chu's avatar
Howard Chu committed
93
	ldap_pvt_thread_mutex_t	s_mutex;
Howard Chu's avatar
Howard Chu committed
94
95
} syncops;

96
97
98
99
100
101
/* A received sync control */
typedef struct sync_control {
	struct sync_cookie sr_state;
	int sr_rhint;
} sync_control;

102
103
104
#if 0 /* moved back to slap.h */
#define	o_sync	o_ctrlflag[slap_cids.sc_LDAPsync]
#endif
105
/* o_sync_mode uses data bits of o_sync */
106
#define	o_sync_mode	o_ctrlflag[slap_cids.sc_LDAPsync]
107
108
109
110
111
112

#define SLAP_SYNC_NONE					(LDAP_SYNC_NONE<<SLAP_CONTROL_SHIFT)
#define SLAP_SYNC_REFRESH				(LDAP_SYNC_REFRESH_ONLY<<SLAP_CONTROL_SHIFT)
#define SLAP_SYNC_PERSIST				(LDAP_SYNC_RESERVED<<SLAP_CONTROL_SHIFT)
#define SLAP_SYNC_REFRESH_AND_PERSIST	(LDAP_SYNC_REFRESH_AND_PERSIST<<SLAP_CONTROL_SHIFT)

Howard Chu's avatar
Howard Chu committed
113
114
115
116
117
118
/* Record of which searches matched at premodify step */
typedef struct syncmatches {
	struct syncmatches *sm_next;
	syncops *sm_op;
} syncmatches;

119
120
121
122
123
/* Session log data */
typedef struct slog_entry {
	struct slog_entry *se_next;
	struct berval se_uuid;
	struct berval se_csn;
124
	int	se_sid;
125
126
127
128
	ber_tag_t	se_tag;
} slog_entry;

typedef struct sessionlog {
129
130
131
	BerVarray	sl_mincsn;
	int		*sl_sids;
	int		sl_numcsns;
132
133
	int		sl_num;
	int		sl_size;
134
	int		sl_playing;
135
136
137
138
139
	slog_entry *sl_head;
	slog_entry *sl_tail;
	ldap_pvt_thread_mutex_t sl_mutex;
} sessionlog;

Howard Chu's avatar
Cleanup    
Howard Chu committed
140
/* The main state for this overlay */
Howard Chu's avatar
Howard Chu committed
141
142
typedef struct syncprov_info_t {
	syncops		*si_ops;
143
	struct berval	si_contextdn;
144
	BerVarray	si_ctxcsn;	/* ldapsync context */
145
146
	int		*si_sids;
	int		si_numcsns;
147
148
149
	int		si_chkops;	/* checkpointing info */
	int		si_chktime;
	int		si_numops;	/* number of ops since last checkpoint */
150
	int		si_nopres;	/* Skip present phase */
151
	int		si_usehint;	/* use reload hint */
152
	int		si_active;	/* True if there are active mods */
153
154
	int		si_dirty;	/* True if the context is dirty, i.e changes
						 * have been made without updating the csn. */
155
	time_t	si_chklast;	/* time of last checkpoint */
156
	Avlnode	*si_mods;	/* entries being modified */
157
	sessionlog	*si_logs;
Howard Chu's avatar
Howard Chu committed
158
	ldap_pvt_thread_rdwr_t	si_csn_rwlock;
Howard Chu's avatar
Howard Chu committed
159
	ldap_pvt_thread_mutex_t	si_ops_mutex;
Howard Chu's avatar
Howard Chu committed
160
	ldap_pvt_thread_mutex_t	si_mods_mutex;
Howard Chu's avatar
Howard Chu committed
161
	ldap_pvt_thread_mutex_t	si_resp_mutex;
Howard Chu's avatar
Howard Chu committed
162
163
164
165
166
} syncprov_info_t;

typedef struct opcookie {
	slap_overinst *son;
	syncmatches *smatches;
167
	modtarget *smt;
168
	Entry *se;
169
170
171
172
	struct berval sdn;	/* DN of entry, for deletes */
	struct berval sndn;
	struct berval suuid;	/* UUID of entry */
	struct berval sctxcsn;
Howard Chu's avatar
Howard Chu committed
173
174
	short osid;	/* sid of op csn */
	short rsid;	/* sid of relay */
175
	short sreference;	/* Is the entry a reference? */
Howard Chu's avatar
Howard Chu committed
176
	syncres ssres;
Howard Chu's avatar
Howard Chu committed
177
178
} opcookie;

179
typedef struct fbase_cookie {
180
181
182
183
	struct berval *fdn;	/* DN of a modified entry, for scope testing */
	syncops *fss;	/* persistent search we're testing against */
	int fbase;	/* if TRUE we found the search base and it's still valid */
	int fscope;	/* if TRUE then fdn is within the psearch scope */
184
} fbase_cookie;
Howard Chu's avatar
Howard Chu committed
185

186
static AttributeName csn_anlist[3];
Howard Chu's avatar
Howard Chu committed
187
188
static AttributeName uuid_anlist[2];

Howard Chu's avatar
Cleanup    
Howard Chu committed
189
/* Build a LDAPsync intermediate state control */
190
191
192
193
194
static int
syncprov_state_ctrl(
	Operation	*op,
	SlapReply	*rs,
	Entry		*e,
195
	int		entry_sync_state,
196
	LDAPControl	**ctrls,
197
198
199
	int		num_ctrls,
	int		send_cookie,
	struct berval	*cookie )
200
201
202
203
204
205
{
	Attribute* a;
	int ret;

	BerElementBuffer berbuf;
	BerElement *ber = (BerElement *)&berbuf;
206
207
	LDAPControl *cp;
	struct berval bv;
208
	struct berval	entryuuid_bv = BER_BVNULL;
209
210
211
212

	ber_init2( ber, 0, LBER_USE_DER );
	ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );

213
214
215
216
217
	for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
		AttributeDescription *desc = a->a_desc;
		if ( desc == slap_schema.si_ad_entryUUID ) {
			entryuuid_bv = a->a_nvals[0];
			break;
218
		}
219
	}
220

221
222
	/* FIXME: what if entryuuid is NULL or empty ? */

223
224
225
226
227
228
	if ( send_cookie && cookie ) {
		ber_printf( ber, "{eOON}",
			entry_sync_state, &entryuuid_bv, cookie );
	} else {
		ber_printf( ber, "{eON}",
			entry_sync_state, &entryuuid_bv );
229
230
	}

231
232
233
234
235
236
237
238
239
240
	ret = ber_flatten2( ber, &bv, 0 );
	if ( ret == 0 ) {
		cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx );
		cp->ldctl_oid = LDAP_CONTROL_SYNC_STATE;
		cp->ldctl_iscritical = (op->o_sync == SLAP_CONTROL_CRITICAL);
		cp->ldctl_value.bv_val = (char *)&cp[1];
		cp->ldctl_value.bv_len = bv.bv_len;
		AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
		ctrls[num_ctrls] = cp;
	}
241
242
243
244
	ber_free_buf( ber );

	if ( ret < 0 ) {
		Debug( LDAP_DEBUG_TRACE,
Howard Chu's avatar
Howard Chu committed
245
			"slap_build_sync_ctrl: ber_flatten2 failed (%d)\n",
246
			ret );
247
		send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
Howard Chu's avatar
Howard Chu committed
248
		return LDAP_OTHER;
249
250
251
252
253
	}

	return LDAP_SUCCESS;
}

Howard Chu's avatar
Cleanup    
Howard Chu committed
254
/* Build a LDAPsync final state control */
255
256
257
258
259
260
261
262
263
264
265
266
267
static int
syncprov_done_ctrl(
	Operation	*op,
	SlapReply	*rs,
	LDAPControl	**ctrls,
	int			num_ctrls,
	int			send_cookie,
	struct berval *cookie,
	int			refreshDeletes )
{
	int ret;
	BerElementBuffer berbuf;
	BerElement *ber = (BerElement *)&berbuf;
268
269
	LDAPControl *cp;
	struct berval bv;
270
271
272
273
274
275
276
277
278
279
280

	ber_init2( ber, NULL, LBER_USE_DER );
	ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );

	ber_printf( ber, "{" );
	if ( send_cookie && cookie ) {
		ber_printf( ber, "O", cookie );
	}
	if ( refreshDeletes == LDAP_SYNC_REFRESH_DELETES ) {
		ber_printf( ber, "b", refreshDeletes );
	}
281
	ber_printf( ber, "N}" );
282

283
284
285
286
287
288
289
290
291
292
	ret = ber_flatten2( ber, &bv, 0 );
	if ( ret == 0 ) {
		cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx );
		cp->ldctl_oid = LDAP_CONTROL_SYNC_DONE;
		cp->ldctl_iscritical = (op->o_sync == SLAP_CONTROL_CRITICAL);
		cp->ldctl_value.bv_val = (char *)&cp[1];
		cp->ldctl_value.bv_len = bv.bv_len;
		AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
		ctrls[num_ctrls] = cp;
	}
293
294
295
296
297

	ber_free_buf( ber );

	if ( ret < 0 ) {
		Debug( LDAP_DEBUG_TRACE,
Howard Chu's avatar
Howard Chu committed
298
			"syncprov_done_ctrl: ber_flatten2 failed (%d)\n",
299
			ret );
300
		send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
Howard Chu's avatar
Howard Chu committed
301
		return LDAP_OTHER;
302
303
304
305
306
	}

	return LDAP_SUCCESS;
}

Howard Chu's avatar
Howard Chu committed
307
static int
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
syncprov_sendinfo(
	Operation	*op,
	SlapReply	*rs,
	int			type,
	struct berval *cookie,
	int			refreshDone,
	BerVarray	syncUUIDs,
	int			refreshDeletes )
{
	BerElementBuffer berbuf;
	BerElement *ber = (BerElement *)&berbuf;
	struct berval rspdata;

	int ret;

	ber_init2( ber, NULL, LBER_USE_DER );
	ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );

	if ( type ) {
		switch ( type ) {
		case LDAP_TAG_SYNC_NEW_COOKIE:
329
330
331
			Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendinfo: "
				"sending a new cookie=%s\n",
				op->o_log_prefix, cookie->bv_val );
332
333
334
335
			ber_printf( ber, "tO", type, cookie );
			break;
		case LDAP_TAG_SYNC_REFRESH_DELETE:
		case LDAP_TAG_SYNC_REFRESH_PRESENT:
336
337
338
339
340
			Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendinfo: "
				"%s cookie=%s\n",
				op->o_log_prefix,
				type == LDAP_TAG_SYNC_REFRESH_DELETE ? "refreshDelete" : "refreshPresent",
				cookie ? cookie->bv_val : "" );
341
342
343
344
345
346
347
348
349
350
			ber_printf( ber, "t{", type );
			if ( cookie ) {
				ber_printf( ber, "O", cookie );
			}
			if ( refreshDone == 0 ) {
				ber_printf( ber, "b", refreshDone );
			}
			ber_printf( ber, "N}" );
			break;
		case LDAP_TAG_SYNC_ID_SET:
351
352
353
354
			Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendinfo: "
				"%s syncIdSet cookie=%s\n",
				op->o_log_prefix, refreshDeletes ? "delete" : "present",
				cookie ? cookie->bv_val : "" );
355
356
357
358
359
360
361
362
363
364
365
366
			ber_printf( ber, "t{", type );
			if ( cookie ) {
				ber_printf( ber, "O", cookie );
			}
			if ( refreshDeletes == 1 ) {
				ber_printf( ber, "b", refreshDeletes );
			}
			ber_printf( ber, "[W]", syncUUIDs );
			ber_printf( ber, "N}" );
			break;
		default:
			Debug( LDAP_DEBUG_TRACE,
367
368
				"%s syncprov_sendinfo: invalid syncinfo type (%d)\n",
				op->o_log_prefix, type );
369
370
371
372
373
374
375
376
			return LDAP_OTHER;
		}
	}

	ret = ber_flatten2( ber, &rspdata, 0 );

	if ( ret < 0 ) {
		Debug( LDAP_DEBUG_TRACE,
Howard Chu's avatar
Howard Chu committed
377
			"syncprov_sendinfo: ber_flatten2 failed (%d)\n",
378
			ret );
379
		send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
Howard Chu's avatar
Howard Chu committed
380
		return LDAP_OTHER;
381
382
	}

Howard Chu's avatar
Cleanup    
Howard Chu committed
383
	rs->sr_rspoid = LDAP_SYNC_INFO;
384
385
386
387
388
389
390
	rs->sr_rspdata = &rspdata;
	send_ldap_intermediate( op, rs );
	rs->sr_rspdata = NULL;
	ber_free_buf( ber );

	return LDAP_SUCCESS;
}
391

Howard Chu's avatar
Cleanup    
Howard Chu committed
392
/* Find a modtarget in an AVL tree */
393
394
395
396
397
398
399
static int
sp_avl_cmp( const void *c1, const void *c2 )
{
	const modtarget *m1, *m2;
	int rc;

	m1 = c1; m2 = c2;
400
	rc = m1->mt_dn.bv_len - m2->mt_dn.bv_len;
401
402

	if ( rc ) return rc;
403
	return ber_bvcmp( &m1->mt_dn, &m2->mt_dn );
404
405
}

406
407
408
409
410
411
412
413
414
/* syncprov_findbase:
 *   finds the true DN of the base of a search (with alias dereferencing) and
 * checks to make sure the base entry doesn't get replaced with a different
 * entry (e.g., swapping trees via ModDN, or retargeting an alias). If a
 * change is detected, any persistent search on this base must be terminated /
 * reloaded.
 *   On the first call, we just save the DN and entryID. On subsequent calls
 * we compare the DN and entryID with the saved values.
 */
Howard Chu's avatar
Howard Chu committed
415
416
417
418
419
420
static int
findbase_cb( Operation *op, SlapReply *rs )
{
	slap_callback *sc = op->o_callback;

	if ( rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS ) {
421
422
423
424
425
426
		fbase_cookie *fc = sc->sc_private;

		/* If no entryID, we're looking for the first time.
		 * Just store whatever we got.
		 */
		if ( fc->fss->s_eid == NOID ) {
Howard Chu's avatar
Howard Chu committed
427
			fc->fbase = 2;
428
429
			fc->fss->s_eid = rs->sr_entry->e_id;
			ber_dupbv( &fc->fss->s_base, &rs->sr_entry->e_nname );
430

431
		} else if ( rs->sr_entry->e_id == fc->fss->s_eid &&
Howard Chu's avatar
Howard Chu committed
432
			dn_match( &rs->sr_entry->e_nname, &fc->fss->s_base )) {
433

Howard Chu's avatar
Howard Chu committed
434
		/* OK, the DN is the same and the entryID is the same. */
Howard Chu's avatar
Howard Chu committed
435
436
			fc->fbase = 1;
		}
Howard Chu's avatar
Howard Chu committed
437
	}
438
	if ( rs->sr_err != LDAP_SUCCESS ) {
439
		Debug( LDAP_DEBUG_ANY, "findbase failed! %d\n", rs->sr_err );
440
	}
Howard Chu's avatar
Howard Chu committed
441
442
443
	return LDAP_SUCCESS;
}

Howard Chu's avatar
Howard Chu committed
444
445
446
static Filter generic_filter = { LDAP_FILTER_PRESENT, { 0 }, NULL };
static struct berval generic_filterstr = BER_BVC("(objectclass=*)");

Howard Chu's avatar
Howard Chu committed
447
static int
448
syncprov_findbase( Operation *op, fbase_cookie *fc )
Howard Chu's avatar
Howard Chu committed
449
{
Howard Chu's avatar
Howard Chu committed
450
451
452
	/* Use basic parameters from syncrepl search, but use
	 * current op's threadctx / tmpmemctx
	 */
Howard Chu's avatar
Howard Chu committed
453
454
455
456
457
458
	ldap_pvt_thread_mutex_lock( &fc->fss->s_mutex );
	if ( fc->fss->s_flags & PS_FIND_BASE ) {
		slap_callback cb = {0};
		Operation fop;
		SlapReply frs = { REP_RESULT };
		int rc;
Howard Chu's avatar
Howard Chu committed
459

Howard Chu's avatar
Howard Chu committed
460
461
		fc->fss->s_flags ^= PS_FIND_BASE;
		ldap_pvt_thread_mutex_unlock( &fc->fss->s_mutex );
Howard Chu's avatar
Howard Chu committed
462

Howard Chu's avatar
Howard Chu committed
463
		fop = *fc->fss->s_op;
Howard Chu's avatar
Howard Chu committed
464

465
		fop.o_bd = fop.o_bd->bd_self;
Howard Chu's avatar
Howard Chu committed
466
467
468
		fop.o_hdr = op->o_hdr;
		fop.o_time = op->o_time;
		fop.o_tincr = op->o_tincr;
469
		fop.o_extra = op->o_extra;
Howard Chu's avatar
Howard Chu committed
470

Howard Chu's avatar
Howard Chu committed
471
472
		cb.sc_response = findbase_cb;
		cb.sc_private = fc;
Howard Chu's avatar
Howard Chu committed
473

Howard Chu's avatar
Howard Chu committed
474
475
476
477
478
479
480
481
482
483
		fop.o_sync_mode = 0;	/* turn off sync mode */
		fop.o_managedsait = SLAP_CONTROL_CRITICAL;
		fop.o_callback = &cb;
		fop.o_tag = LDAP_REQ_SEARCH;
		fop.ors_scope = LDAP_SCOPE_BASE;
		fop.ors_limit = NULL;
		fop.ors_slimit = 1;
		fop.ors_tlimit = SLAP_NO_LIMIT;
		fop.ors_attrs = slap_anlist_no_attrs;
		fop.ors_attrsonly = 1;
Howard Chu's avatar
Howard Chu committed
484
485
		fop.ors_filter = &generic_filter;
		fop.ors_filterstr = generic_filterstr;
Howard Chu's avatar
Howard Chu committed
486

487
		Debug( LDAP_DEBUG_SYNC, "%s syncprov_findbase: searching\n", op->o_log_prefix );
488
		rc = fop.o_bd->be_search( &fop, &frs );
Howard Chu's avatar
Howard Chu committed
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
	} else {
		ldap_pvt_thread_mutex_unlock( &fc->fss->s_mutex );
		fc->fbase = 1;
	}

	/* After the first call, see if the fdn resides in the scope */
	if ( fc->fbase == 1 ) {
		switch ( fc->fss->s_op->ors_scope ) {
		case LDAP_SCOPE_BASE:
			fc->fscope = dn_match( fc->fdn, &fc->fss->s_base );
			break;
		case LDAP_SCOPE_ONELEVEL: {
			struct berval pdn;
			dnParent( fc->fdn, &pdn );
			fc->fscope = dn_match( &pdn, &fc->fss->s_base );
			break; }
		case LDAP_SCOPE_SUBTREE:
			fc->fscope = dnIsSuffix( fc->fdn, &fc->fss->s_base );
			break;
		case LDAP_SCOPE_SUBORDINATE:
			fc->fscope = dnIsSuffix( fc->fdn, &fc->fss->s_base ) &&
				!dn_match( fc->fdn, &fc->fss->s_base );
			break;
		}
	}

	if ( fc->fbase )
		return LDAP_SUCCESS;
Howard Chu's avatar
Howard Chu committed
517
518
519
520
521
522

	/* If entryID has changed, then the base of this search has
	 * changed. Invalidate the psearch.
	 */
	return LDAP_NO_SUCH_OBJECT;
}
Howard Chu's avatar
Howard Chu committed
523

524
/* syncprov_findcsn:
525
 *   This function has three different purposes, but they all use a search
526
 * that filters on entryCSN so they're combined here.
527
528
529
530
531
 * 1: at startup time, after a contextCSN has been read from the database,
 * we search for all entries with CSN >= contextCSN in case the contextCSN
 * was not checkpointed at the previous shutdown.
 *
 * 2: when the current contextCSN is known and we have a sync cookie, we search
Howard Chu's avatar
Howard Chu committed
532
533
 * for one entry with CSN = the cookie CSN. If not found, try <= cookie CSN.
 * If an entry is found, the cookie CSN is valid, otherwise it is stale.
534
 *
535
 * 3: during a refresh phase, we search for all entries with CSN <= the cookie
536
537
538
 * CSN, and generate Present records for them. We always collect this result
 * in SyncID sets, even if there's only one match.
 */
539
540
541
542
543
typedef enum find_csn_t {
	FIND_MAXCSN	= 1,
	FIND_CSN	= 2,
	FIND_PRESENT	= 3
} find_csn_t;
544
545
546
547
548
549
550
551
552

static int
findmax_cb( Operation *op, SlapReply *rs )
{
	if ( rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS ) {
		struct berval *maxcsn = op->o_callback->sc_private;
		Attribute *a = attr_find( rs->sr_entry->e_attrs,
			slap_schema.si_ad_entryCSN );

553
554
		if ( a && ber_bvcmp( &a->a_vals[0], maxcsn ) > 0 &&
			slap_parse_csn_sid( &a->a_vals[0] ) == slap_serverID ) {
555
556
557
558
559
560
			maxcsn->bv_len = a->a_vals[0].bv_len;
			strcpy( maxcsn->bv_val, a->a_vals[0].bv_val );
		}
	}
	return LDAP_SUCCESS;
}
Howard Chu's avatar
Howard Chu committed
561
562
563
564
565
566

static int
findcsn_cb( Operation *op, SlapReply *rs )
{
	slap_callback *sc = op->o_callback;

Howard Chu's avatar
Howard Chu committed
567
568
569
570
571
	/* We just want to know that at least one exists, so it's OK if
	 * we exceed the unchecked limit.
	 */
	if ( rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED ||
		(rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS )) {
572
		sc->sc_private = (void *)1;
Howard Chu's avatar
Howard Chu committed
573
574
575
576
	}
	return LDAP_SUCCESS;
}

577
578
/* Build a list of entryUUIDs for sending in a SyncID set */

579
580
#define UUID_LEN	16

Howard Chu's avatar
Howard Chu committed
581
582
583
typedef struct fpres_cookie {
	int num;
	BerVarray uuids;
584
	char *last;
Howard Chu's avatar
Howard Chu committed
585
586
587
588
589
590
591
} fpres_cookie;

static int
findpres_cb( Operation *op, SlapReply *rs )
{
	slap_callback *sc = op->o_callback;
	fpres_cookie *pc = sc->sc_private;
592
	Attribute *a;
Howard Chu's avatar
Howard Chu committed
593
	int ret = SLAP_CB_CONTINUE;
Howard Chu's avatar
Howard Chu committed
594

595
596
597
	switch ( rs->sr_type ) {
	case REP_SEARCH:
		a = attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryUUID );
598
599
600
601
		if ( a ) {
			pc->uuids[pc->num].bv_val = pc->last;
			AC_MEMCPY( pc->uuids[pc->num].bv_val, a->a_nvals[0].bv_val,
				pc->uuids[pc->num].bv_len );
Howard Chu's avatar
Howard Chu committed
602
			pc->num++;
603
604
			pc->last = pc->uuids[pc->num].bv_val;
			pc->uuids[pc->num].bv_val = NULL;
Howard Chu's avatar
Howard Chu committed
605
		}
606
		ret = LDAP_SUCCESS;
607
608
609
610
		if ( pc->num != SLAP_SYNCUUID_SET_SIZE )
			break;
		/* FALLTHRU */
	case REP_RESULT:
Howard Chu's avatar
Howard Chu committed
611
612
		ret = rs->sr_err;
		if ( pc->num ) {
613
			ret = syncprov_sendinfo( op, rs, LDAP_TAG_SYNC_ID_SET, NULL,
Howard Chu's avatar
Howard Chu committed
614
				0, pc->uuids, 0 );
615
			pc->uuids[pc->num].bv_val = pc->last;
Howard Chu's avatar
Howard Chu committed
616
			pc->num = 0;
617
			pc->last = pc->uuids[0].bv_val;
Howard Chu's avatar
Howard Chu committed
618
		}
619
620
621
		break;
	default:
		break;
Howard Chu's avatar
Howard Chu committed
622
623
624
625
626
	}
	return ret;
}

static int
cmikk@qwest.net's avatar
cmikk@qwest.net committed
627
syncprov_findcsn( Operation *op, find_csn_t mode, struct berval *csn )
Howard Chu's avatar
Howard Chu committed
628
629
630
631
632
633
634
{
	slap_overinst		*on = (slap_overinst *)op->o_bd->bd_info;
	syncprov_info_t		*si = on->on_bi.bi_private;

	slap_callback cb = {0};
	Operation fop;
	SlapReply frs = { REP_RESULT };
635
636
	char buf[LDAP_PVT_CSNSTR_BUFSIZE + STRLENOF("(entryCSN<=)")];
	char cbuf[LDAP_PVT_CSNSTR_BUFSIZE];
637
	struct berval maxcsn;
638
	Filter cf;
639
	AttributeAssertion eq = ATTRIBUTEASSERTION_INIT;
Howard Chu's avatar
Howard Chu committed
640
	fpres_cookie pcookie;
Pierangelo Masarati's avatar
Pierangelo Masarati committed
641
	sync_control *srs = NULL;
642
643
	struct slap_limits_set fc_limits;
	int i, rc = LDAP_SUCCESS, findcsn_retry = 1;
644
	int maxid;
645
646
647

	if ( mode != FIND_MAXCSN ) {
		srs = op->o_controls[slap_cids.sc_LDAPsync];
Howard Chu's avatar
Howard Chu committed
648
649
	}

650
651
652
653
654
655
656
657
658
	Debug( LDAP_DEBUG_SYNC, "%s syncprov_findcsn: mode=%s csn=%s\n",
		op->o_log_prefix,
		mode == FIND_MAXCSN ?
			"FIND_MAXCSN" :
			mode == FIND_CSN ?
				"FIND_CSN" :
				"FIND_PRESENT",
		csn ? csn->bv_val : "" );

Howard Chu's avatar
Howard Chu committed
659
	fop = *op;
660
	fop.o_sync_mode &= SLAP_CONTROL_MASK;	/* turn off sync_mode */
661
662
	/* We want pure entries, not referrals */
	fop.o_managedsait = SLAP_CONTROL_CRITICAL;
Howard Chu's avatar
Howard Chu committed
663

664
665
	cf.f_ava = &eq;
	cf.f_av_desc = slap_schema.si_ad_entryCSN;
666
	BER_BVZERO( &cf.f_av_value );
667
668
	cf.f_next = NULL;

669
670
671
672
	fop.o_callback = &cb;
	fop.ors_limit = NULL;
	fop.ors_tlimit = SLAP_NO_LIMIT;
	fop.ors_filter = &cf;
673
	fop.ors_filterstr.bv_val = buf;
674

Howard Chu's avatar
Howard Chu committed
675
again:
676
677
678
	switch( mode ) {
	case FIND_MAXCSN:
		cf.f_choice = LDAP_FILTER_GE;
679
680
681
		/* If there are multiple CSNs, use the one with our serverID */
		for ( i=0; i<si->si_numcsns; i++) {
			if ( slap_serverID == si->si_sids[i] ) {
682
				maxid = i;
683
				break;
684
685
			}
		}
686
687
688
689
690
691
692
		if ( i == si->si_numcsns ) {
			/* No match: this is multimaster, and none of the content in the DB
			 * originated locally. Treat like no CSN.
			 */
			return LDAP_NO_SUCH_OBJECT;
		}
		cf.f_av_value = si->si_ctxcsn[maxid];
693
694
		fop.ors_filterstr.bv_len = snprintf( buf, sizeof( buf ),
			"(entryCSN>=%s)", cf.f_av_value.bv_val );
695
		if ( fop.ors_filterstr.bv_len >= sizeof( buf ) ) {
696
697
			return LDAP_OTHER;
		}
698
699
700
701
702
		fop.ors_attrsonly = 0;
		fop.ors_attrs = csn_anlist;
		fop.ors_slimit = SLAP_NO_LIMIT;
		cb.sc_private = &maxcsn;
		cb.sc_response = findmax_cb;
703
		strcpy( cbuf, cf.f_av_value.bv_val );
704
		maxcsn.bv_val = cbuf;
705
		maxcsn.bv_len = cf.f_av_value.bv_len;
706
707
		break;
	case FIND_CSN:
708
		if ( BER_BVISEMPTY( &cf.f_av_value )) {
cmikk@qwest.net's avatar
cmikk@qwest.net committed
709
			cf.f_av_value = *csn;
710
		}
cmikk@qwest.net's avatar
cmikk@qwest.net committed
711
712
713
714
		fop.o_dn = op->o_bd->be_rootdn;
		fop.o_ndn = op->o_bd->be_rootndn;
		fop.o_req_dn = op->o_bd->be_suffix[0];
		fop.o_req_ndn = op->o_bd->be_nsuffix[0];
Howard Chu's avatar
Howard Chu committed
715
716
717
		/* Look for exact match the first time */
		if ( findcsn_retry ) {
			cf.f_choice = LDAP_FILTER_EQUALITY;
718
719
			fop.ors_filterstr.bv_len = snprintf( buf, sizeof( buf ),
				"(entryCSN=%s)", cf.f_av_value.bv_val );
Howard Chu's avatar
Howard Chu committed
720
721
722
		/* On retry, look for <= */
		} else {
			cf.f_choice = LDAP_FILTER_LE;
723
			fop.ors_limit = &fc_limits;
724
			memset( &fc_limits, 0, sizeof( fc_limits ));
725
			fc_limits.lms_s_unchecked = 1;
726
727
728
			fop.ors_filterstr.bv_len = snprintf( buf, sizeof( buf ),
				"(entryCSN<=%s)", cf.f_av_value.bv_val );
		}
729
		if ( fop.ors_filterstr.bv_len >= sizeof( buf ) ) {
730
			return LDAP_OTHER;
Howard Chu's avatar
Howard Chu committed
731
		}
732
733
734
735
		fop.ors_attrsonly = 1;
		fop.ors_attrs = slap_anlist_no_attrs;
		fop.ors_slimit = 1;
		cb.sc_private = NULL;
Howard Chu's avatar
Howard Chu committed
736
		cb.sc_response = findcsn_cb;
737
738
		break;
	case FIND_PRESENT:
739
740
		fop.ors_filter = op->ors_filter;
		fop.ors_filterstr = op->ors_filterstr;
Howard Chu's avatar
Howard Chu committed
741
742
743
		fop.ors_attrsonly = 0;
		fop.ors_attrs = uuid_anlist;
		fop.ors_slimit = SLAP_NO_LIMIT;
Howard Chu's avatar
Howard Chu committed
744
		cb.sc_private = &pcookie;
Howard Chu's avatar
Howard Chu committed
745
746
		cb.sc_response = findpres_cb;
		pcookie.num = 0;
747
748
749
750
751
752
753
754
755
756
757
758

		/* preallocate storage for a full set */
		pcookie.uuids = op->o_tmpalloc( (SLAP_SYNCUUID_SET_SIZE+1) *
			sizeof(struct berval) + SLAP_SYNCUUID_SET_SIZE * UUID_LEN,
			op->o_tmpmemctx );
		pcookie.last = (char *)(pcookie.uuids + SLAP_SYNCUUID_SET_SIZE+1);
		pcookie.uuids[0].bv_val = pcookie.last;
		pcookie.uuids[0].bv_len = UUID_LEN;
		for (i=1; i<SLAP_SYNCUUID_SET_SIZE; i++) {
			pcookie.uuids[i].bv_val = pcookie.uuids[i-1].bv_val + UUID_LEN;
			pcookie.uuids[i].bv_len = UUID_LEN;
		}
759
		break;
Howard Chu's avatar
Howard Chu committed
760
761
	}

Howard Chu's avatar
Howard Chu committed
762
	fop.o_bd->bd_info = (BackendInfo *)on->on_info;
763
	fop.o_bd->be_search( &fop, &frs );
764
	fop.o_bd->bd_info = (BackendInfo *)on;
Howard Chu's avatar
Howard Chu committed
765

766
767
	switch( mode ) {
	case FIND_MAXCSN:
768
		if ( ber_bvcmp( &si->si_ctxcsn[maxid], &maxcsn )) {
Howard Chu's avatar
Howard Chu committed
769
770
771
772
#ifdef CHECK_CSN
			Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax;
			assert( !syn->ssyn_validate( syn, &maxcsn ));
#endif
773
774
775
			ber_bvreplace( &si->si_ctxcsn[maxid], &maxcsn );
			si->si_numops++;	/* ensure a checkpoint */
		}
776
777
778
		break;
	case FIND_CSN:
		/* If matching CSN was not found, invalidate the context. */
779
780
781
782
		Debug( LDAP_DEBUG_SYNC, "%s syncprov_findcsn: csn%s=%s %sfound\n",
			op->o_log_prefix,
			cf.f_choice == LDAP_FILTER_EQUALITY ? "=" : "<",
			cf.f_av_value.bv_val, cb.sc_private ? "" : "not " );
Howard Chu's avatar
Howard Chu committed
783
784
785
786
		if ( !cb.sc_private ) {
			/* If we didn't find an exact match, then try for <= */
			if ( findcsn_retry ) {
				findcsn_retry = 0;
787
				rs_reinit( &frs, REP_RESULT );
Howard Chu's avatar
Howard Chu committed
788
789
790
791
				goto again;
			}
			rc = LDAP_NO_SUCH_OBJECT;
		}
792
793
		break;
	case FIND_PRESENT:
794
		op->o_tmpfree( pcookie.uuids, op->o_tmpmemctx );
795
		break;
Howard Chu's avatar
Howard Chu committed
796
797
	}

798
	return rc;
Howard Chu's avatar
Howard Chu committed
799
800
}

Howard Chu's avatar
Howard Chu committed
801
static void free_resinfo( syncres *sr )
802
{
Howard Chu's avatar
Howard Chu committed
803
	syncres **st;
Howard Chu's avatar
Howard Chu committed
804
	int freeit = 0;
Howard Chu's avatar
Howard Chu committed
805
806
807
808
809
810
811
	ldap_pvt_thread_mutex_lock( &sr->s_info->ri_mutex );
	for (st = &sr->s_info->ri_list; *st; st = &(*st)->s_rilist) {
		if (*st == sr) {
			*st = sr->s_rilist;
			break;
		}
	}
Howard Chu's avatar
Howard Chu committed
812
813
	if ( !sr->s_info->ri_list )
		freeit = 1;
Howard Chu's avatar
Howard Chu committed
814
	ldap_pvt_thread_mutex_unlock( &sr->s_info->ri_mutex );
Howard Chu's avatar
Howard Chu committed
815
	if ( freeit ) {
Howard Chu's avatar
Howard Chu committed
816
817
818
		ldap_pvt_thread_mutex_destroy( &sr->s_info->ri_mutex );
		if ( sr->s_info->ri_e )
			entry_free( sr->s_info->ri_e );
Howard Chu's avatar
Howard Chu committed
819
820
		if ( !BER_BVISNULL( &sr->s_info->ri_cookie ))
			ch_free( sr->s_info->ri_cookie.bv_val );
Howard Chu's avatar
Howard Chu committed
821
		ch_free( sr->s_info );
822
823
824
	}
}

825
826
827
#define FS_UNLINK	1
#define FS_LOCK		2

828
static int
829
syncprov_free_syncop( syncops *so, int flags )
830
831
832
833
{
	syncres *sr, *srnext;
	GroupAssertion *ga, *gnext;

834
835
	if ( flags & FS_LOCK )
		ldap_pvt_thread_mutex_lock( &so->s_mutex );
836
837
	/* already being freed, or still in use */
	if ( !so->s_inuse || --so->s_inuse > 0 ) {
838
839
		if ( flags & FS_LOCK )
			ldap_pvt_thread_mutex_unlock( &so->s_mutex );
840
		return 0;
841
842
	}
	ldap_pvt_thread_mutex_unlock( &so->s_mutex );
843
	if (( flags & FS_UNLINK ) && so->s_si ) {
844
845
846
847
848
849
850
851
852
853
		syncops **sop;
		ldap_pvt_thread_mutex_lock( &so->s_si->si_ops_mutex );
		for ( sop = &so->s_si->si_ops; *sop; sop = &(*sop)->s_next ) {
			if ( *sop == so ) {
				*sop = so->s_next;
				break;
			}
		}
		ldap_pvt_thread_mutex_unlock( &so->s_si->si_ops_mutex );
	}
854
855
856
857
858
859
860
861
862
863
864
	if ( so->s_flags & PS_IS_DETACHED ) {
		filter_free( so->s_op->ors_filter );
		for ( ga = so->s_op->o_groups; ga; ga=gnext ) {
			gnext = ga->ga_next;
			ch_free( ga );
		}
		ch_free( so->s_op );
	}
	ch_free( so->s_base.bv_val );
	for ( sr=so->s_res; sr; sr=srnext ) {
		srnext = sr->s_next;
Howard Chu's avatar
Howard Chu committed
865
		free_resinfo( sr );
866
867
868
869
		ch_free( sr );
	}
	ldap_pvt_thread_mutex_destroy( &so->s_mutex );
	ch_free( so );
870
	return 1;
871
872
}

873
874
/* Send a persistent search response */
static int
Howard Chu's avatar
Howard Chu committed
875
syncprov_sendresp( Operation *op, resinfo *ri, syncops *so, int mode )
876
877
{
	SlapReply rs = { REP_SEARCH };
878
	struct berval cookie, csns[2];
879
880
881
	Entry e_uuid = {0};
	Attribute a_uuid = {0};

Howard Chu's avatar
Howard Chu committed
882
883
884
	if ( so->s_op->o_abandon )
		return SLAPD_ABANDON;

885
886
887
	rs.sr_ctrls = op->o_tmpalloc( sizeof(LDAPControl *)*2, op->o_tmpmemctx );
	rs.sr_ctrls[1] = NULL;
	rs.sr_flags = REP_CTRLS_MUSTBEFREED;
Howard Chu's avatar
Howard Chu committed
888
	csns[0] = ri->ri_csn;
889
	BER_BVZERO( &csns[1] );
890
891
	slap_compose_sync_cookie( op, &cookie, csns, so->s_rid,
				 slap_serverID ? slap_serverID : -1, NULL );
892

893
#ifdef LDAP_DEBUG
894
	if ( so->s_sid > 0 ) {
895
896
		Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: to=%03x, cookie=%s\n",
			op->o_log_prefix, so->s_sid, cookie.bv_val );
897
	} else {
898
899
		Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: cookie=%s\n",
			op->o_log_prefix, cookie.bv_val );
900
	}
901
#endif
902

903
904
	e_uuid.e_attrs = &a_uuid;
	a_uuid.a_desc = slap_schema.si_ad_entryUUID;
Howard Chu's avatar
Howard Chu committed
905
	a_uuid.a_nvals = &ri->ri_uuid;
906
	rs.sr_err = syncprov_state_ctrl( op, &rs, &e_uuid,
907
		mode, rs.sr_ctrls, 0, 1, &cookie );
908
	op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx );
909

910
911
	rs.sr_entry = &e_uuid;
	if ( mode == LDAP_SYNC_ADD || mode == LDAP_SYNC_MODIFY ) {
Howard Chu's avatar
Howard Chu committed
912
		e_uuid = *ri->ri_e;
913
914
915
		e_uuid.e_private = NULL;
	}

916
917
	switch( mode ) {
	case LDAP_SYNC_ADD:
Howard Chu's avatar
Howard Chu committed
918
		if ( ri->ri_isref && so->s_op->o_managedsait <= SLAP_CONTROL_IGNORED ) {
919
			rs.sr_ref = get_entry_referrals( op, rs.sr_entry );
920
			rs.sr_err = send_search_reference( op, &rs );
921
922
923
924
925
			ber_bvarray_free( rs.sr_ref );
			break;
		}
		/* fallthru */
	case LDAP_SYNC_MODIFY:
926
927
928
929
		Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: sending %s, dn=%s\n",
			op->o_log_prefix,
			mode == LDAP_SYNC_ADD ? "LDAP_SYNC_ADD" : "LDAP_SYNC_MODIFY",
			e_uuid.e_nname.bv_val );
930
		rs.sr_attrs = op->ors_attrs;
931
		rs.sr_err = send_search_entry( op, &rs );
932
933
		break;
	case LDAP_SYNC_DELETE:
934
935
936
		Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: "
			"sending LDAP_SYNC_DELETE, dn=%s\n",
			op->o_log_prefix, ri->ri_dn.bv_val );
937
		e_uuid.e_attrs = NULL;
Howard Chu's avatar
Howard Chu committed
938
939
940
		e_uuid.e_name = ri->ri_dn;
		e_uuid.e_nname = ri->ri_ndn;
		if ( ri->ri_isref && so->s_op->o_managedsait <= SLAP_CONTROL_IGNORED ) {
941
			struct berval bv = BER_BVNULL;
942
			rs.sr_ref = &bv;
943
			rs.sr_err = send_search_reference( op, &rs );
Howard Chu's avatar
Howard Chu committed
944
		} else {
945
			rs.sr_err = send_search_entry( op, &rs );
946
947
948
949
950
		}
		break;
	default:
		assert(0);
	}
951
952
953
	return rs.sr_err;
}

954
955
956
static void
syncprov_qstart( syncops *so );

957
958
/* Play back queued responses */
static int
959
syncprov_qplay( Operation *op, syncops *so )
960
961
{
	syncres *sr;
962
	int rc = 0;
963

964
	do {
965
		ldap_pvt_thread_mutex_lock( &so->s_mutex );
966
		sr = so->s_res;
Howard Chu's avatar
Howard Chu committed
967
		/* Exit loop with mutex held */
Howard Chu's avatar
Howard Chu committed
968
		if ( !sr )
969
			break;
Howard Chu's avatar
Howard Chu committed
970
971
972
		so->s_res = sr->s_next;
		if ( !so->s_res )
			so->s_restail = NULL;
Howard Chu's avatar
Howard Chu committed
973
		ldap_pvt_thread_mutex_unlock( &so->s_mutex );
974

975
		if ( !so->s_op->o_abandon ) {
976

977
978
			if ( sr->s_mode == LDAP_SYNC_NEW_COOKIE ) {
				SlapReply rs = { REP_INTERMEDIATE };
979

980
				rc = syncprov_sendinfo( op, &rs, LDAP_TAG_SYNC_NEW_COOKIE,
Howard Chu's avatar
Howard Chu committed
981
					&sr->s_info->ri_cookie, 0, NULL, 0 );
982
			} else {
Howard Chu's avatar
Howard Chu committed
983
				rc = syncprov_sendresp( op, sr->s_info, so, sr->s_mode );
984
			}
985
		}
986

Howard Chu's avatar
Howard Chu committed
987
		free_resinfo( sr );
988
		ch_free( sr );
989

990
991
992
		if ( so->s_op->o_abandon )
			continue;

993
994
		/* Exit loop with mutex held */
		ldap_pvt_thread_mutex_lock( &so->s_mutex );
995
		break;
Howard Chu's avatar
Howard Chu committed
996

997
	} while (1);
998
999

	/* We now only send one change at a time, to prevent one
Howard Chu's avatar
Howard Chu committed
1000
1001
	 * psearch from hogging all the CPU. Resubmit this task if
	 * there are more responses queued and no errors occurred.
1002
	 */
1003

Howard Chu's avatar
Howard Chu committed
1004
	if ( rc == 0 && so->s_res ) {
1005
		syncprov_qstart( so );
1006
	}
1007

1008
1009
1010
	return rc;
}

1011
/* task for playing back queued responses */
1012
1013
1014
static void *
syncprov_qtask( void *ctx, void *arg )
{
1015
	syncops *so = arg;
1016
	OperationBuffer opbuf;
1017