Zebra config renamed.
[idzebra-moved-to-github.git] / index / extract.c
1 /*
2  * Copyright (C) 1994-1995, Index Data I/S 
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: extract.c,v $
7  * Revision 1.34  1995-11-28 09:09:38  adam
8  * Zebra config renamed.
9  * Use setting 'recordId' to identify record now.
10  * Bug fix in recindex.c: rec_release_blocks was invokeded even
11  * though the blocks were already released.
12  * File traversal properly deletes records when needed.
13  *
14  * Revision 1.33  1995/11/27  09:56:20  adam
15  * Record info elements better enumerated. Internal store of records.
16  *
17  * Revision 1.32  1995/11/25  10:24:05  adam
18  * More record fields - they are enumerated now.
19  * New options: flagStoreData flagStoreKey.
20  *
21  * Revision 1.31  1995/11/24  11:31:35  adam
22  * Commands add & del read filenames from stdin if source directory is
23  * empty.
24  * Match criteria supports 'constant' strings.
25  *
26  * Revision 1.30  1995/11/22  17:19:16  adam
27  * Record management uses the bfile system.
28  *
29  * Revision 1.29  1995/11/21  15:01:14  adam
30  * New general match criteria implemented.
31  * New feature: document groups.
32  *
33  * Revision 1.28  1995/11/21  09:20:30  adam
34  * Yet more work on record match.
35  *
36  * Revision 1.27  1995/11/20  16:59:45  adam
37  * New update method: the 'old' keys are saved for each records.
38  *
39  * Revision 1.26  1995/11/20  11:56:24  adam
40  * Work on new traversal.
41  *
42  * Revision 1.25  1995/11/16  15:34:54  adam
43  * Uses new record management system in both indexer and server.
44  *
45  * Revision 1.24  1995/11/15  19:13:08  adam
46  * Work on record management.
47  *
48  * Revision 1.23  1995/10/27  14:00:10  adam
49  * Implemented detection of database availability.
50  *
51  * Revision 1.22  1995/10/17  18:02:07  adam
52  * New feature: databases. Implemented as prefix to words in dictionary.
53  *
54  * Revision 1.21  1995/10/10  12:24:38  adam
55  * Temporary sort files are compressed.
56  *
57  * Revision 1.20  1995/10/06  13:52:05  adam
58  * Bug fixes. Handler may abort further scanning.
59  *
60  * Revision 1.19  1995/10/04  12:55:16  adam
61  * Bug fix in ranked search. Use=Any keys inserted.
62  *
63  * Revision 1.18  1995/10/04  09:37:08  quinn
64  * Fixed bug.
65  *
66  * Revision 1.17  1995/10/03  14:28:57  adam
67  * Buffered read in extract works.
68  *
69  * Revision 1.16  1995/10/03  14:28:45  adam
70  * Work on more effecient read handler in extract.
71  *
72  * Revision 1.15  1995/10/02  15:42:53  adam
73  * Extract uses file descriptors instead of FILE pointers.
74  *
75  * Revision 1.14  1995/10/02  15:29:13  adam
76  * More logging in file_extract.
77  *
78  * Revision 1.13  1995/09/29  14:01:39  adam
79  * Bug fixes.
80  *
81  * Revision 1.12  1995/09/28  14:22:56  adam
82  * Sort uses smaller temporary files.
83  *
84  * Revision 1.11  1995/09/28  12:10:31  adam
85  * Bug fixes. Field prefix used in queries.
86  *
87  * Revision 1.10  1995/09/28  09:19:41  adam
88  * xfree/xmalloc used everywhere.
89  * Extract/retrieve method seems to work for text records.
90  *
91  * Revision 1.9  1995/09/27  12:22:28  adam
92  * More work on extract in record control.
93  * Field name is not in isam keys but in prefix in dictionary words.
94  *
95  * Revision 1.8  1995/09/14  07:48:22  adam
96  * Record control management.
97  *
98  * Revision 1.7  1995/09/11  13:09:32  adam
99  * More work on relevance feedback.
100  *
101  * Revision 1.6  1995/09/08  14:52:27  adam
102  * Minor changes. Dictionary is lower case now.
103  *
104  * Revision 1.5  1995/09/06  16:11:16  adam
105  * Option: only one word key per file.
106  *
107  * Revision 1.4  1995/09/05  15:28:39  adam
108  * More work on search engine.
109  *
110  * Revision 1.3  1995/09/04  12:33:41  adam
111  * Various cleanup. YAZ util used instead.
112  *
113  * Revision 1.2  1995/09/04  09:10:34  adam
114  * More work on index add/del/update.
115  * Merge sort implemented.
116  * Initial work on z39 server.
117  *
118  * Revision 1.1  1995/09/01  14:06:35  adam
119  * Split of work into more files.
120  *
121  */
122 #include <stdio.h>
123 #include <assert.h>
124 #include <unistd.h>
125 #include <fcntl.h>
126 #include <ctype.h>
127
128 #include <alexutil.h>
129 #include <recctrl.h>
130 #include "index.h"
131
132 #include "recindex.h"
133
134 static Dict matchDict;
135
136 static Records records = NULL;
137
138 static char **key_buf;
139 static size_t ptr_top;
140 static size_t ptr_i;
141 static size_t key_buf_used;
142 static int key_file_no;
143
144 static int records_inserted = 0;
145 static int records_updated = 0;
146 static int records_deleted = 0;
147
148 #define MATCH_DICT "match"
149
150 void key_open (int mem)
151 {
152     if (mem < 50000)
153         mem = 50000;
154     key_buf = xmalloc (mem);
155     ptr_top = mem/sizeof(char*);
156     ptr_i = 0;
157
158     key_buf_used = 0;
159     key_file_no = 0;
160
161     if (!(matchDict = dict_open (MATCH_DICT, 20, 1)))
162     {
163         logf (LOG_FATAL, "dict_open fail of %s", MATCH_DICT);
164         exit (1);
165     }
166     assert (!records);
167     records = rec_open (1);
168 }
169
170 struct encode_info {
171     int  sysno;
172     int  seqno;
173     char buf[512];
174 };
175
176 void encode_key_init (struct encode_info *i)
177 {
178     i->sysno = 0;
179     i->seqno = 0;
180 }
181
182 char *encode_key_int (int d, char *bp)
183 {
184     if (d <= 63)
185         *bp++ = d;
186     else if (d <= 16383)
187     {
188         *bp++ = 64 + (d>>8);
189         *bp++ = d  & 255;
190     }
191     else if (d <= 4194303)
192     {
193         *bp++ = 128 + (d>>16);
194         *bp++ = (d>>8) & 255;
195         *bp++ = d & 255;
196     }
197     else
198     {
199         *bp++ = 192 + (d>>24);
200         *bp++ = (d>>16) & 255;
201         *bp++ = (d>>8) & 255;
202         *bp++ = d & 255;
203     }
204     return bp;
205 }
206
207 void encode_key_write (char *k, struct encode_info *i, FILE *outf)
208 {
209     struct it_key key;
210     char *bp = i->buf;
211
212     while ((*bp++ = *k++))
213         ;
214     memcpy (&key, k+1, sizeof(struct it_key));
215     bp = encode_key_int ( (key.sysno - i->sysno) * 2 + *k, bp);
216     if (i->sysno != key.sysno)
217     {
218         i->sysno = key.sysno;
219         i->seqno = 0;
220     }
221     bp = encode_key_int (key.seqno - i->seqno, bp);
222     i->seqno = key.seqno;
223     if (fwrite (i->buf, bp - i->buf, 1, outf) != 1)
224     {
225         logf (LOG_FATAL|LOG_ERRNO, "fwrite");
226         exit (1);
227     }
228 }
229
230 void key_flush (void)
231 {
232     FILE *outf;
233     char out_fname[200];
234     char *prevcp, *cp;
235     struct encode_info encode_info;
236     
237     if (ptr_i <= 0)
238         return;
239
240     key_file_no++;
241     logf (LOG_LOG, "sorting section %d", key_file_no);
242     qsort (key_buf + ptr_top-ptr_i, ptr_i, sizeof(char*), key_qsort_compare);
243     sprintf (out_fname, TEMP_FNAME, key_file_no);
244
245     if (!(outf = fopen (out_fname, "w")))
246     {
247         logf (LOG_FATAL|LOG_ERRNO, "fopen (4) %s", out_fname);
248         exit (1);
249     }
250     logf (LOG_LOG, "writing section %d", key_file_no);
251     prevcp = cp = key_buf[ptr_top-ptr_i];
252     
253     encode_key_init (&encode_info);
254     encode_key_write (cp, &encode_info, outf);
255     while (--ptr_i > 0)
256     {
257         cp = key_buf[ptr_top-ptr_i];
258         if (strcmp (cp, prevcp))
259         {
260             encode_key_init (&encode_info);
261             encode_key_write (cp, &encode_info, outf);
262             prevcp = cp;
263         }
264         else
265             encode_key_write (cp + strlen(cp), &encode_info, outf);
266     }
267     if (fclose (outf))
268     {
269         logf (LOG_FATAL|LOG_ERRNO, "fclose %s", out_fname);
270         exit (1);
271     }
272     logf (LOG_LOG, "finished section %d", key_file_no);
273     ptr_i = 0;
274     key_buf_used = 0;
275 }
276
277 int key_close (void)
278 {
279     key_flush ();
280     xfree (key_buf);
281     rec_close (&records);
282     dict_close (matchDict);
283
284     logf (LOG_LOG, "Records inserted %6d", records_inserted);
285     logf (LOG_LOG, "Records updated  %6d", records_updated);
286     logf (LOG_LOG, "Records deleted  %6d", records_deleted);
287     return key_file_no;
288 }
289
290 static void wordInit (RecWord *p)
291 {
292     p->attrSet = 1;
293     p->attrUse = 1016;
294     p->which = Word_String;
295 }
296
297 struct recKeys {
298     int buf_used;
299     int buf_max;
300     char *buf;
301 } reckeys;
302
303 static void addRecordKey (const RecWord *p)
304 {
305     char *dst;
306     char attrSet;
307     short attrUse;
308     size_t i;
309
310     if (reckeys.buf_used+1024 > reckeys.buf_max)
311     {
312         char *b;
313
314         b = malloc (reckeys.buf_max += 65000);
315         if (reckeys.buf_used > 0)
316             memcpy (b, reckeys.buf, reckeys.buf_used);
317         free (reckeys.buf);
318         reckeys.buf = b;
319     }
320     dst = reckeys.buf + reckeys.buf_used;
321     switch (p->which)
322     {
323     case Word_String:
324         attrSet = p->attrSet;
325         memcpy (dst, &attrSet, sizeof(attrSet));
326         dst += sizeof(attrSet);
327
328         attrUse = p->attrUse;
329         memcpy (dst, &attrUse, sizeof(attrUse));
330         dst += sizeof(attrUse);
331         
332         for (i = 0; p->u.string[i]; i++)
333             *dst++ = p->u.string[i];
334         *dst++ = '\0';
335
336         memcpy (dst, &p->seqno, sizeof(p->seqno));
337         dst += sizeof(p->seqno);
338
339         break;
340     default:
341         return;
342     }
343     reckeys.buf_used = dst - reckeys.buf;
344 }
345
346 static void flushRecordKeys (SYSNO sysno, int cmd, struct recKeys *reckeys, 
347                              const char *databaseName)
348 {
349     int off = 0;
350     while (off < reckeys->buf_used)
351     {
352         const char *src = reckeys->buf + off;
353         char attrSet;
354         short attrUse;
355         struct it_key key;
356         
357         memcpy (&attrSet, src, sizeof(attrSet));
358         src += sizeof(attrSet);
359
360         memcpy (&attrUse, src, sizeof(attrUse));
361         src += sizeof(attrUse);
362
363         if (key_buf_used + 1024 > (ptr_top-ptr_i)*sizeof(char*))
364             key_flush ();
365         ++ptr_i;
366         key_buf[ptr_top-ptr_i] = (char*)key_buf + key_buf_used;
367         key_buf_used += index_word_prefix ((char*)key_buf + key_buf_used,
368                                            attrSet, attrUse, databaseName);
369         while (*src)
370             ((char*)key_buf) [key_buf_used++] = index_char_cvt (*src++);
371         src++;
372         ((char*)key_buf) [key_buf_used++] = '\0';
373         
374         ((char*) key_buf)[key_buf_used++] = cmd;
375
376         memcpy (&key.seqno, src, sizeof(key.seqno));
377         src += sizeof(key.seqno);
378         key.sysno = sysno;
379         memcpy ((char*)key_buf + key_buf_used, &key, sizeof(key));
380         key_buf_used += sizeof(key);
381         off = src - reckeys->buf;
382     }
383     assert (off == reckeys->buf_used);
384 }
385
386 static const char **searchRecordKey (struct recKeys *reckeys,
387                                int attrSetS, int attrUseS)
388 {
389     static const char *ws[32];
390     int off = 0;
391     int startSeq = -1;
392     int i;
393
394     for (i = 0; i<32; i++)
395         ws[i] = NULL;
396     
397     while (off < reckeys->buf_used)
398     {
399         const char *src = reckeys->buf + off;
400         char attrSet;
401         short attrUse;
402         int seqno;
403         const char *wstart;
404         
405         memcpy (&attrSet, src, sizeof(attrSet));
406         src += sizeof(attrSet);
407
408         memcpy (&attrUse, src, sizeof(attrUse));
409         src += sizeof(attrUse);
410
411         wstart = src;
412         while (*src++)
413             ;
414
415         memcpy (&seqno, src, sizeof(seqno));
416         src += sizeof(seqno);
417
418 #if 0
419         logf (LOG_LOG, "(%d,%d) %d %s", attrSet, attrUse, seqno, wstart);
420 #endif
421         if (attrUseS == attrUse && attrSetS == attrSet)
422         {
423             int woff;
424
425
426             if (startSeq == -1)
427                 startSeq = seqno;
428             woff = seqno - startSeq;
429             if (woff >= 0 && woff < 31)
430                 ws[woff] = wstart;
431         }
432
433         off = src - reckeys->buf;
434     }
435     assert (off == reckeys->buf_used);
436     return ws;
437 }
438
439 static void addRecordKeyAny (const RecWord *p)
440 {
441     if (p->attrSet != 1 || p->attrUse != 1016)
442     {
443         RecWord w;
444
445         memcpy (&w, p, sizeof(w));
446         w.attrSet = 1;
447         w.attrUse = 1016;
448         addRecordKey (&w);
449     }
450     addRecordKey (p);
451 }
452
453 #define FILE_READ_BUFSIZE 4096 
454
455 static char *file_buf;
456 static int file_offset;
457 static int file_bufsize;
458 static int file_noread;
459
460 static void file_read_start (int fd)
461 {
462     file_offset = 0;
463     file_buf = xmalloc (FILE_READ_BUFSIZE);
464     file_bufsize = read (fd, file_buf, FILE_READ_BUFSIZE);
465     file_noread = 0;
466 }
467
468 static void file_read_stop (int fd)
469 {
470     xfree (file_buf);
471     file_buf = NULL;
472 }
473
474 static int file_read (int fd, char *buf, size_t count)
475 {
476     int l = file_bufsize - file_offset;
477
478     if (count > l)
479     {
480         int r;
481         if (l > 0)
482             memcpy (buf, file_buf + file_offset, l);
483         count = count-l;
484         if (count > file_bufsize)
485         {
486             if ((r = read (fd, buf + l, count)) == -1)
487             {
488                 logf (LOG_FATAL|LOG_ERRNO, "read");
489                 exit (1);
490             }
491             file_bufsize = 0;
492             file_offset = 0;
493             file_noread += r;
494             return r;
495         }
496         file_bufsize = r = read (fd, file_buf, FILE_READ_BUFSIZE);
497         if (r == -1)
498         {
499             logf (LOG_FATAL|LOG_ERRNO, "read");
500             exit (1);
501         }
502         else if (r <= count)
503         {
504             file_offset = r;
505             memcpy (buf + l, file_buf, r);
506             file_noread += (l+r);
507             return l + r;
508         }
509         else
510         {
511             file_offset = count;
512             memcpy (buf + l, file_buf, count - l);
513             file_noread += count;
514             return count;
515         }
516     }
517     memcpy (buf, file_buf + file_offset, count);
518     file_offset += count;
519     file_noread += count;
520     return count;
521 }
522
523 static int atois (const char **s)
524 {
525     int val = 0, c;
526     while ( (c=**s) >= '0' && c <= '9')
527     {
528         val = val*10 + c - '0';
529         ++(*s);
530     }
531     return val;
532 }
533
534 static char *fileMatchStr (struct recKeys *reckeys, struct recordGroup *rGroup,
535                            const char *fname,
536                            const char *spec)
537 {
538     static char dstBuf[2048];
539     char *dst = dstBuf;
540     const char *s = spec;
541     static const char **w;
542     int i;
543
544     while (1)
545     {
546         while (*s == ' ' || *s == '\t')
547             s++;
548         if (!*s)
549             break;
550         if (*s == '(')
551         {
552             char matchFlag[32];
553             int attrSet, attrUse;
554             int first = 1;
555
556             s++;
557             attrSet = atois (&s);
558             if (*s != ',')
559             {
560                 logf (LOG_WARN, "Missing , in match criteria %s in group %s",
561                       spec, rGroup->groupName ? rGroup->groupName : "none");
562                 return NULL;
563             }
564             s++;
565             attrUse = atois (&s);
566             w = searchRecordKey (reckeys, attrSet, attrUse);
567             assert (w);
568
569             if (*s == ')')
570             {
571                 for (i = 0; i<32; i++)
572                     matchFlag[i] = 1;
573             }
574             else
575             {
576                 logf (LOG_WARN, "Missing ) in match criteria %s in group %s",
577                       spec, rGroup->groupName ? rGroup->groupName : "none");
578                 return NULL;
579             }
580             s++;
581
582             for (i = 0; i<32; i++)
583                 if (matchFlag[i] && w[i])
584                 {
585                     if (first)
586                     {
587                         *dst++ = ' ';
588                         first = 0;
589                     }
590                     strcpy (dst, w[i]);
591                     dst += strlen(w[i]);
592                 }
593             if (first)
594             {
595                 logf (LOG_WARN, "Record in file %s didn't contain match"
596                       " fields in (%d,%d)", fname, attrSet, attrUse);
597                 return NULL;
598             }
599         }
600         else if (*s == '$')
601         {
602             int spec_len;
603             char special[64];
604             const char *spec_src = NULL;
605             const char *s1 = ++s;
606             while (*s1 && *s1 != ' ' && *s1 != '\t')
607                 s1++;
608
609             spec_len = s1 - s;
610             if (spec_len > 63)
611                 spec_len = 63;
612             memcpy (special, s, spec_len);
613             special[spec_len] = '\0';
614             s = s1;
615
616             if (strcmp (special, "group"))
617                 spec_src = rGroup->groupName;
618             else if (strcmp (special, "database"))
619                 spec_src = rGroup->databaseName;
620             else if (strcmp (special, "filename"))
621                 spec_src = fname;
622             else if (strcmp (special, "type"))
623                 spec_src = rGroup->recordType;
624             else 
625                 spec_src = NULL;
626             if (spec_src)
627             {
628                 strcpy (dst, spec_src);
629                 dst += strlen (spec_src);
630             }
631         }
632         else if (*s == '\"' || *s == '\'')
633         {
634             int stopMarker = *s++;
635             char tmpString[64];
636             int i = 0;
637
638             while (*s && *s != stopMarker)
639             {
640                 if (i < 63)
641                     tmpString[i++] = *s;
642             }
643             if (*s)
644                 s++;
645             tmpString[i] = '\0';
646             strcpy (dst, tmpString);
647             dst += strlen (tmpString);
648         }
649         else
650         {
651             logf (LOG_WARN, "Syntax error in match criteria %s in group %s",
652                   spec, rGroup->groupName ? rGroup->groupName : "none");
653             return NULL;
654         }
655         *dst++ = 1;
656     }
657     if (dst == dstBuf)
658     {
659         logf (LOG_WARN, "No match criteria for record %s in group %s",
660               fname, rGroup->groupName ? rGroup->groupName : "none");
661         return NULL;
662     }
663     return dstBuf;
664 }
665
666 static int recordExtract (SYSNO *sysno, const char *fname,
667                           struct recordGroup *rGroup, int deleteFlag,
668                           int fd,
669                           RecType recType)
670 {
671     struct recExtractCtrl extractCtrl;
672     int r;
673     char *matchStr;
674     SYSNO sysnotmp;
675     Record rec;
676
677     if (fd != -1)
678     {
679         extractCtrl.fd = fd;
680         /* extract keys */
681         extractCtrl.subType = "";
682         extractCtrl.init = wordInit;
683         extractCtrl.add = addRecordKeyAny;
684
685         reckeys.buf_used = 0;
686         extractCtrl.readf = file_read;
687         r = (*recType->extract)(&extractCtrl);
688   
689         if (r)      
690         {
691             logf (LOG_WARN, "Couldn't extract file %s, code %d", fname, r);
692             return 0;
693         }
694     }
695
696     /* perform match if sysno not known and if match criteria is specified */
697        
698     matchStr = NULL;
699     if (!sysno) 
700     {
701         sysnotmp = 0;
702         sysno = &sysnotmp;
703         if (rGroup->recordId && *rGroup->recordId)
704         {
705             char *rinfo;
706         
707             matchStr = fileMatchStr (&reckeys, rGroup, fname, 
708                                      rGroup->recordId);
709             if (matchStr)
710             {
711                 rinfo = dict_lookup (matchDict, matchStr);
712                 if (rinfo)
713                     memcpy (sysno, rinfo+1, sizeof(*sysno));
714             }
715             else
716             {
717                 logf (LOG_WARN, "Record not inserted");
718                 return 0;
719             }
720         }
721     }
722
723     /* new record ? */
724     if (! *sysno)
725     {
726         if (deleteFlag)
727         {
728             logf (LOG_LOG, "? record %s", fname);
729             return 1;
730         }
731         logf (LOG_LOG, "add %s record %s", rGroup->recordType, fname);
732         rec = rec_new (records);
733         *sysno = rec->sysno;
734
735         if (matchStr)
736             dict_insert (matchDict, matchStr, sizeof(*sysno), sysno);
737         flushRecordKeys (*sysno, 1, &reckeys, rGroup->databaseName);
738
739         records_inserted++;
740     }
741     else
742     {
743         struct recKeys delkeys;
744
745         rec = rec_get (records, *sysno);
746
747         delkeys.buf_used = rec->size[recInfo_delKeys];
748         delkeys.buf = rec->info[recInfo_delKeys];
749         flushRecordKeys (*sysno, 0, &delkeys, rec->info[recInfo_databaseName]);
750         if (deleteFlag)
751         {
752             if (!delkeys.buf_used)
753             {
754                 logf (LOG_WARN, "cannot delete %s - no delete keys",
755                       fname);
756             }
757             else
758                 logf (LOG_LOG, "delete %s record %s", rGroup->recordType,
759                       fname);
760             records_deleted++;
761             rec_del (records, &rec);
762             return 1;
763         }
764         else
765         {
766             if (!delkeys.buf_used)
767             {
768                 logf (LOG_WARN, "cannot update %s - no delete keys",
769                       fname);
770             }
771             else
772             {
773                 logf (LOG_LOG, "update %s record %s", rGroup->recordType,
774                       fname);
775                 flushRecordKeys (*sysno, 1, &reckeys, rGroup->databaseName); 
776                 records_updated++;
777             }
778         }
779     }
780     free (rec->info[recInfo_fileType]);
781     rec->info[recInfo_fileType] =
782         rec_strdup (rGroup->recordType, &rec->size[recInfo_fileType]);
783
784     free (rec->info[recInfo_filename]);
785     rec->info[recInfo_filename] =
786         rec_strdup (fname, &rec->size[recInfo_filename]);
787
788     free (rec->info[recInfo_delKeys]);
789     if (reckeys.buf_used > 0 && rGroup->flagStoreKeys == 1)
790     {
791         rec->info[recInfo_delKeys] = malloc (reckeys.buf_used);
792         rec->size[recInfo_delKeys] = reckeys.buf_used;
793         memcpy (rec->info[recInfo_delKeys], reckeys.buf,
794                 rec->size[recInfo_delKeys]);
795     }
796     else
797     {
798         rec->info[recInfo_delKeys] = NULL;
799         rec->size[recInfo_delKeys] = 0;
800     }
801
802     free (rec->info[recInfo_storeData]);
803     if (rGroup->flagStoreData == 1)
804     {
805         rec->size[recInfo_storeData] = file_noread;
806         rec->info[recInfo_storeData] = malloc (file_noread);
807         if (file_noread < FILE_READ_BUFSIZE)
808             memcpy (rec->info[recInfo_storeData], file_buf, file_noread);
809         else
810         {
811             if (lseek (fd, 0L, SEEK_SET) < 0)
812             {
813                 logf (LOG_ERRNO|LOG_FATAL, "seek to 0 in %s", fname);
814                 exit (1);
815             }
816             if (read (fd, rec->info[recInfo_storeData], file_noread) 
817                 < file_noread)
818             {
819                 logf (LOG_ERRNO|LOG_FATAL, "read %d bytes of %s",
820                       file_noread, fname);
821                 exit (1);
822             }
823         }
824     }
825     else
826     {
827         rec->info[recInfo_storeData] = NULL;
828         rec->size[recInfo_storeData] = 0;
829     }
830     free (rec->info[recInfo_databaseName]);
831     rec->info[recInfo_databaseName] =
832         rec_strdup (rGroup->databaseName, &rec->size[recInfo_databaseName]); 
833
834     rec_put (records, &rec);
835     return 1;
836 }
837
838 int fileExtract (SYSNO *sysno, const char *fname, 
839                  const struct recordGroup *rGroupP, int deleteFlag)
840 {
841     int i, fd;
842     char gprefix[128];
843     char ext[128];
844     char ext_res[128];
845     RecType recType;
846     struct recordGroup rGroupM;
847     struct recordGroup *rGroup = &rGroupM;
848
849     memcpy (rGroup, rGroupP, sizeof(*rGroupP));
850    
851     if (!rGroup->groupName || !*rGroup->groupName)
852         *gprefix = '\0';
853     else
854         sprintf (gprefix, "%s.", rGroup->groupName);
855
856     logf (LOG_DEBUG, "fileExtract %s", fname);
857
858     /* determine file extension */
859     for (i = strlen(fname); --i >= 0; )
860         if (fname[i] == '/')
861         {
862             strcpy (ext, "");
863             break;
864         }
865         else if (fname[i] == '.')
866         {
867             strcpy (ext, fname+i+1);
868             break;
869         }
870     /* determine file type - depending on extension */
871     if (!rGroup->recordType)
872     {
873         sprintf (ext_res, "%srecordType.%s", gprefix, ext);
874         if (!(rGroup->recordType = res_get (common_resource, ext_res)))
875         {
876             sprintf (ext_res, "%srecordType", gprefix);
877             if (!(rGroup->recordType = res_get (common_resource, ext_res)))
878             {
879                 logf (LOG_LOG, "? record %s", fname);
880                 return 0;
881             }
882         }
883     }
884     if (!rGroup->recordType)
885     {
886         logf (LOG_LOG, "? record %s", fname);
887         return 0;
888     }
889     if (!(recType = recType_byName (rGroup->recordType)))
890     {
891         logf (LOG_WARN, "No such record type: %s", rGroup->recordType);
892         return 0;
893     }
894
895     /* determine match criteria */
896     if (!rGroup->recordId)
897     {
898         sprintf (ext_res, "%srecordId.%s", gprefix, ext);
899         rGroup->recordId = res_get (common_resource, ext_res);
900     }
901
902     /* determine database name */
903     if (!rGroup->databaseName)
904     {
905         sprintf (ext_res, "%sdatabase.%s", gprefix, ext);
906         if (!(rGroup->databaseName = res_get (common_resource, ext_res)))
907         {
908             sprintf (ext_res, "%sdatabase", gprefix);
909             rGroup->databaseName = res_get (common_resource, ext_res);
910         }
911     }
912     if (!rGroup->databaseName)
913         rGroup->databaseName = "Default";
914
915     if (rGroup->flagStoreData == -1)
916     {
917         const char *sval;
918         sprintf (ext_res, "%sstoreData.%s", gprefix, ext);
919         if (!(sval = res_get (common_resource, ext_res)))
920         {
921             sprintf (ext_res, "%sstoreData", gprefix);
922             sval = res_get (common_resource, ext_res);
923         }
924         if (sval)
925             rGroup->flagStoreData = atoi (sval);
926     }
927     if (rGroup->flagStoreData == -1)
928         rGroup->flagStoreData = 0;
929
930     if (rGroup->flagStoreKeys == -1)
931     {
932         const char *sval;
933
934         sprintf (ext_res, "%sstoreKeys.%s", gprefix, ext);
935         if (!(sval = res_get (common_resource, ext_res)))
936         {
937             sprintf (ext_res, "%sstoreKeys", gprefix);
938             sval = res_get (common_resource, ext_res);
939         }
940         if (sval)
941             rGroup->flagStoreKeys = atoi (sval);
942     }
943     if (rGroup->flagStoreKeys == -1)
944         rGroup->flagStoreKeys = 0;
945
946     if (sysno && deleteFlag)
947         fd = -1;
948     else
949     {
950         if ((fd = open (fname, O_RDONLY)) == -1)
951         {
952             logf (LOG_WARN|LOG_ERRNO, "open %s", fname);
953             return 0;
954         }
955     }
956     file_read_start (fd);
957     recordExtract (sysno, fname, rGroup, deleteFlag, fd, recType);
958     file_read_stop (fd);
959     if (fd != -1)
960         close (fd);
961     return 1;
962 }
963