New module recctrl. Used to manage records (extract/retrieval).
[idzebra-moved-to-github.git] / index / extract.c
1 /*
2  * Copyright (C) 1994-1996, Index Data I/S 
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: extract.c,v $
7  * Revision 1.62  1996-10-11 10:57:01  adam
8  * New module recctrl. Used to manage records (extract/retrieval).
9  * Several files have been moved to the recctrl sub directory.
10  *
11  * Revision 1.61  1996/06/06 12:08:37  quinn
12  * Added showRecord function
13  *
14  * Revision 1.60  1996/06/04  10:18:12  adam
15  * Search/scan uses character mapping module.
16  *
17  * Revision 1.59  1996/05/14  15:47:07  adam
18  * Cleanup of various buffer size entities.
19  *
20  * Revision 1.58  1996/05/14  06:16:38  adam
21  * Compact use/set bytes used in search service.
22  *
23  * Revision 1.57  1996/05/13 14:23:04  adam
24  * Work on compaction of set/use bytes in dictionary.
25  *
26  * Revision 1.56  1996/05/09  09:54:42  adam
27  * Server supports maps from one logical attributes to a list of physical
28  * attributes.
29  * The extraction process doesn't make space consuming 'any' keys.
30  *
31  * Revision 1.55  1996/05/09  07:28:55  quinn
32  * Work towards phrases and multiple registers
33  *
34  * Revision 1.54  1996/05/01  13:46:35  adam
35  * First work on multiple records in one file.
36  * New option, -offset, to the "unread" command in the filter module.
37  *
38  * Revision 1.53  1996/04/26  12:09:43  adam
39  * Added a few comments.
40  *
41  * Revision 1.52  1996/04/25  13:27:57  adam
42  * Function recordExtract modified so that files with no keys (possibly empty)
43  * are ignored.
44  *
45  * Revision 1.51  1996/03/19  11:08:42  adam
46  * Bug fix: Log preamble wasn't always turned off after recordExtract.
47  *
48  * Revision 1.50  1996/02/12  18:45:36  adam
49  * New fileVerboseFlag in record group control.
50  *
51  * Revision 1.49  1996/02/05  12:29:57  adam
52  * Logging reduced a bit.
53  * The remaining running time is estimated during register merge.
54  *
55  * Revision 1.48  1996/02/01  20:53:26  adam
56  * The temporary per-record keys are compacted a little, and duplication
57  * of the per-records keys are avoided when they are saved in the record
58  * information buffer.
59  *
60  * Revision 1.47  1996/01/17  14:57:48  adam
61  * Prototype changed for reader functions in extract/retrieve. File
62  *  is identified by 'void *' instead of 'int.
63  *
64  * Revision 1.46  1995/12/15  14:57:16  adam
65  * Bug fix.
66  *
67  * Revision 1.45  1995/12/15  12:37:41  adam
68  * In addRecordKeyAny: Writes key only when attrSet != -1.
69  *
70  * Revision 1.44  1995/12/12  16:00:54  adam
71  * System call sync(2) used after update/commit.
72  * Locking (based on fcntl) uses F_EXLCK and F_SHLCK instead of F_WRLCK
73  * and F_RDLCK.
74  *
75  * Revision 1.43  1995/12/11  09:12:46  adam
76  * The rec_get function returns NULL if record doesn't exist - will
77  * happen in the server if the result set records have been deleted since
78  * the creation of the set (i.e. the search).
79  * The server saves a result temporarily if it is 'volatile', i.e. the
80  * set is register dependent.
81  *
82  * Revision 1.42  1995/12/07  17:38:46  adam
83  * Work locking mechanisms for concurrent updates/commit.
84  *
85  * Revision 1.41  1995/12/06  16:06:42  adam
86  * Better diagnostics. Work on 'real' dictionary deletion.
87  *
88  * Revision 1.40  1995/12/05  16:57:40  adam
89  * More work on regular patterns.
90  *
91  * Revision 1.39  1995/12/05  13:20:18  adam
92  * Bug fix: file_read sometimes returned early EOF.
93  *
94  * Revision 1.38  1995/12/04  17:59:21  adam
95  * More work on regular expression conversion.
96  *
97  * Revision 1.37  1995/12/04  14:22:27  adam
98  * Extra arg to recType_byName.
99  * Started work on new regular expression parsed input to
100  * structured records.
101  *
102  * Revision 1.36  1995/11/30  08:34:29  adam
103  * Started work on commit facility.
104  * Changed a few malloc/free to xmalloc/xfree.
105  *
106  * Revision 1.35  1995/11/28  14:26:21  adam
107  * Bug fix: recordId with constant wasn't right.
108  * Bug fix: recordId dictionary entry wasn't deleted when needed.
109  *
110  * Revision 1.34  1995/11/28  09:09:38  adam
111  * Zebra config renamed.
112  * Use setting 'recordId' to identify record now.
113  * Bug fix in recindex.c: rec_release_blocks was invokeded even
114  * though the blocks were already released.
115  * File traversal properly deletes records when needed.
116  *
117  * Revision 1.33  1995/11/27  09:56:20  adam
118  * Record info elements better enumerated. Internal store of records.
119  *
120  * Revision 1.32  1995/11/25  10:24:05  adam
121  * More record fields - they are enumerated now.
122  * New options: flagStoreData flagStoreKey.
123  *
124  * Revision 1.31  1995/11/24  11:31:35  adam
125  * Commands add & del read filenames from stdin if source directory is
126  * empty.
127  * Match criteria supports 'constant' strings.
128  *
129  * Revision 1.30  1995/11/22  17:19:16  adam
130  * Record management uses the bfile system.
131  *
132  * Revision 1.29  1995/11/21  15:01:14  adam
133  * New general match criteria implemented.
134  * New feature: document groups.
135  *
136  * Revision 1.28  1995/11/21  09:20:30  adam
137  * Yet more work on record match.
138  *
139  * Revision 1.27  1995/11/20  16:59:45  adam
140  * New update method: the 'old' keys are saved for each records.
141  *
142  * Revision 1.26  1995/11/20  11:56:24  adam
143  * Work on new traversal.
144  *
145  * Revision 1.25  1995/11/16  15:34:54  adam
146  * Uses new record management system in both indexer and server.
147  *
148  * Revision 1.24  1995/11/15  19:13:08  adam
149  * Work on record management.
150  *
151  * Revision 1.23  1995/10/27  14:00:10  adam
152  * Implemented detection of database availability.
153  *
154  * Revision 1.22  1995/10/17  18:02:07  adam
155  * New feature: databases. Implemented as prefix to words in dictionary.
156  *
157  * Revision 1.21  1995/10/10  12:24:38  adam
158  * Temporary sort files are compressed.
159  *
160  * Revision 1.20  1995/10/06  13:52:05  adam
161  * Bug fixes. Handler may abort further scanning.
162  *
163  * Revision 1.19  1995/10/04  12:55:16  adam
164  * Bug fix in ranked search. Use=Any keys inserted.
165  *
166  * Revision 1.18  1995/10/04  09:37:08  quinn
167  * Fixed bug.
168  *
169  * Revision 1.17  1995/10/03  14:28:57  adam
170  * Buffered read in extract works.
171  *
172  * Revision 1.16  1995/10/03  14:28:45  adam
173  * Work on more effecient read handler in extract.
174  *
175  * Revision 1.15  1995/10/02  15:42:53  adam
176  * Extract uses file descriptors instead of FILE pointers.
177  *
178  * Revision 1.14  1995/10/02  15:29:13  adam
179  * More logging in file_extract.
180  *
181  * Revision 1.13  1995/09/29  14:01:39  adam
182  * Bug fixes.
183  *
184  * Revision 1.12  1995/09/28  14:22:56  adam
185  * Sort uses smaller temporary files.
186  *
187  * Revision 1.11  1995/09/28  12:10:31  adam
188  * Bug fixes. Field prefix used in queries.
189  *
190  * Revision 1.10  1995/09/28  09:19:41  adam
191  * xfree/xmalloc used everywhere.
192  * Extract/retrieve method seems to work for text records.
193  *
194  * Revision 1.9  1995/09/27  12:22:28  adam
195  * More work on extract in record control.
196  * Field name is not in isam keys but in prefix in dictionary words.
197  *
198  * Revision 1.8  1995/09/14  07:48:22  adam
199  * Record control management.
200  *
201  * Revision 1.7  1995/09/11  13:09:32  adam
202  * More work on relevance feedback.
203  *
204  * Revision 1.6  1995/09/08  14:52:27  adam
205  * Minor changes. Dictionary is lower case now.
206  *
207  * Revision 1.5  1995/09/06  16:11:16  adam
208  * Option: only one word key per file.
209  *
210  * Revision 1.4  1995/09/05  15:28:39  adam
211  * More work on search engine.
212  *
213  * Revision 1.3  1995/09/04  12:33:41  adam
214  * Various cleanup. YAZ util used instead.
215  *
216  * Revision 1.2  1995/09/04  09:10:34  adam
217  * More work on index add/del/update.
218  * Merge sort implemented.
219  * Initial work on z39 server.
220  *
221  * Revision 1.1  1995/09/01  14:06:35  adam
222  * Split of work into more files.
223  *
224  */
225 #include <stdio.h>
226 #include <assert.h>
227 #include <unistd.h>
228 #include <fcntl.h>
229
230 #include <alexutil.h>
231 #include <recctrl.h>
232 #include "index.h"
233
234 #include "zinfo.h"
235
236 static Dict matchDict;
237
238 static Records records = NULL;
239
240 static char **key_buf;
241 static size_t ptr_top;
242 static size_t ptr_i;
243 static size_t key_buf_used;
244 static int key_file_no;
245
246 static int records_inserted = 0;
247 static int records_updated = 0;
248 static int records_deleted = 0;
249 static int records_processed = 0;
250
251 static ZebTargetInfo *zti = NULL;
252
253 static void logRecord (int showFlag)
254 {
255     if (!showFlag)
256         ++records_processed;
257     if (showFlag || !(records_processed % 1000))
258     {
259         logf (LOG_LOG, "Records: %7d i/u/d %d/%d/%d", 
260               records_processed, records_inserted, records_updated,
261               records_deleted);
262     }
263 }
264
265 void key_open (int mem)
266 {
267     if (mem < 50000)
268         mem = 50000;
269     key_buf = xmalloc (mem);
270     ptr_top = mem/sizeof(char*);
271     ptr_i = 0;
272
273     key_buf_used = 0;
274     key_file_no = 0;
275
276     if (!(matchDict = dict_open (GMATCH_DICT, 50, 1)))
277     {
278         logf (LOG_FATAL, "dict_open fail of %s", GMATCH_DICT);
279         exit (1);
280     }
281     assert (!records);
282     records = rec_open (1);
283 #if 1
284     zti = zebTargetInfo_open (records, 1);
285 #endif
286 }
287
288 struct encode_info {
289     int  sysno;
290     int  seqno;
291     char buf[768];
292 };
293
294 void encode_key_init (struct encode_info *i)
295 {
296     i->sysno = 0;
297     i->seqno = 0;
298 }
299
300 char *encode_key_int (int d, char *bp)
301 {
302     if (d <= 63)
303         *bp++ = d;
304     else if (d <= 16383)
305     {
306         *bp++ = 64 + (d>>8);
307         *bp++ = d  & 255;
308     }
309     else if (d <= 4194303)
310     {
311         *bp++ = 128 + (d>>16);
312         *bp++ = (d>>8) & 255;
313         *bp++ = d & 255;
314     }
315     else
316     {
317         *bp++ = 192 + (d>>24);
318         *bp++ = (d>>16) & 255;
319         *bp++ = (d>>8) & 255;
320         *bp++ = d & 255;
321     }
322     return bp;
323 }
324
325 void encode_key_write (char *k, struct encode_info *i, FILE *outf)
326 {
327     struct it_key key;
328     char *bp = i->buf;
329
330     while ((*bp++ = *k++))
331         ;
332     memcpy (&key, k+1, sizeof(struct it_key));
333     bp = encode_key_int ( (key.sysno - i->sysno) * 2 + *k, bp);
334     if (i->sysno != key.sysno)
335     {
336         i->sysno = key.sysno;
337         i->seqno = 0;
338     }
339     bp = encode_key_int (key.seqno - i->seqno, bp);
340     i->seqno = key.seqno;
341     if (fwrite (i->buf, bp - i->buf, 1, outf) != 1)
342     {
343         logf (LOG_FATAL|LOG_ERRNO, "fwrite");
344         exit (1);
345     }
346 }
347
348 void key_flush (void)
349 {
350     FILE *outf;
351     char out_fname[200];
352     char *prevcp, *cp;
353     struct encode_info encode_info;
354     
355     if (ptr_i <= 0)
356         return;
357
358     key_file_no++;
359     logf (LOG_LOG, "sorting section %d", key_file_no);
360     qsort (key_buf + ptr_top-ptr_i, ptr_i, sizeof(char*), key_qsort_compare);
361     sprintf (out_fname, TEMP_FNAME, key_file_no);
362
363     if (!(outf = fopen (out_fname, "w")))
364     {
365         logf (LOG_FATAL|LOG_ERRNO, "fopen (4) %s", out_fname);
366         exit (1);
367     }
368     logf (LOG_LOG, "writing section %d", key_file_no);
369     prevcp = cp = key_buf[ptr_top-ptr_i];
370     
371     encode_key_init (&encode_info);
372     encode_key_write (cp, &encode_info, outf);
373     while (--ptr_i > 0)
374     {
375         cp = key_buf[ptr_top-ptr_i];
376         if (strcmp (cp, prevcp))
377         {
378             encode_key_init (&encode_info);
379             encode_key_write (cp, &encode_info, outf);
380             prevcp = cp;
381         }
382         else
383             encode_key_write (cp + strlen(cp), &encode_info, outf);
384     }
385     if (fclose (outf))
386     {
387         logf (LOG_FATAL|LOG_ERRNO, "fclose %s", out_fname);
388         exit (1);
389     }
390     logf (LOG_LOG, "finished section %d", key_file_no);
391     ptr_i = 0;
392     key_buf_used = 0;
393 }
394
395 int key_close (void)
396 {
397     key_flush ();
398     xfree (key_buf);
399 #if 1
400     zebTargetInfo_close (zti, 1);
401 #endif
402     rec_close (&records);
403     dict_close (matchDict);
404
405     logRecord (1);
406     return key_file_no;
407 }
408
409 static void wordInit (RecWord *p)
410 {
411     p->attrSet = 1;
412     p->attrUse = 1016;
413     p->which = Word_String;
414 }
415
416 struct recKeys {
417     int buf_used;
418     int buf_max;
419     char *buf;
420     char prevAttrSet;
421     short prevAttrUse;
422 } reckeys;
423
424 static void addRecordKey (const RecWord *p)
425 {
426     char *dst;
427     char attrSet;
428     short attrUse;
429     size_t i;
430     int lead = 0;
431
432     if (reckeys.buf_used+1024 > reckeys.buf_max)
433     {
434         char *b;
435
436         b = xmalloc (reckeys.buf_max += 128000);
437         if (reckeys.buf_used > 0)
438             memcpy (b, reckeys.buf, reckeys.buf_used);
439         xfree (reckeys.buf);
440         reckeys.buf = b;
441     }
442     dst = reckeys.buf + reckeys.buf_used;
443
444     attrSet = p->attrSet;
445     if (reckeys.buf_used > 0 && reckeys.prevAttrSet == attrSet)
446         lead |= 1;
447     else
448         reckeys.prevAttrSet = attrSet;
449     attrUse = p->attrUse;
450     if (reckeys.buf_used > 0 && reckeys.prevAttrUse == attrUse)
451         lead |= 2;
452     else
453         reckeys.prevAttrUse = attrUse;
454
455     *dst++ = lead;
456
457     if (!(lead & 1))
458     {
459         memcpy (dst, &attrSet, sizeof(attrSet));
460         dst += sizeof(attrSet);
461     }
462     if (!(lead & 2))
463     {
464         memcpy (dst, &attrUse, sizeof(attrUse));
465         dst += sizeof(attrUse);
466     }
467     switch (p->which)
468     {
469         case Word_String:
470             *dst++ = 'w';
471             break;
472         case Word_Phrase:
473             *dst++ = 'p';
474             break;
475         case Word_Numeric:
476             *dst++ = 'n';
477     }
478     for (i = 0; p->u.string[i]; i++)
479         *dst++ = p->u.string[i];
480     *dst++ = '\0';
481
482     memcpy (dst, &p->seqno, sizeof(p->seqno));
483     dst += sizeof(p->seqno);
484     reckeys.buf_used = dst - reckeys.buf;
485 }
486
487 static void flushRecordKeys (SYSNO sysno, int cmd, struct recKeys *reckeys, 
488                              const char *databaseName)
489 {
490     char attrSet = -1;
491     short attrUse = -1;
492     int off = 0;
493
494     if (zebTargetInfo_curDatabase (zti, databaseName))
495     {
496         if (zebTargetInfo_newDatabase (zti, databaseName))
497             abort ();
498     }
499     while (off < reckeys->buf_used)
500     {
501         const char *src = reckeys->buf + off;
502         struct it_key key;
503         int lead;
504     
505         lead = *src++;
506
507         if (!(lead & 1))
508         {
509             memcpy (&attrSet, src, sizeof(attrSet));
510             src += sizeof(attrSet);
511         }
512         if (!(lead & 2))
513         {
514             memcpy (&attrUse, src, sizeof(attrUse));
515             src += sizeof(attrUse);
516         }
517         if (key_buf_used + 1024 > (ptr_top-ptr_i)*sizeof(char*))
518             key_flush ();
519         ++ptr_i;
520         key_buf[ptr_top-ptr_i] = (char*)key_buf + key_buf_used;
521
522         lead = zebTargetInfo_lookupSU (zti, attrSet, attrUse);
523         if (lead < 0)
524             lead = zebTargetInfo_addSU (zti, attrSet, attrUse);
525         assert (lead > 0);
526         ((char*) key_buf) [key_buf_used++] = lead;
527         while (*src)
528             ((char*)key_buf) [key_buf_used++] = *src++;
529         src++;
530         ((char*)key_buf) [key_buf_used++] = '\0';
531         
532         ((char*) key_buf)[key_buf_used++] = cmd;
533
534         memcpy (&key.seqno, src, sizeof(key.seqno));
535         src += sizeof(key.seqno);
536         key.sysno = sysno;
537         memcpy ((char*)key_buf + key_buf_used, &key, sizeof(key));
538         key_buf_used += sizeof(key);
539         off = src - reckeys->buf;
540     }
541     assert (off == reckeys->buf_used);
542 }
543
544 static const char **searchRecordKey (struct recKeys *reckeys,
545                                int attrSetS, int attrUseS)
546 {
547     static const char *ws[32];
548     int off = 0;
549     int startSeq = -1;
550     int i;
551
552     for (i = 0; i<32; i++)
553         ws[i] = NULL;
554     
555     while (off < reckeys->buf_used)
556     {
557         const char *src = reckeys->buf + off;
558         char attrSet;
559         short attrUse;
560         int seqno;
561         const char *wstart;
562         
563         memcpy (&attrSet, src, sizeof(attrSet));
564         src += sizeof(attrSet);
565
566         memcpy (&attrUse, src, sizeof(attrUse));
567         src += sizeof(attrUse);
568
569         wstart = src;
570         while (*src++)
571             ;
572
573         memcpy (&seqno, src, sizeof(seqno));
574         src += sizeof(seqno);
575
576 #if 0
577         logf (LOG_LOG, "(%d,%d) %d %s", attrSet, attrUse, seqno, wstart);
578 #endif
579         if (attrUseS == attrUse && attrSetS == attrSet)
580         {
581             int woff;
582
583
584             if (startSeq == -1)
585                 startSeq = seqno;
586             woff = seqno - startSeq;
587             if (woff >= 0 && woff < 31)
588                 ws[woff] = wstart;
589         }
590
591         off = src - reckeys->buf;
592     }
593     assert (off == reckeys->buf_used);
594     return ws;
595 }
596
597 struct file_read_info {
598     off_t file_max;
599     off_t file_offset;
600     off_t file_moffset;
601     int file_more;
602     int fd;
603 };
604
605 static struct file_read_info *file_read_start (int fd)
606 {
607     struct file_read_info *fi = xmalloc (sizeof(*fi));
608
609     fi->fd = fd;
610     fi->file_max = 0;
611     fi->file_moffset = 0;
612     return fi;
613 }
614
615 static void file_read_stop (struct file_read_info *fi)
616 {
617     assert (fi);
618     xfree (fi);
619 }
620
621 static off_t file_seek (void *handle, off_t offset)
622 {
623     struct file_read_info *p = handle;
624     p->file_offset = offset;
625     return lseek (p->fd, offset, SEEK_SET);
626 }
627
628 static int file_read (void *handle, char *buf, size_t count)
629 {
630     struct file_read_info *p = handle;
631     int fd = p->fd;
632     int r;
633     r = read (fd, buf, count);
634     if (r > 0)
635     {
636         p->file_offset += r;
637         if (p->file_offset > p->file_max)
638             p->file_max = p->file_offset;
639     }
640     return r;
641 }
642
643 static void file_begin (void *handle)
644 {
645     struct file_read_info *p = handle;
646
647     p->file_offset = p->file_moffset;
648     if (p->file_moffset)
649         lseek (p->fd, p->file_moffset, SEEK_SET);
650     p->file_more = 0;
651 }
652
653 static void file_end (void *handle, off_t offset)
654 {
655     struct file_read_info *p = handle;
656
657     assert (p->file_more == 0);
658     p->file_more = 1;
659     p->file_moffset = offset;
660 }
661
662 static int atois (const char **s)
663 {
664     int val = 0, c;
665     while ( (c=**s) >= '0' && c <= '9')
666     {
667         val = val*10 + c - '0';
668         ++(*s);
669     }
670     return val;
671 }
672
673 static char *fileMatchStr (struct recKeys *reckeys, struct recordGroup *rGroup,
674                            const char *fname,
675                            const char *spec)
676 {
677     static char dstBuf[2048];
678     char *dst = dstBuf;
679     const char *s = spec;
680     static const char **w;
681     int i;
682
683     while (1)
684     {
685         while (*s == ' ' || *s == '\t')
686             s++;
687         if (!*s)
688             break;
689         if (*s == '(')
690         {
691             char matchFlag[32];
692             int attrSet, attrUse;
693             int first = 1;
694
695             s++;
696             attrSet = atois (&s);
697             if (*s != ',')
698             {
699                 logf (LOG_WARN, "Missing , in match criteria %s in group %s",
700                       spec, rGroup->groupName ? rGroup->groupName : "none");
701                 return NULL;
702             }
703             s++;
704             attrUse = atois (&s);
705             w = searchRecordKey (reckeys, attrSet, attrUse);
706             assert (w);
707
708             if (*s == ')')
709             {
710                 for (i = 0; i<32; i++)
711                     matchFlag[i] = 1;
712             }
713             else
714             {
715                 logf (LOG_WARN, "Missing ) in match criteria %s in group %s",
716                       spec, rGroup->groupName ? rGroup->groupName : "none");
717                 return NULL;
718             }
719             s++;
720
721             for (i = 0; i<32; i++)
722                 if (matchFlag[i] && w[i])
723                 {
724                     if (first)
725                     {
726                         *dst++ = ' ';
727                         first = 0;
728                     }
729                     strcpy (dst, w[i]);
730                     dst += strlen(w[i]);
731                 }
732             if (first)
733             {
734                 logf (LOG_WARN, "Record didn't contain match"
735                       " fields in (%d,%d)", attrSet, attrUse);
736                 return NULL;
737             }
738         }
739         else if (*s == '$')
740         {
741             int spec_len;
742             char special[64];
743             const char *spec_src = NULL;
744             const char *s1 = ++s;
745             while (*s1 && *s1 != ' ' && *s1 != '\t')
746                 s1++;
747
748             spec_len = s1 - s;
749             if (spec_len > 63)
750                 spec_len = 63;
751             memcpy (special, s, spec_len);
752             special[spec_len] = '\0';
753             s = s1;
754
755             if (!strcmp (special, "group"))
756                 spec_src = rGroup->groupName;
757             else if (!strcmp (special, "database"))
758                 spec_src = rGroup->databaseName;
759             else if (!strcmp (special, "filename"))
760                 spec_src = fname;
761             else if (!strcmp (special, "type"))
762                 spec_src = rGroup->recordType;
763             else 
764                 spec_src = NULL;
765             if (spec_src)
766             {
767                 strcpy (dst, spec_src);
768                 dst += strlen (spec_src);
769             }
770         }
771         else if (*s == '\"' || *s == '\'')
772         {
773             int stopMarker = *s++;
774             char tmpString[64];
775             int i = 0;
776
777             while (*s && *s != stopMarker)
778             {
779                 if (i < 63)
780                     tmpString[i++] = *s++;
781             }
782             if (*s)
783                 s++;
784             tmpString[i] = '\0';
785             strcpy (dst, tmpString);
786             dst += strlen (tmpString);
787         }
788         else
789         {
790             logf (LOG_WARN, "Syntax error in match criteria %s in group %s",
791                   spec, rGroup->groupName ? rGroup->groupName : "none");
792             return NULL;
793         }
794         *dst++ = 1;
795     }
796     if (dst == dstBuf)
797     {
798         logf (LOG_WARN, "No match criteria for record %s in group %s",
799               fname, rGroup->groupName ? rGroup->groupName : "none");
800         return NULL;
801     }
802     return dstBuf;
803 }
804
805 struct recordLogInfo {
806     const char *fname;
807     char *op;
808     struct recordGroup *rGroup;
809 };
810      
811 static void recordLogPreamble (int level, const char *msg, void *info)
812 {
813     struct recordLogInfo *p = info;
814     FILE *outf = log_file ();
815
816     if (level & LOG_LOG)
817         return ;
818     if (p->op) 
819         fprintf (outf, "%s of ", p->op);
820     fprintf (outf, "%s type %s\n", p->rGroup->recordType, p->fname);
821     log_event_start (NULL, NULL);
822 }
823
824 static int recordExtract (SYSNO *sysno, const char *fname,
825                           struct recordGroup *rGroup, int deleteFlag,
826                           struct file_read_info *fi, RecType recType,
827                           char *subType)
828 {
829     struct recExtractCtrl extractCtrl;
830     int r;
831     char *matchStr;
832     SYSNO sysnotmp;
833     off_t recordOffset = 0;
834     Record rec;
835     struct recordLogInfo logInfo;
836
837     logInfo.fname = fname;
838     logInfo.op = NULL;
839     logInfo.rGroup = rGroup;
840     log_event_start (recordLogPreamble, &logInfo);
841     
842     if (fi->fd != -1)
843     {
844         /* we are going to read from a file, so prepare the extraction */
845         extractCtrl.fh = fi;
846         extractCtrl.subType = subType;
847         extractCtrl.init = wordInit;
848         extractCtrl.add = addRecordKey;
849
850         reckeys.buf_used = 0;
851         reckeys.prevAttrUse = -1;
852         reckeys.prevAttrSet = -1;
853
854         recordOffset = fi->file_moffset;
855         extractCtrl.offset = recordOffset;
856         extractCtrl.readf = file_read;
857         extractCtrl.seekf = file_seek;
858         extractCtrl.endf = file_end;
859         extractCtrl.map_chrs_input = map_chrs_input;
860         extractCtrl.flagShowRecords = rGroup->flagShowRecords;
861         r = (*recType->extract)(&extractCtrl);
862
863         if (r)      
864         {
865             /* error occured during extraction ... */
866             logf (LOG_WARN, "Couldn't extract file %s, code %d", fname, r);
867             return 0;
868         }
869         if (reckeys.buf_used == 0)
870         {
871             /* the extraction process returned no information - the record
872                is probably empty */
873             logf (LOG_WARN, "Empty file %s", fname);
874             return 0;
875         }
876     }
877
878     /* perform match if sysno not known and if match criteria is specified */
879        
880     matchStr = NULL;
881     if (!sysno) 
882     {
883         sysnotmp = 0;
884         sysno = &sysnotmp;
885         if (rGroup->recordId && *rGroup->recordId)
886         {
887             char *rinfo;
888         
889             matchStr = fileMatchStr (&reckeys, rGroup, fname, 
890                                      rGroup->recordId);
891             if (matchStr)
892             {
893                 rinfo = dict_lookup (matchDict, matchStr);
894                 if (rinfo)
895                     memcpy (sysno, rinfo+1, sizeof(*sysno));
896             }
897             else
898             {
899                 logf (LOG_WARN, "Bad match criteria");
900                 return 0;
901             }
902         }
903     }
904
905     if (! *sysno)
906     {
907         /* new record */
908         if (deleteFlag)
909         {
910             logf (LOG_LOG, "Cannot delete new record");
911             return 1;
912         }
913         logInfo.op = "add";
914         if (rGroup->fileVerboseFlag)
915             logf (LOG_LOG, "add %s %s+%ld", rGroup->recordType,
916                   fname, (long) recordOffset);
917         rec = rec_new (records);
918         *sysno = rec->sysno;
919
920         if (matchStr)
921         {
922             dict_insert (matchDict, matchStr, sizeof(*sysno), sysno);
923         }
924         flushRecordKeys (*sysno, 1, &reckeys, rGroup->databaseName);
925
926         records_inserted++;
927     }
928     else
929     {
930         /* record already exists */
931         struct recKeys delkeys;
932
933         rec = rec_get (records, *sysno);
934         assert (rec);
935         delkeys.buf_used = rec->size[recInfo_delKeys];
936         delkeys.buf = rec->info[recInfo_delKeys];
937         flushRecordKeys (*sysno, 0, &delkeys, rec->info[recInfo_databaseName]);
938         if (deleteFlag)
939         {
940             /* record going to be deleted */
941             logInfo.op = "delete";
942             if (!delkeys.buf_used)
943             {
944                 logf (LOG_WARN, "cannot delete; storeKeys false");
945             }
946             else
947             {
948                 if (rGroup->fileVerboseFlag)
949                     logf (LOG_LOG, "delete %s %s %ld", rGroup->recordType,
950                           fname, (long) recordOffset);
951                 records_deleted++;
952                 if (matchStr)
953                     dict_delete (matchDict, matchStr);
954                 rec_del (records, &rec);
955             }
956             logRecord (0);
957             return 1;
958         }
959         else
960         {
961             /* record going to be updated */
962             logInfo.op = "update";
963             if (!delkeys.buf_used)
964             {
965                 logf (LOG_WARN, "cannot update; storeKeys false");
966             }
967             else
968             {
969                 if (rGroup->fileVerboseFlag)
970                     logf (LOG_LOG, "update %s %s %ld", rGroup->recordType,
971                           fname, (long) recordOffset);
972                 flushRecordKeys (*sysno, 1, &reckeys, rGroup->databaseName); 
973                 records_updated++;
974             }
975         }
976     }
977     /* update file type */
978     xfree (rec->info[recInfo_fileType]);
979     rec->info[recInfo_fileType] =
980         rec_strdup (rGroup->recordType, &rec->size[recInfo_fileType]);
981
982     /* update filename */
983     xfree (rec->info[recInfo_filename]);
984     rec->info[recInfo_filename] =
985         rec_strdup (fname, &rec->size[recInfo_filename]);
986
987     /* update delete keys */
988     xfree (rec->info[recInfo_delKeys]);
989     if (reckeys.buf_used > 0 && rGroup->flagStoreKeys == 1)
990     {
991 #if 1
992         rec->size[recInfo_delKeys] = reckeys.buf_used;
993         rec->info[recInfo_delKeys] = reckeys.buf;
994         reckeys.buf = NULL;
995         reckeys.buf_max = 0;
996 #else
997         rec->info[recInfo_delKeys] = xmalloc (reckeys.buf_used);
998         rec->size[recInfo_delKeys] = reckeys.buf_used;
999         memcpy (rec->info[recInfo_delKeys], reckeys.buf,
1000                 rec->size[recInfo_delKeys]);
1001 #endif
1002     }
1003     else
1004     {
1005         rec->info[recInfo_delKeys] = NULL;
1006         rec->size[recInfo_delKeys] = 0;
1007     }
1008
1009     /* update store data */
1010     xfree (rec->info[recInfo_storeData]);
1011     if (rGroup->flagStoreData == 1)
1012     {
1013         rec->size[recInfo_storeData] = fi->file_max;
1014         rec->info[recInfo_storeData] = xmalloc (fi->file_max);
1015         if (lseek (fi->fd, recordOffset, SEEK_SET) < 0)
1016         {
1017             logf (LOG_ERRNO|LOG_FATAL, "seek to %ld in %s", fname,
1018                   (long) recordOffset);
1019             exit (1);
1020         }
1021         if (read (fi->fd, rec->info[recInfo_storeData], fi->file_max)
1022             < fi->file_max)
1023         {
1024             logf (LOG_ERRNO|LOG_FATAL, "read %d bytes of %s",
1025                   fi->file_max, fname);
1026             exit (1);
1027         }
1028     }
1029     else
1030     {
1031         rec->info[recInfo_storeData] = NULL;
1032         rec->size[recInfo_storeData] = 0;
1033     }
1034     /* update database name */
1035     xfree (rec->info[recInfo_databaseName]);
1036     rec->info[recInfo_databaseName] =
1037         rec_strdup (rGroup->databaseName, &rec->size[recInfo_databaseName]); 
1038
1039     /* update offset */
1040     xfree (rec->info[recInfo_offset]);
1041
1042     rec->size[recInfo_offset] = sizeof(recordOffset);
1043     rec->info[recInfo_offset] = xmalloc (sizeof(recordOffset));
1044     memcpy (rec->info[recInfo_offset], &recordOffset, sizeof(recordOffset));
1045     
1046     /* commit this record */
1047     rec_put (records, &rec);
1048     logRecord (0);
1049     return 1;
1050 }
1051
1052 int fileExtract (SYSNO *sysno, const char *fname, 
1053                  const struct recordGroup *rGroupP, int deleteFlag)
1054 {
1055     int r, i, fd;
1056     char gprefix[128];
1057     char ext[128];
1058     char ext_res[128];
1059     char subType[128];
1060     RecType recType;
1061     struct recordGroup rGroupM;
1062     struct recordGroup *rGroup = &rGroupM;
1063     struct file_read_info *fi;
1064
1065     memcpy (rGroup, rGroupP, sizeof(*rGroupP));
1066    
1067     if (!rGroup->groupName || !*rGroup->groupName)
1068         *gprefix = '\0';
1069     else
1070         sprintf (gprefix, "%s.", rGroup->groupName);
1071
1072     logf (LOG_DEBUG, "fileExtract %s", fname);
1073
1074     /* determine file extension */
1075     for (i = strlen(fname); --i >= 0; )
1076         if (fname[i] == '/')
1077         {
1078             strcpy (ext, "");
1079             break;
1080         }
1081         else if (fname[i] == '.')
1082         {
1083             strcpy (ext, fname+i+1);
1084             break;
1085         }
1086     /* determine file type - depending on extension */
1087     if (!rGroup->recordType)
1088     {
1089         sprintf (ext_res, "%srecordType.%s", gprefix, ext);
1090         if (!(rGroup->recordType = res_get (common_resource, ext_res)))
1091         {
1092             sprintf (ext_res, "%srecordType", gprefix);
1093             if (!(rGroup->recordType = res_get (common_resource, ext_res)))
1094             {
1095                 if (rGroup->fileVerboseFlag)
1096                     logf (LOG_LOG, "? %s", fname);
1097                 return 0;
1098             }
1099         }
1100     }
1101     if (!rGroup->recordType)
1102     {
1103         if (rGroup->fileVerboseFlag)
1104             logf (LOG_LOG, "? record %s", fname);
1105         return 0;
1106     }
1107     if (!(recType = recType_byName (rGroup->recordType, subType)))
1108     {
1109         logf (LOG_WARN, "No such record type: %s", rGroup->recordType);
1110         return 0;
1111     }
1112
1113     /* determine match criteria */
1114     if (!rGroup->recordId)
1115     {
1116         sprintf (ext_res, "%srecordId.%s", gprefix, ext);
1117         rGroup->recordId = res_get (common_resource, ext_res);
1118     }
1119
1120     /* determine database name */
1121     if (!rGroup->databaseName)
1122     {
1123         sprintf (ext_res, "%sdatabase.%s", gprefix, ext);
1124         if (!(rGroup->databaseName = res_get (common_resource, ext_res)))
1125         {
1126             sprintf (ext_res, "%sdatabase", gprefix);
1127             rGroup->databaseName = res_get (common_resource, ext_res);
1128         }
1129     }
1130     if (!rGroup->databaseName)
1131         rGroup->databaseName = "Default";
1132
1133     if (rGroup->flagStoreData == -1)
1134     {
1135         const char *sval;
1136         sprintf (ext_res, "%sstoreData.%s", gprefix, ext);
1137         if (!(sval = res_get (common_resource, ext_res)))
1138         {
1139             sprintf (ext_res, "%sstoreData", gprefix);
1140             sval = res_get (common_resource, ext_res);
1141         }
1142         if (sval)
1143             rGroup->flagStoreData = atoi (sval);
1144     }
1145     if (rGroup->flagStoreData == -1)
1146         rGroup->flagStoreData = 0;
1147
1148     if (rGroup->flagStoreKeys == -1)
1149     {
1150         const char *sval;
1151
1152         sprintf (ext_res, "%sstoreKeys.%s", gprefix, ext);
1153         if (!(sval = res_get (common_resource, ext_res)))
1154         {
1155             sprintf (ext_res, "%sstoreKeys", gprefix);
1156             sval = res_get (common_resource, ext_res);
1157         }
1158         if (sval)
1159             rGroup->flagStoreKeys = atoi (sval);
1160     }
1161     if (rGroup->flagStoreKeys == -1)
1162         rGroup->flagStoreKeys = 0;
1163
1164     if (sysno && deleteFlag)
1165         fd = -1;
1166     else
1167     {
1168         if ((fd = open (fname, O_RDONLY)) == -1)
1169         {
1170             logf (LOG_WARN|LOG_ERRNO, "open %s", fname);
1171             return 0;
1172         }
1173     }
1174     fi = file_read_start (fd);
1175     do
1176     {
1177         file_begin (fi);
1178         r = recordExtract (sysno, fname, rGroup, deleteFlag, fi,
1179                            recType, subType);
1180     } while (r && !sysno && fi->file_more);
1181     log_event_start (NULL, NULL);
1182     file_read_stop (fi);
1183     if (fd != -1)
1184         close (fd);
1185     return r;
1186 }
1187