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