1 /* -*- show-trailing-whitespace: t; indent-tabs: t -*-
2  * Copyright (c) 2003,2004,2005,2006 David Lichteblau
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18 #include "common.h"
19 #include "config.h"
20 
21 typedef void (*note_function)(void *, void *, void *);
22 
23 static void
compare_ptr_arrays(GPtrArray * a,GPtrArray * b,int (* cmp)(const void *,const void *),note_function note,void * x)24 compare_ptr_arrays(GPtrArray *a, GPtrArray *b,
25 		   int (*cmp)(const void *, const void *),
26 		   note_function note,
27 		   void *x)
28 {
29 	int i = 0;
30 	int j = 0;
31 
32 	qsort(a->pdata, a->len, sizeof(void *), cmp);
33 	qsort(b->pdata, b->len, sizeof(void *), cmp);
34 
35 	while (i < a->len && j < b->len) {
36 		void *ax = g_ptr_array_index(a, i);
37 		void *bx = g_ptr_array_index(b, j);
38 		int n = cmp(&ax, &bx);
39 		if (n < 0)		{ note(ax, 0,  x);	i++; }
40 		else if (n == 0)	{ note(ax, bx, x);	i++; j++; }
41 		else 			{ note(0,  bx, x);	j++; }
42 	}
43 	if (i == a->len)
44 		for (; j < b->len; j++) note(0, g_ptr_array_index(b, j), x);
45 	else
46 		for (; i < a->len; i++) note(g_ptr_array_index(a, i), 0, x);
47 }
48 
49 static void
note_values(GArray * a,GArray * b,int * changed)50 note_values(GArray *a, GArray *b, int *changed)
51 {
52 	if (!(a && b)) *changed = 1;
53 }
54 
55 static void
compare_attributes(tattribute * clean,tattribute * new,GPtrArray * mods)56 compare_attributes(tattribute *clean, tattribute *new, GPtrArray *mods)
57 {
58 	int changed = 0;
59 	compare_ptr_arrays(attribute_values(clean),
60 			   attribute_values(new),
61 			   carray_ptr_cmp,
62 			   (note_function) note_values,
63 			   &changed);
64 	if (changed) {
65 		LDAPMod *m = attribute2mods(new);
66 		m->mod_op |= LDAP_MOD_REPLACE;
67 		g_ptr_array_add(mods, m);
68 	}
69 }
70 
71 static void
note_attributes(tattribute * a1,tattribute * a2,GPtrArray * mods)72 note_attributes(tattribute *a1, tattribute *a2, GPtrArray *mods)
73 {
74 	tattribute *a;
75 	GPtrArray *values;
76 	LDAPMod *m;
77 	int i;
78 
79 	if (a1 && a2) {
80 		compare_attributes(a1, a2, mods);
81 		return;
82 	}
83 
84 	m = xalloc(sizeof(LDAPMod));
85 	if (a1) {
86 		a = a1;
87 		m->mod_op = LDAP_MOD_DELETE;
88 	} else {
89 		a = a2;
90 		m->mod_op = LDAP_MOD_ADD;
91 	}
92 
93 	values = attribute_values(a);
94 	m->mod_op |= LDAP_MOD_BVALUES;
95 	m->mod_type = xdup(attribute_ad(a));
96 	m->mod_bvalues = xalloc((1 + values->len) * sizeof(struct berval *));
97 	for (i = 0; i < values->len; i++)
98 		m->mod_bvalues[i]
99 			= string2berval(g_ptr_array_index(values, i));
100 	m->mod_bvalues[values->len] = 0;
101 	g_ptr_array_add(mods, m);
102 }
103 
104 static LDAPMod **
compare_entries(tentry * eclean,tentry * enew)105 compare_entries(tentry *eclean, tentry *enew)
106 {
107 	GPtrArray *mods = g_ptr_array_new();
108 	compare_ptr_arrays(entry_attributes(eclean),
109 			   entry_attributes(enew),
110 			   named_array_ptr_cmp,
111 			   (note_function) note_attributes,
112 			   mods);
113 	if (!mods->len) {
114 		g_ptr_array_free(mods, 1);
115 		return 0;
116 	}
117 	g_ptr_array_add(mods, 0);
118 	{
119 		LDAPMod **result = (LDAPMod **) mods->pdata;
120 		g_ptr_array_free(mods, 0);
121 		return result;
122 	}
123 }
124 
125 void
long_array_invert(GArray * array,int i)126 long_array_invert(GArray *array, int i)
127 {
128 	g_array_index(array, long, i) = -2 - g_array_index(array, long, i);
129 }
130 
131 /*
132  * Read N bytes from stream S at position P and stream T at position Q
133  * and compare them.  Return 0 if the segments are equal, else return 1.
134  * If one the files terminates early, return 1.  In any case, reset the
135  * streams to the position they had when this function was invoked.
136  */
137 int
fastcmp(FILE * s,FILE * t,long p,long q,long n)138 fastcmp(FILE *s, FILE *t, long p, long q, long n)
139 {
140 	char *b = xalloc(n); /* XXX */
141 	char *c = xalloc(n); /* XXX */
142 	int rc = -1;
143 	long p_save;
144 	long q_save;
145 
146 	if ( (p_save = ftell(s)) == -1) syserr();
147 	if ( (q_save = ftell(t)) == -1) syserr();
148 
149 	if (fseek(s, p, SEEK_SET) == -1) syserr();
150 	if (fseek(t, q, SEEK_SET) == -1) syserr();
151 	if (fread(b, 1, n, s) != n) { if (ferror(s)) syserr(); goto cleanup; }
152 	if (fread(c, 1, n, t) != n) { if (ferror(t)) syserr(); goto cleanup; }
153 	rc = memcmp(b, c, n) != 0;
154 
155 cleanup:
156 	if (fseek(s, p_save, SEEK_SET) == -1) syserr();
157 	if (fseek(t, q_save, SEEK_SET) == -1) syserr();
158 	free(b);
159 	free(c);
160 	return rc;
161 }
162 
163 /*
164  * Do something with ENTRY and attribute AD, value DATA.
165  *
166  * With mode FROB_RDN_CHECK, determine whether the attribute value is present.
167  * With mode FROB_RDN_CHECK_NONE, determine whether it isn't.
168  * (Return 0 if so, -1 if not.)
169  *
170  * With mode FROB_RDN_REMOVE, remove it
171  * With mode FROB_RDN_ADD, add it (unless already present)
172  * (Return 0.)
173  */
174 int
frob_ava(tentry * entry,int mode,char * ad,char * data,int n)175 frob_ava(tentry *entry, int mode, char *ad, char *data, int n)
176 {
177 	tattribute *a;
178 	switch (mode) {
179 	case FROB_RDN_CHECK:
180 		a = entry_find_attribute(entry, ad, 0);
181 		if (!a) return -1;
182 		if (attribute_find_value(a, data, n) == -1) return -1;
183 		break;
184 	case FROB_RDN_CHECK_NONE:
185 		a = entry_find_attribute(entry, ad, 0);
186 		if (!a) return 0;
187 		if (attribute_find_value(a, data, n) == -1) return 0;
188 		return -1;
189 		break;
190 	case FROB_RDN_REMOVE:
191 		a = entry_find_attribute(entry, ad, 0);
192 		attribute_remove_value(a, data, n);
193 		break;
194 	case FROB_RDN_ADD:
195 		a = entry_find_attribute(entry, ad, 1);
196                 if (attribute_find_value(a, data, n) == -1)
197                         attribute_append_value(a, data, n);
198 		break;
199 	}
200 	return 0;
201 }
202 
203 #if defined(LIBLDAP21)
204 #warning compiling for libldap <= 2.1, running with >= 2.2 will result in segfault
205 #define safe_str2dn ldap_str2dn
206 #elif defined(LIBLDAP22)
207 /*
208  * the following is exactly equivalent to ldap_str2dn in libldap >= 2.2,
209  * but will fail linking on 2.1.  This way we avoid calling the old 2.1
210  * version of ldap_str2dn (leading to a segfault when accessing the result).
211  */
212 static void
safe_str2dn(char * str,LDAPDN * out,int flags)213 safe_str2dn(char *str, LDAPDN *out, int flags)
214 {
215         struct berval bv;
216         bv.bv_val = str;
217         bv.bv_len = strlen(str);
218         ldap_bv2dn_x(&bv, out, flags);
219 }
220 #else
221 #error oops
222 #endif
223 
224 /*
225  * Call frob_ava for every ava in DN's (first) RDN.
226  * DN must be valid.
227  *
228  * Return -1 if frob_ava ever does so, 0 else.
229  */
230 int
frob_rdn(tentry * entry,char * dn,int mode)231 frob_rdn(tentry *entry, char *dn, int mode)
232 {
233 #ifdef LIBLDAP21
234 	LDAPDN *olddn;
235 #else
236 	LDAPDN olddn;
237 #endif
238 	LDAPRDN rdn;
239 	int i;
240 	int rc = 0;
241 
242 	safe_str2dn(dn, &olddn, LDAP_DN_FORMAT_LDAPV3);
243 
244 #ifdef LIBLDAP21
245 	rdn = (**olddn)[0];
246 #else
247 	rdn = olddn[0];
248 #endif
249 	for (i = 0; rdn[i]; i++) {
250 		LDAPAVA *ava = rdn[i];
251 		char *ad = ava->la_attr.bv_val; /* XXX */
252 		struct berval *bv = &ava->la_value;
253 		if (frob_ava(entry, mode, ad, bv->bv_val, bv->bv_len) == -1) {
254 			rc = -1;
255 			goto cleanup;
256 		}
257 	}
258 
259 cleanup:
260 	ldap_dnfree(olddn);
261 	return rc;
262 }
263 
264 /*
265  * Check whether all of the following conditions are true and return a boolean.
266  *   - none of the DNs is empty, so RDN-frobbing code can rely on senseful DNs
267  *   - the attribute values in clean's RDN are contained in clean.
268  *   - the attribute values in data's RDN are contained in data.
269  *   - the attribute values in clean's RDN are either all contained in data
270  *     or that none of them are.
271  */
272 int
validate_rename(tentry * clean,tentry * data,int * deleteoldrdn)273 validate_rename(tentry *clean, tentry *data, int *deleteoldrdn)
274 {
275 	if (!*entry_dn(clean)) {
276 		puts("Error: Cannot rename ROOT_DSE.");
277 		return -1;
278 	}
279 	if (!*entry_dn(data)) {
280 		puts("Error: Cannot replace ROOT_DSE.");
281 		return -1;
282 	}
283 	if (frob_rdn(clean, entry_dn(clean), FROB_RDN_CHECK) == -1) {
284 		puts("Error: Old RDN not found in entry.");
285 		return -1;
286 	}
287 	if (frob_rdn(data, entry_dn(data), FROB_RDN_CHECK) == -1) {
288 		puts("Error: New RDN not found in entry.");
289 		return -1;
290 	}
291 	if (frob_rdn(data, entry_dn(clean), FROB_RDN_CHECK) != -1)
292 		*deleteoldrdn = 0;
293 	else if (frob_rdn(data, entry_dn(clean), FROB_RDN_CHECK_NONE) != -1)
294 		*deleteoldrdn = 1;
295 	else {
296 		puts("Error: Incomplete RDN change.");
297 		return -1;
298 	}
299 	return 0;
300 }
301 
302 static void
rename_entry(tentry * entry,char * newdn,int deleteoldrdn)303 rename_entry(tentry *entry, char *newdn, int deleteoldrdn)
304 {
305 	if (deleteoldrdn)
306 		frob_rdn(entry, entry_dn(entry), FROB_RDN_REMOVE);
307 	frob_rdn(entry, newdn, FROB_RDN_ADD);
308 	free(entry_dn(entry));
309 	entry_dn(entry) = xdup(newdn);
310 }
311 
312 static void
update_clean_copy(GArray * offsets,char * key,FILE * s,tentry * cleanentry,tparser * p)313 update_clean_copy(
314 	GArray *offsets, char *key, FILE *s, tentry *cleanentry, tparser *p)
315 {
316 	long pos = fseek(s, 0, SEEK_END);
317 	if (pos == -1) syserr();
318 	g_array_index(offsets, long, atoi(key)) = ftell(s);
319 	p->print(s, cleanentry, key, 0);
320 }
321 
322 /*
323  * read a changerecord of type `key' from `data', handle it, and return
324  *    0 on success
325  *   -1 on syntax error
326  *   -2 on handler error
327  */
328 int
process_immediate(tparser * p,thandler * handler,void * userdata,FILE * data,long datapos,char * key)329 process_immediate(tparser *p, thandler *handler, void *userdata, FILE *data,
330 		  long datapos, char *key)
331 {
332 	if (!strcmp(key, "add")) {
333 		tentry *entry;
334 		LDAPMod **mods;
335 		if (p->entry(data, datapos, 0, &entry, 0) == -1)
336 			return -1;
337 		mods = entry2mods(entry);
338 		if (handler->add(-1, entry_dn(entry), mods, userdata) == -1) {
339 			ldap_mods_free(mods, 1);
340 			entry_free(entry);
341 			return -2;
342 		}
343 		ldap_mods_free(mods, 1);
344 		entry_free(entry);
345 		entry = 0;
346 	} else if (!strcmp(key, "replace")) {
347 		tentry *entry;
348 		LDAPMod **mods;
349 		int i;
350 		if (p->entry(data, datapos, 0, &entry, 0) == -1)
351 			return -1;
352 		mods = entry2mods(entry);
353 		for (i = 0; mods[i]; i++) {
354 			LDAPMod *mod = mods[i];
355 			mod->mod_op &= LDAP_MOD_BVALUES;
356 			mod->mod_op |= LDAP_MOD_REPLACE;
357 		}
358 		if (handler->change(-1,
359 				    entry_dn(entry),
360 				    entry_dn(entry),
361 				    mods,
362 				    userdata) == -1) {
363 			ldap_mods_free(mods, 1);
364 			entry_free(entry);
365 			return -2;
366 		}
367 		ldap_mods_free(mods, 1);
368 		entry_free(entry);
369 		entry = 0;
370 	} else if (!strcmp(key, "rename")) {
371 		char *dn1;
372 		char *dn2;
373 		int deleteoldrdn;
374 		int rc;
375 		if (p->rename(data, datapos, &dn1, &dn2, &deleteoldrdn) ==-1)
376 			return -1;
377 		rc = handler->rename0(-1, dn1, dn2, deleteoldrdn, userdata);
378 		free(dn1);
379 		free(dn2);
380 		if (rc)
381 			return -2;
382 	} else if (!strcmp(key, "delete")) {
383 		char *dn;
384 		int rc;
385 		if (p->delete(data, datapos, &dn) == -1)
386 			return -1;
387 		rc = handler->delete(-1, dn, userdata);
388 		free(dn);
389 		if (rc)
390 			return -2;
391 	} else if (!strcmp(key, "modify")) {
392 		char *dn;
393 		LDAPMod **mods;
394 		if (p->modify(data, datapos, &dn, &mods) ==-1)
395 			return -1;
396 		if (handler->change(-1, dn, dn, mods, userdata) == -1) {
397 			free(dn);
398 			ldap_mods_free(mods, 1);
399 			return -2;
400 		}
401 	} else {
402 		fprintf(stderr, "Error: Invalid key: `%s'.\n", key);
403 		return -1;
404 	}
405 	return 0;
406 }
407 
408 /*
409  * read the next entry from `data', its clean copy from `clean', process
410  * them as described for compare_streams, and return
411  *    0 on success
412  *   -1 on syntax error
413  *   -2 on handler error
414  */
415 static int
process_next_entry(tparser * p,thandler * handler,void * userdata,GArray * offsets,FILE * clean,FILE * data,char * key,long datapos)416 process_next_entry(
417 	tparser *p, thandler *handler, void *userdata, GArray *offsets,
418 	FILE *clean, FILE *data, char *key, long datapos)
419 {
420 	tentry *entry = 0;
421 	tentry *cleanentry = 0;
422 	int rc = -1;
423 	LDAPMod **mods;
424 	long pos;
425 	char *ptr;
426 	int n;
427 	int rename, deleteoldrdn;
428 
429 	/* find clean copy */
430 	n = strtol(key, &ptr, 10);
431 	if (*ptr)
432 		return process_immediate(
433 			p, handler, userdata, data, datapos, key);
434 	if (n < 0 || n >= offsets->len) {
435 		fprintf(stderr, "Error: Invalid key: `%s'.\n", key);
436 		goto cleanup;
437 	}
438 	pos = g_array_index(offsets, long, n);
439 	if (pos < 0) {
440 		fprintf(stderr, "Error: Duplicate entry %d.\n", n);
441 		goto cleanup;
442 	}
443 
444 	/* find precise position */
445 	if (p->entry(clean, pos, 0, 0, &pos) == -1) abort();
446 	/* fast comparison */
447 	if (n + 1 < offsets->len) {
448 		long next = g_array_index(offsets, long, n + 1);
449 		if (next >= 0
450 		    && !fastcmp(clean, data, pos, datapos, next-pos+1))
451 		{
452 			datapos += next - pos;
453 			long_array_invert(offsets, n);
454 			if (fseek(data, datapos, SEEK_SET) == -1)
455 				syserr();
456 			return 0;
457 		}
458 	}
459 
460 	/* if we get here, a quick scan found a difference in the
461 	 * files, so we need to read the entries and compare them */
462 	if (p->entry(data, datapos, 0, &entry, 0) == -1)
463 		goto cleanup;
464 	if (p->entry(clean, pos, 0, &cleanentry, 0) == -1) abort();
465 
466 	/* compare and update */
467 	if ( (rename = strcmp(entry_dn(cleanentry), entry_dn(entry)))){
468 		if (validate_rename(cleanentry, entry, &deleteoldrdn)){
469 			rc = -1;
470 			goto cleanup;
471 		}
472 		if (handler->rename(n, entry_dn(cleanentry), entry, userdata)
473 		    == -1)
474 		{
475 			rc = -2;
476 			goto cleanup;
477 		}
478 		rename_entry(cleanentry, entry_dn(entry), deleteoldrdn);
479 	}
480 	if ( (mods = compare_entries(cleanentry, entry))) {
481 		if (handler->change(n,
482 				    entry_dn(cleanentry),
483 				    entry_dn(entry),
484 				    mods,
485 				    userdata)
486 		    == -1)
487 		{
488 			if (mods) ldap_mods_free(mods, 1);
489 			if (rename)
490 				update_clean_copy(
491 					offsets, key, clean, cleanentry, p);
492 			rc = -2;
493 			goto cleanup;
494 		}
495 		ldap_mods_free(mods, 1);
496 	}
497 
498 	/* mark as seen */
499 	long_array_invert(offsets, n);
500 
501 	entry_free(entry);
502 	entry = 0;
503 	entry_free(cleanentry);
504 	cleanentry = 0;
505 	return 0;
506 
507 cleanup:
508 	if (entry) {
509 		if (*entry_dn(entry))
510 			fprintf(stderr, "Error at: %s\n", entry_dn(entry));
511 		entry_free(entry);
512 	}
513 	if (cleanentry) entry_free(cleanentry);
514 	return rc;
515 }
516 
517 static int
nonleaf_action(tentry * entry,GArray * offsets,int n)518 nonleaf_action(tentry *entry, GArray *offsets, int n)
519 {
520 	int i;
521 
522 	printf("Error: Cannot delete non-leaf entry: %s\n", entry_dn(entry));
523 
524 	for (i = n + 1; i < offsets->len; i++) {
525 		if (g_array_index(offsets, long, n) >= 0)
526 			goto more_deletions;
527 	}
528 	/* no more deletions anyway, so no need to ignore this one */
529 	return 0;
530 
531 more_deletions:
532 	switch (choose("Continue?", "yn!Q?", "(Type '?' for help.)")) {
533 	case 'y':
534 		return 1;
535 	case '!':
536 		return 2;
537 	case 'n':
538 		return 0;
539 	case 'Q':
540 		exit(0);
541 	case '?':
542 		puts("Commands:\n"
543 		     "  y -- continue deleting other entries\n"
544 		     "  ! -- continue and assume 'y' until done\n"
545 		     "  n -- abort deletions\n"
546 		     "  Q -- discard changes and quit\n"
547 		     "  ? -- this help");
548 		goto more_deletions;
549 	}
550 
551 	/* notreached */
552 	return 0;
553 }
554 
555 /*
556  * process deletions as described for compare_streams.
557  * return 0 on success, -2 else.
558  */
559 static int
process_deletions(tparser * p,thandler * handler,void * userdata,GArray * offsets,FILE * clean)560 process_deletions(tparser *p,
561 		  thandler *handler,
562 		  void *userdata,
563 		  GArray *offsets,
564 		  FILE *clean)
565 {
566 	tentry *cleanentry = 0;
567 	long pos;
568 	int n;
569 	int ignore_nonleaf = 0;
570 	int n_leaf;
571 	int n_nonleaf;
572 
573 	do {
574 		if (ignore_nonleaf)
575 			printf("Retrying %d failed deletion%s...\n",
576 			       n_nonleaf,
577 			       n_nonleaf == 1 ? "" : "s");
578 		n_leaf = 0;
579 		n_nonleaf = 0;
580 		for (n = 0; n < offsets->len; n++) {
581 			if ( (pos = g_array_index(offsets, long, n)) < 0)
582 				continue;
583 			if (p->entry(clean, pos, 0, &cleanentry, 0) == -1)
584 				abort();
585 			switch (handler->delete(
586 					n, entry_dn(cleanentry), userdata))
587 			{
588 			case -1:
589 				entry_free(cleanentry);
590 				return -2;
591 			case -2:
592 				if (ignore_nonleaf) {
593 					printf("Skipping non-leaf entry: %s\n",
594 					       entry_dn(cleanentry));
595 					n_nonleaf++;
596 					break;
597 				}
598 				switch (nonleaf_action(cleanentry,offsets,n)) {
599 				case 0:
600 					entry_free(cleanentry);
601 					return -2;
602 				case 2:
603 					ignore_nonleaf = 1;
604 					/* fall through */
605 				case 1:
606 					n_nonleaf++;
607 				}
608 				break;
609 			default:
610 				n_leaf++;
611 				long_array_invert(offsets, n);
612 			}
613 			entry_free(cleanentry);
614 		}
615 	} while (ignore_nonleaf && n_nonleaf > 0 && n_leaf > 0);
616 
617 	return n_nonleaf ? -2 : 0;
618 }
619 
620 /*
621  * Die compare_streams-Schleife ist das Herz von ldapvi.
622  *
623  * Read two ldapvi data files in streams CLEAN and DATA and compare them.
624  *
625  * File CLEAN must contain numbered entries with consecutive keys starting at
626  * zero.  For each of these entries, array offset must contain a position
627  * in the file, such that the entry can be read by seeking to that position
628  * and calling read_entry().
629  *
630  * File DATA, a modified copy of CLEAN may contain entries in any order,
631  * which must be numbered or labeled "add", "rename", or "modify".  If a
632  * key is a number, the corresponding entry in CLEAN must exist, it is
633  * read and compared to the modified copy.
634  *
635  * For each change, call the appropriate handler method with arguments
636  * described below.  Handler methods must return 0 on success, or -1 on
637  * failure.  (As a special case, return value -2 on a deletion indicates
638  * an attempt to delete a non-leaf entry, which is non-fatal.)
639  *
640  * For each new entry (labeled with "add"), call
641  *   handler->add(dn, mods, USERDATA)
642  * where MODS is a LDAPMod structure for the new entry.
643  *
644  * For each entry present in CLEAN but not DATA, call
645  *   handler->delete(dn, USERDATA)
646  * (This step can be repeated in the case of non-leaf entries.)
647  *
648  * For each entry present in both files, handler can be called two times.
649  * If the distinguished names of the old and new entry disagree, call
650  *   handler->change(old_entry, new_entry, 0, USERDATA)
651  * If there are additional changes to the attributes of the entry, call
652  *   handler->change(renamed_entry, new_entry, mods, USERDATA)
653  * where RENAMED_ENTRY is a copy of the original entry, which accounts
654  * for attribute modifications due to a possible RDN change (new RDN
655  * component values have to be added, and old RDN values be removed),
656  * and MODS describes the changes between RENAMED_ENTRY and NEW_ENTRY.
657  *
658  * Entries labeled "delete" are changerecords for which the handler is
659  * called as described above.
660  *
661  * Entries labeled "rename" are changerecords with their own method,
662  * called as:
663  *   handler->rename(olddn, newdn, deleteoldrdn, USERDATA)
664  *
665  * Return 0 on success, -1 on parse error, -2 on handler failure.
666  *
667  * If an error occured, *error_position is the offset in DATA after
668  * which the erroneous entry can be found.
669  */
670 int
compare_streams(tparser * p,thandler * handler,void * userdata,GArray * offsets,FILE * clean,FILE * data,long * error_position,long * syntax_error_position)671 compare_streams(tparser *p,
672 		thandler *handler,
673 		void *userdata,
674 		GArray *offsets,
675 		FILE *clean,
676 		FILE *data,
677 		long *error_position,
678 		long *syntax_error_position)
679 {
680 	char *key = 0;
681 	int n;
682 	int rc;
683 
684 	for (;;) {
685 		long datapos;
686 
687 		/* read updated entry */
688 		if (key) { free(key); key = 0; }
689 		if (p->peek(data, -1, &key, &datapos) == -1) goto cleanup;
690 		*error_position = datapos;
691 		if (!key) break;
692 
693 		/* and do something with it */
694 		if ( (rc = process_next_entry(
695 			      p, handler, userdata, offsets, clean, data,
696 			      key, datapos)))
697 			goto cleanup;
698 	}
699 	if ( (*error_position = ftell(data)) == -1) syserr();
700 
701 	rc = process_deletions(p, handler, userdata, offsets, clean);
702 
703 cleanup:
704 	if (key) free(key);
705 
706 	if (syntax_error_position)
707 		if ( (*syntax_error_position = ftell(data)) == -1) syserr();
708 
709 	/* on user error, return now and keep state for recovery */
710 	if (rc == -2) return rc;
711 
712 	/* else some cleanup: unmark offsets */
713 	for (n = 0; n < offsets->len; n++)
714 		if (g_array_index(offsets, long, n) < 0)
715 			long_array_invert(offsets, n);
716 	return rc;
717 }
718