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