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