Register type (w,p) + set-use/string attriute combined in register
[idzebra-moved-to-github.git] / index / extract.c
1 /* $Id: extract.c,v 1.187 2005-06-23 06:45:46 adam Exp $
2    Copyright (C) 1995-2005
3    Index Data ApS
4
5 This file is part of the Zebra server.
6
7 Zebra is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
11
12 Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Zebra; see the file LICENSE.zebra.  If not, write to the
19 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20 02111-1307, USA.
21 */
22
23 #include <stdio.h>
24 #include <assert.h>
25 #include <ctype.h>
26 #ifdef WIN32
27 #include <io.h>
28 #endif
29 #if HAVE_UNISTD_H
30 #include <unistd.h>
31 #endif
32 #include <fcntl.h>
33
34 #include "index.h"
35 #include <direntz.h>
36 #include <charmap.h>
37
38 #if _FILE_OFFSET_BITS == 64
39 #define PRINTF_OFF_T "%Ld"
40 #else
41 #define PRINTF_OFF_T "%ld"
42 #endif
43
44 #define USE_SHELLSORT 0
45
46 #if USE_SHELLSORT
47 static void shellsort(void *ar, int r, size_t s,
48                       int (*cmp)(const void *a, const void *b))
49 {
50     char *a = ar;
51     char v[100];
52     int h, i, j, k;
53     static const int incs[16] = { 1391376, 463792, 198768, 86961, 33936,
54                                   13776, 4592, 1968, 861, 336, 
55                                   112, 48, 21, 7, 3, 1 };
56     for ( k = 0; k < 16; k++)
57         for (h = incs[k], i = h; i < r; i++)
58         { 
59             memcpy (v, a+s*i, s);
60             j = i;
61             while (j > h && (*cmp)(a + s*(j-h), v) > 0)
62             {
63                 memcpy (a + s*j, a + s*(j-h), s);
64                 j -= h;
65             }
66             memcpy (a+s*j, v, s);
67         } 
68 }
69 #endif
70
71 static void logRecord (ZebraHandle zh)
72 {
73     ++zh->records_processed;
74     if (!(zh->records_processed % 1000))
75     {
76         yaz_log (YLOG_LOG, "Records: "ZINT_FORMAT" i/u/d "
77                         ZINT_FORMAT"/"ZINT_FORMAT"/"ZINT_FORMAT, 
78               zh->records_processed, zh->records_inserted, zh->records_updated,
79               zh->records_deleted);
80     }
81 }
82
83 static void extract_set_store_data_prepare(struct recExtractCtrl *p);
84
85 static void extract_init (struct recExtractCtrl *p, RecWord *w)
86 {
87     w->zebra_maps = p->zebra_maps;
88     w->seqno = 1;
89 #if NATTR
90 #else
91     w->attrSet = VAL_BIB1;
92     w->attrUse = 1016;
93 #endif
94     w->index_name = 0;
95     w->index_type = 'w';
96     w->extractCtrl = p;
97     w->record_id = 0;
98     w->section_id = 0;
99 }
100
101 static const char **searchRecordKey (ZebraHandle zh,
102                                      struct recKeys *reckeys,
103                                      int attrSetS, int attrUseS)
104 {
105     static const char *ws[32];
106     void *decode_handle = iscz1_start();
107     int off = 0;
108     int startSeq = -1;
109     int seqno = 0;
110     int i;
111
112     for (i = 0; i<32; i++)
113         ws[i] = NULL;
114
115     while (off < reckeys->buf_used)
116     {
117         const char *src = reckeys->buf + off;
118         struct it_key key;
119         char *dst = (char*) &key;
120         int attrSet, attrUse;
121
122         iscz1_decode(decode_handle, &dst, &src);
123         assert(key.len <= 4 && key.len > 2);
124
125         attrSet = (int) key.mem[0] >> 16;
126         attrUse = (int) key.mem[0] & 65535;
127         seqno = (int) key.mem[key.len-1];
128
129         if (attrUseS == attrUse && attrSetS == attrSet)
130         {
131             int woff;
132
133             if (startSeq == -1)
134                 startSeq = seqno;
135             woff = seqno - startSeq;
136             if (woff >= 0 && woff < 31)
137                 ws[woff] = src;
138         }
139
140         while (*src++)
141             ;
142         off = src - reckeys->buf;
143     }
144     iscz1_stop(decode_handle);
145     assert (off == reckeys->buf_used);
146     return ws;
147 }
148
149 struct file_read_info {
150     off_t file_max;         /* maximum offset so far */
151     off_t file_offset;      /* current offset */
152     off_t file_moffset;     /* offset of rec/rec boundary */
153     int file_more;
154     int fd;
155 };
156
157 static struct file_read_info *file_read_start (int fd)
158 {
159     struct file_read_info *fi = (struct file_read_info *)
160         xmalloc (sizeof(*fi));
161
162     fi->fd = fd;
163     fi->file_max = 0;
164     fi->file_moffset = 0;
165     fi->file_offset = 0;
166     fi->file_more = 0;
167     return fi;
168 }
169
170 static void file_read_stop (struct file_read_info *fi)
171 {
172     xfree (fi);
173 }
174
175 static off_t file_seek (void *handle, off_t offset)
176 {
177     struct file_read_info *p = (struct file_read_info *) handle;
178     p->file_offset = offset;
179     return lseek (p->fd, offset, SEEK_SET);
180 }
181
182 static off_t file_tell (void *handle)
183 {
184     struct file_read_info *p = (struct file_read_info *) handle;
185     return p->file_offset;
186 }
187
188 static int file_read (void *handle, char *buf, size_t count)
189 {
190     struct file_read_info *p = (struct file_read_info *) handle;
191     int fd = p->fd;
192     int r;
193     r = read (fd, buf, count);
194     if (r > 0)
195     {
196         p->file_offset += r;
197         if (p->file_offset > p->file_max)
198             p->file_max = p->file_offset;
199     }
200     return r;
201 }
202
203 static void file_end (void *handle, off_t offset)
204 {
205     struct file_read_info *p = (struct file_read_info *) handle;
206
207     if (offset != p->file_moffset)
208     {
209         p->file_moffset = offset;
210         p->file_more = 1;
211     }
212 }
213
214 static char *fileMatchStr (ZebraHandle zh,
215                            struct recKeys *reckeys,
216                            const char *fname, const char *spec)
217 {
218     static char dstBuf[2048];      /* static here ??? */
219     char *dst = dstBuf;
220     const char *s = spec;
221     static const char **w;
222
223     while (1)
224     {
225         while (*s == ' ' || *s == '\t')
226             s++;
227         if (!*s)
228             break;
229         if (*s == '(')
230         {
231             char attset_str[64], attname_str[64];
232             data1_attset *attset;
233             int i;
234             char matchFlag[32];
235             int attSet = 1, attUse = 1;
236             int first = 1;
237
238             s++;
239             for (i = 0; *s && *s != ',' && *s != ')'; s++)
240                 if (i < 63)
241                     attset_str[i++] = *s;
242             attset_str[i] = '\0';
243
244             if (*s == ',')
245             {
246                 s++;
247                 for (i = 0; *s && *s != ')'; s++)
248                     if (i < 63)
249                         attname_str[i++] = *s;
250                 attname_str[i] = '\0';
251             }
252             
253             if ((attset = data1_get_attset (zh->reg->dh, attset_str)))
254             {
255                 data1_att *att;
256                 attSet = attset->reference;
257                 att = data1_getattbyname(zh->reg->dh, attset, attname_str);
258                 if (att)
259                     attUse = att->value;
260                 else
261                     attUse = atoi (attname_str);
262             }
263             w = searchRecordKey (zh, reckeys, attSet, attUse);
264             assert (w);
265
266             if (*s == ')')
267             {
268                 for (i = 0; i<32; i++)
269                     matchFlag[i] = 1;
270             }
271             else
272             {
273                 yaz_log (YLOG_WARN, "Missing ) in match criteria %s in group %s",
274                       spec, zh->m_group ? zh->m_group : "none");
275                 return NULL;
276             }
277             s++;
278
279             for (i = 0; i<32; i++)
280                 if (matchFlag[i] && w[i])
281                 {
282                     if (first)
283                     {
284                         *dst++ = ' ';
285                         first = 0;
286                     }
287                     strcpy (dst, w[i]);
288                     dst += strlen(w[i]);
289                 }
290             if (first)
291             {
292                 yaz_log (YLOG_WARN, "Record didn't contain match"
293                       " fields in (%s,%s)", attset_str, attname_str);
294                 return NULL;
295             }
296         }
297         else if (*s == '$')
298         {
299             int spec_len;
300             char special[64];
301             const char *spec_src = NULL;
302             const char *s1 = ++s;
303             while (*s1 && *s1 != ' ' && *s1 != '\t')
304                 s1++;
305
306             spec_len = s1 - s;
307             if (spec_len > 63)
308                 spec_len = 63;
309             memcpy (special, s, spec_len);
310             special[spec_len] = '\0';
311             s = s1;
312
313             if (!strcmp (special, "group"))
314                 spec_src = zh->m_group;
315             else if (!strcmp (special, "database"))
316                 spec_src = zh->basenames[0];
317             else if (!strcmp (special, "filename")) {
318                 spec_src = fname;
319             }
320             else if (!strcmp (special, "type"))
321                 spec_src = zh->m_record_type;
322             else 
323                 spec_src = NULL;
324             if (spec_src)
325             {
326                 strcpy (dst, spec_src);
327                 dst += strlen (spec_src);
328             }
329         }
330         else if (*s == '\"' || *s == '\'')
331         {
332             int stopMarker = *s++;
333             char tmpString[64];
334             int i = 0;
335
336             while (*s && *s != stopMarker)
337             {
338                 if (i < 63)
339                     tmpString[i++] = *s++;
340             }
341             if (*s)
342                 s++;
343             tmpString[i] = '\0';
344             strcpy (dst, tmpString);
345             dst += strlen (tmpString);
346         }
347         else
348         {
349             yaz_log (YLOG_WARN, "Syntax error in match criteria %s in group %s",
350                   spec, zh->m_group ? zh->m_group : "none");
351             return NULL;
352         }
353         *dst++ = 1;
354     }
355     if (dst == dstBuf)
356     {
357         yaz_log (YLOG_WARN, "No match criteria for record %s in group %s",
358               fname, zh->m_group ? zh->m_group : "none");
359         return NULL;
360     }
361     *dst = '\0';
362     return dstBuf;
363 }
364
365 struct recordLogInfo {
366     const char *fname;
367     int recordOffset;
368     struct recordGroup *rGroup;
369 };
370
371 void create_rec_keys_codec(struct recKeys *keys)
372 {
373     keys->buf_used = 0;
374     iscz1_reset(keys->codec_handle);
375 }
376
377 static void init_extractCtrl(ZebraHandle zh, struct recExtractCtrl *ctrl)
378 {
379     int i;
380     for (i = 0; i<256; i++)
381     {
382         if (zebra_maps_is_positioned(zh->reg->zebra_maps, i))
383             ctrl->seqno[i] = 1;
384         else
385             ctrl->seqno[i] = 0;
386     }
387     ctrl->zebra_maps = zh->reg->zebra_maps;
388     ctrl->flagShowRecords = !zh->m_flag_rw;
389 }
390
391 static int file_extract_record(ZebraHandle zh,
392                                SYSNO *sysno, const char *fname,
393                                int deleteFlag,
394                                struct file_read_info *fi,
395                                int force_update,
396                                RecType recType,
397                                void *recTypeClientData)
398 {
399     RecordAttr *recordAttr;
400     int r;
401     const char *matchStr = 0;
402     SYSNO sysnotmp;
403     Record rec;
404     off_t recordOffset = 0;
405     
406     /* announce database */
407     if (zebraExplain_curDatabase (zh->reg->zei, zh->basenames[0]))
408     {
409         if (zebraExplain_newDatabase (zh->reg->zei, zh->basenames[0],
410                                       zh->m_explain_database))
411             return 0;
412     }
413
414     if (fi->fd != -1)
415     {
416         struct recExtractCtrl extractCtrl;
417
418         /* we are going to read from a file, so prepare the extraction */
419         create_rec_keys_codec(&zh->reg->keys);
420 #if NATTR
421         create_rec_keys_codec(&zh->reg->sortKeys);
422 #else
423         zh->reg->sortKeys.buf_used = 0;
424 #endif
425         recordOffset = fi->file_moffset;
426         extractCtrl.handle = zh;
427         extractCtrl.offset = fi->file_moffset;
428         extractCtrl.readf = file_read;
429         extractCtrl.seekf = file_seek;
430         extractCtrl.tellf = file_tell;
431         extractCtrl.endf = file_end;
432         extractCtrl.fh = fi;
433         extractCtrl.init = extract_init;
434         extractCtrl.tokenAdd = extract_token_add;
435         extractCtrl.schemaAdd = extract_schema_add;
436         extractCtrl.dh = zh->reg->dh;
437         extractCtrl.match_criteria[0] = '\0';
438         extractCtrl.first_record = fi->file_offset ? 0 : 1;
439
440         extract_set_store_data_prepare(&extractCtrl);
441
442         init_extractCtrl(zh, &extractCtrl);
443
444         if (!zh->m_flag_rw)
445             printf ("File: %s " PRINTF_OFF_T "\n", fname, recordOffset);
446         if (zh->m_flag_rw)
447         {
448             char msg[512];
449             sprintf (msg, "%s:" PRINTF_OFF_T , fname, recordOffset);
450             yaz_log_init_prefix2 (msg);
451         }
452
453         r = (*recType->extract)(recTypeClientData, &extractCtrl);
454
455         yaz_log_init_prefix2 (0);
456         if (r == RECCTRL_EXTRACT_EOF)
457             return 0;
458         else if (r == RECCTRL_EXTRACT_ERROR_GENERIC)
459         {
460             /* error occured during extraction ... */
461             if (zh->m_flag_rw &&
462                 zh->records_processed < zh->m_file_verbose_limit)
463             {
464                 yaz_log (YLOG_WARN, "fail %s %s " PRINTF_OFF_T, zh->m_record_type,
465                       fname, recordOffset);
466             }
467             return 0;
468         }
469         else if (r == RECCTRL_EXTRACT_ERROR_NO_SUCH_FILTER)
470         {
471             /* error occured during extraction ... */
472             if (zh->m_flag_rw &&
473                 zh->records_processed < zh->m_file_verbose_limit)
474             {
475                 yaz_log (YLOG_WARN, "no filter for %s %s " 
476                       PRINTF_OFF_T, zh->m_record_type,
477                       fname, recordOffset);
478             }
479             return 0;
480         }
481         if (extractCtrl.match_criteria[0])
482             matchStr = extractCtrl.match_criteria;
483     }
484
485     /* perform match if sysno not known and if match criteria is specified */
486     if (!sysno) 
487     {
488         sysnotmp = 0;
489         sysno = &sysnotmp;
490
491         if (matchStr == 0 && zh->m_record_id && *zh->m_record_id)
492         {
493         
494             matchStr = fileMatchStr (zh, &zh->reg->keys, fname, 
495                                      zh->m_record_id);
496             if (!matchStr)
497             {
498                 yaz_log(YLOG_WARN, "Bad match criteria");
499                 return 0;
500             }
501         }
502         if (matchStr)
503         {
504             char *rinfo = dict_lookup (zh->reg->matchDict, matchStr);
505             if (rinfo)
506             {
507                 assert(*rinfo == sizeof(*sysno));
508                 memcpy (sysno, rinfo+1, sizeof(*sysno));
509             }
510         }
511     }
512     if (! *sysno && zh->reg->keys.buf_used == 0)
513     {
514          /* the extraction process returned no information - the record
515             is probably empty - unless flagShowRecords is in use */
516          if (!zh->m_flag_rw)
517              return 1;
518   
519          if (zh->records_processed < zh->m_file_verbose_limit)
520              yaz_log (YLOG_WARN, "empty %s %s " PRINTF_OFF_T, zh->m_record_type,
521             fname, recordOffset);
522          return 1;
523     }
524
525     if (! *sysno)
526     {
527         /* new record */
528         if (deleteFlag)
529         {
530             yaz_log (YLOG_LOG, "delete %s %s " PRINTF_OFF_T, zh->m_record_type,
531                   fname, recordOffset);
532             yaz_log (YLOG_WARN, "cannot delete record above (seems new)");
533             return 1;
534         }
535         if (zh->records_processed < zh->m_file_verbose_limit)
536             yaz_log (YLOG_LOG, "add %s %s " PRINTF_OFF_T, zh->m_record_type,
537                   fname, recordOffset);
538         rec = rec_new (zh->reg->records);
539
540         *sysno = rec->sysno;
541
542         recordAttr = rec_init_attr (zh->reg->zei, rec);
543
544         if (matchStr)
545         {
546             dict_insert (zh->reg->matchDict, matchStr, sizeof(*sysno), sysno);
547         }
548         extract_flushSortKeys (zh, *sysno, 1, &zh->reg->sortKeys);
549         extract_flushRecordKeys (zh, *sysno, 1, &zh->reg->keys);
550
551         zh->records_inserted++;
552     }
553     else
554     {
555         /* record already exists */
556         struct recKeys delkeys;
557 #if NATTR
558         struct recKeys sortKeys;
559 #else
560         struct sortKeys sortKeys;
561 #endif
562
563         rec = rec_get (zh->reg->records, *sysno);
564         assert (rec);
565         
566         recordAttr = rec_init_attr (zh->reg->zei, rec);
567
568         if (!force_update && recordAttr->runNumber ==
569             zebraExplain_runNumberIncrement (zh->reg->zei, 0))
570         {
571             yaz_log (YLOG_LOG, "run number = " ZINT_FORMAT,
572                             recordAttr->runNumber);
573             yaz_log (YLOG_LOG, "skipped %s %s " PRINTF_OFF_T,
574                      zh->m_record_type, fname, recordOffset);
575             extract_flushSortKeys (zh, *sysno, -1, &zh->reg->sortKeys);
576             rec_rm (&rec);
577             logRecord (zh);
578             return 1;
579         }
580         delkeys.buf_used = rec->size[recInfo_delKeys];
581         delkeys.buf = rec->info[recInfo_delKeys];
582
583         sortKeys.buf_used = rec->size[recInfo_sortKeys];
584         sortKeys.buf = rec->info[recInfo_sortKeys];
585
586         extract_flushSortKeys (zh, *sysno, 0, &sortKeys);
587         extract_flushRecordKeys (zh, *sysno, 0, &delkeys);
588         if (deleteFlag)
589         {
590             /* record going to be deleted */
591             if (!delkeys.buf_used)
592             {
593                 yaz_log (YLOG_LOG, "delete %s %s " PRINTF_OFF_T,
594                       zh->m_record_type, fname, recordOffset);
595                 yaz_log (YLOG_WARN, "cannot delete file above, storeKeys false");
596             }
597             else
598             {
599                 if (zh->records_processed < zh->m_file_verbose_limit)
600                     yaz_log (YLOG_LOG, "delete %s %s " PRINTF_OFF_T,
601                          zh->m_record_type, fname, recordOffset);
602                 zh->records_deleted++;
603                 if (matchStr)
604                     dict_delete (zh->reg->matchDict, matchStr);
605                 rec_del (zh->reg->records, &rec);
606             }
607             rec_rm (&rec);
608             logRecord (zh);
609             return 1;
610         }
611         else
612         {
613             /* record going to be updated */
614             if (!delkeys.buf_used)
615             {
616                 yaz_log (YLOG_LOG, "update %s %s " PRINTF_OFF_T,
617                       zh->m_record_type, fname, recordOffset);
618                 yaz_log (YLOG_WARN, "cannot update file above, storeKeys false");
619             }
620             else
621             {
622                 if (zh->records_processed < zh->m_file_verbose_limit)
623                     yaz_log (YLOG_LOG, "update %s %s " PRINTF_OFF_T,
624                         zh->m_record_type, fname, recordOffset);
625                 extract_flushSortKeys (zh, *sysno, 1, &zh->reg->sortKeys);
626                 extract_flushRecordKeys (zh, *sysno, 1, &zh->reg->keys);
627                 zh->records_updated++;
628             }
629         }
630     }
631     /* update file type */
632     xfree (rec->info[recInfo_fileType]);
633     rec->info[recInfo_fileType] =
634         rec_strdup (zh->m_record_type, &rec->size[recInfo_fileType]);
635
636     /* update filename */
637     xfree (rec->info[recInfo_filename]);
638     rec->info[recInfo_filename] =
639         rec_strdup (fname, &rec->size[recInfo_filename]);
640
641     /* update delete keys */
642     xfree (rec->info[recInfo_delKeys]);
643     if (zh->reg->keys.buf_used > 0 && zh->m_store_keys == 1)
644     {
645         rec->size[recInfo_delKeys] = zh->reg->keys.buf_used;
646         rec->info[recInfo_delKeys] = zh->reg->keys.buf;
647         zh->reg->keys.buf = NULL;
648         zh->reg->keys.buf_max = 0;
649     }
650     else
651     {
652         rec->info[recInfo_delKeys] = NULL;
653         rec->size[recInfo_delKeys] = 0;
654     }
655
656     /* update sort keys */
657     xfree (rec->info[recInfo_sortKeys]);
658
659     rec->size[recInfo_sortKeys] = zh->reg->sortKeys.buf_used;
660     rec->info[recInfo_sortKeys] = zh->reg->sortKeys.buf;
661     zh->reg->sortKeys.buf = NULL;
662     zh->reg->sortKeys.buf_max = 0;
663
664     /* save file size of original record */
665     zebraExplain_recordBytesIncrement (zh->reg->zei,
666                                        - recordAttr->recordSize);
667     recordAttr->recordSize = fi->file_moffset - recordOffset;
668     if (!recordAttr->recordSize)
669         recordAttr->recordSize = fi->file_max - recordOffset;
670     zebraExplain_recordBytesIncrement (zh->reg->zei,
671                                        recordAttr->recordSize);
672
673     /* set run-number for this record */
674     recordAttr->runNumber = zebraExplain_runNumberIncrement (zh->reg->zei,
675                                                              0);
676
677     /* update store data */
678     xfree (rec->info[recInfo_storeData]);
679     if (zh->store_data_buf)
680     {
681         rec->size[recInfo_storeData] = zh->store_data_size;
682         rec->info[recInfo_storeData] = zh->store_data_buf;
683         zh->store_data_buf = 0;
684     }
685     else if (zh->m_store_data)
686     {
687         rec->size[recInfo_storeData] = recordAttr->recordSize;
688         rec->info[recInfo_storeData] = (char *)
689             xmalloc (recordAttr->recordSize);
690         if (lseek (fi->fd, recordOffset, SEEK_SET) < 0)
691         {
692             yaz_log (YLOG_ERRNO|YLOG_FATAL, "seek to " PRINTF_OFF_T " in %s",
693                   recordOffset, fname);
694             exit (1);
695         }
696         if (read (fi->fd, rec->info[recInfo_storeData], recordAttr->recordSize)
697             < recordAttr->recordSize)
698         {
699             yaz_log (YLOG_ERRNO|YLOG_FATAL, "read %d bytes of %s",
700                   recordAttr->recordSize, fname);
701             exit (1);
702         }
703     }
704     else
705     {
706         rec->info[recInfo_storeData] = NULL;
707         rec->size[recInfo_storeData] = 0;
708     }
709     /* update database name */
710     xfree (rec->info[recInfo_databaseName]);
711     rec->info[recInfo_databaseName] =
712         rec_strdup (zh->basenames[0], &rec->size[recInfo_databaseName]); 
713
714     /* update offset */
715     recordAttr->recordOffset = recordOffset;
716     
717     /* commit this record */
718     rec_put (zh->reg->records, &rec);
719     logRecord (zh);
720     return 1;
721 }
722
723 int fileExtract (ZebraHandle zh, SYSNO *sysno, const char *fname, 
724                  int deleteFlag)
725 {
726     int r, i, fd;
727     char gprefix[128];
728     char ext[128];
729     char ext_res[128];
730     struct file_read_info *fi;
731     const char *original_record_type = 0;
732     RecType recType;
733     void *recTypeClientData;
734
735     if (!zh->m_group || !*zh->m_group)
736         *gprefix = '\0';
737     else
738         sprintf (gprefix, "%s.", zh->m_group);
739     
740     yaz_log (YLOG_DEBUG, "fileExtract %s", fname);
741
742     /* determine file extension */
743     *ext = '\0';
744     for (i = strlen(fname); --i >= 0; )
745         if (fname[i] == '/')
746             break;
747         else if (fname[i] == '.')
748         {
749             strcpy (ext, fname+i+1);
750             break;
751         }
752     /* determine file type - depending on extension */
753     original_record_type = zh->m_record_type;
754     if (!zh->m_record_type)
755     {
756         sprintf (ext_res, "%srecordType.%s", gprefix, ext);
757         zh->m_record_type = res_get (zh->res, ext_res);
758     }
759     if (!zh->m_record_type)
760     {
761         if (zh->records_processed < zh->m_file_verbose_limit)
762             yaz_log (YLOG_LOG, "? %s", fname);
763         return 0;
764     }
765     /* determine match criteria */
766     if (!zh->m_record_id)
767     {
768         sprintf (ext_res, "%srecordId.%s", gprefix, ext);
769         zh->m_record_id = res_get (zh->res, ext_res);
770     }
771
772     if (!(recType =
773           recType_byName (zh->reg->recTypes, zh->res, zh->m_record_type,
774                           &recTypeClientData)))
775     {
776         yaz_log(YLOG_WARN, "No such record type: %s", zh->m_record_type);
777         return 0;
778     }
779
780     switch(recType->version)
781     {
782     case 0:
783         break;
784     default:
785         yaz_log(YLOG_WARN, "Bad filter version: %s", zh->m_record_type);
786     }
787     if (sysno && deleteFlag)
788         fd = -1;
789     else
790     {
791         char full_rep[1024];
792
793         if (zh->path_reg && !yaz_is_abspath (fname))
794         {
795             strcpy (full_rep, zh->path_reg);
796             strcat (full_rep, "/");
797             strcat (full_rep, fname);
798         }
799         else
800             strcpy (full_rep, fname);
801         
802
803         if ((fd = open (full_rep, O_BINARY|O_RDONLY)) == -1)
804         {
805             yaz_log (YLOG_WARN|YLOG_ERRNO, "open %s", full_rep);
806             zh->m_record_type = original_record_type;
807             return 0;
808         }
809     }
810     fi = file_read_start (fd);
811     do
812     {
813         fi->file_moffset = fi->file_offset;
814         fi->file_more = 0;  /* file_end not called (yet) */
815         r = file_extract_record (zh, sysno, fname, deleteFlag, fi, 1,
816                                  recType, recTypeClientData);
817         if (fi->file_more)
818         {   /* file_end has been called so reset offset .. */
819             fi->file_offset = fi->file_moffset;
820             lseek(fi->fd, fi->file_moffset, SEEK_SET);
821         }
822     }
823     while (r && !sysno);
824     file_read_stop (fi);
825     if (fd != -1)
826         close (fd);
827     zh->m_record_type = original_record_type;
828     return r;
829 }
830
831 /*
832   If sysno is provided, then it's used to identify the reocord.
833   If not, and match_criteria is provided, then sysno is guessed
834   If not, and a record is provided, then sysno is got from there
835   
836  */
837 ZEBRA_RES buffer_extract_record (ZebraHandle zh, 
838                                  const char *buf, size_t buf_size,
839                                  int delete_flag,
840                                  int test_mode, 
841                                  const char *recordType,
842                                  SYSNO *sysno,
843                                  const char *match_criteria,
844                                  const char *fname,
845                                  int force_update,
846                                  int allow_update)
847 {
848     RecordAttr *recordAttr;
849     struct recExtractCtrl extractCtrl;
850     int r;
851     const char *matchStr = 0;
852     RecType recType = NULL;
853     void *clientData;
854     Record rec;
855     long recordOffset = 0;
856     struct zebra_fetch_control fc;
857     const char *pr_fname = fname;  /* filename to print .. */
858     int show_progress = zh->records_processed < zh->m_file_verbose_limit ? 1:0;
859
860     if (!pr_fname)
861         pr_fname = "<no file>";  /* make it printable if file is omitted */
862
863     fc.fd = -1;
864     fc.record_int_buf = buf;
865     fc.record_int_len = buf_size;
866     fc.record_int_pos = 0;
867     fc.offset_end = 0;
868     fc.record_offset = 0;
869
870     extractCtrl.offset = 0;
871     extractCtrl.readf = zebra_record_int_read;
872     extractCtrl.seekf = zebra_record_int_seek;
873     extractCtrl.tellf = zebra_record_int_tell;
874     extractCtrl.endf = zebra_record_int_end;
875     extractCtrl.first_record = 1;
876     extractCtrl.fh = &fc;
877
878     create_rec_keys_codec(&zh->reg->keys);
879 #if NATTR
880     create_rec_keys_codec(&zh->reg->sortKeys);
881 #else
882     zh->reg->sortKeys.buf_used = 0;
883 #endif
884     if (zebraExplain_curDatabase (zh->reg->zei, zh->basenames[0]))
885     {
886         if (zebraExplain_newDatabase (zh->reg->zei, zh->basenames[0], 
887                                       zh->m_explain_database))
888             return ZEBRA_FAIL;
889     }
890     
891     if (recordType && *recordType)
892     {
893         yaz_log (YLOG_DEBUG, "Record type explicitly specified: %s", recordType);
894         recType = recType_byName (zh->reg->recTypes, zh->res, recordType,
895                                   &clientData);
896     } 
897     else
898     {
899         if (!(zh->m_record_type))
900         {
901             yaz_log (YLOG_WARN, "No such record type defined");
902             return ZEBRA_FAIL;
903         }
904         yaz_log (YLOG_DEBUG, "Get record type from rgroup: %s",zh->m_record_type);
905         recType = recType_byName (zh->reg->recTypes, zh->res,
906                                   zh->m_record_type, &clientData);
907         recordType = zh->m_record_type;
908     }
909     
910     if (!recType)
911     {
912         yaz_log (YLOG_WARN, "No such record type: %s", zh->m_record_type);
913         return ZEBRA_FAIL;
914     }
915     
916     extractCtrl.init = extract_init;
917     extractCtrl.tokenAdd = extract_token_add;
918     extractCtrl.schemaAdd = extract_schema_add;
919     extractCtrl.dh = zh->reg->dh;
920     extractCtrl.handle = zh;
921     extractCtrl.match_criteria[0] = '\0';
922     
923     init_extractCtrl(zh, &extractCtrl);
924
925     extract_set_store_data_prepare(&extractCtrl);
926
927     r = (*recType->extract)(clientData, &extractCtrl);
928
929     if (r == RECCTRL_EXTRACT_EOF)
930         return ZEBRA_FAIL;
931     else if (r == RECCTRL_EXTRACT_ERROR_GENERIC)
932     {
933         /* error occured during extraction ... */
934         yaz_log (YLOG_WARN, "extract error: generic");
935         return ZEBRA_FAIL;
936     }
937     else if (r == RECCTRL_EXTRACT_ERROR_NO_SUCH_FILTER)
938     {
939         /* error occured during extraction ... */
940         yaz_log (YLOG_WARN, "extract error: no such filter");
941         return ZEBRA_FAIL;
942     }
943     /* match criteria */
944     matchStr = NULL;
945
946     if (extractCtrl.match_criteria[0])
947         match_criteria = extractCtrl.match_criteria;
948
949     if (! *sysno) {
950         char *rinfo;
951         if (match_criteria && *match_criteria) {
952             matchStr = match_criteria;
953         } else {
954             if (zh->m_record_id && *zh->m_record_id) {
955                 matchStr = fileMatchStr (zh, &zh->reg->keys, pr_fname, 
956                                          zh->m_record_id);
957                 if (!matchStr)
958                 {
959                     yaz_log (YLOG_WARN, "Bad match criteria (recordID)");
960                     return ZEBRA_FAIL;
961                 }
962             }
963         }
964         if (matchStr) {
965             rinfo = dict_lookup (zh->reg->matchDict, matchStr);
966             if (rinfo)
967             {
968                 assert(*rinfo == sizeof(*sysno));
969                 memcpy (sysno, rinfo+1, sizeof(*sysno));
970             }
971         }
972     }
973     if (zh->reg->keys.buf_used == 0)
974     {
975         /* the extraction process returned no information - the record
976            is probably empty - unless flagShowRecords is in use */
977         if (test_mode)
978             return ZEBRA_OK;
979     }
980
981     if (! *sysno)
982     {
983         /* new record */
984         if (delete_flag)
985         {
986             if (show_progress)
987                 yaz_log (YLOG_LOG, "delete %s %s %ld", recordType,
988                          pr_fname, (long) recordOffset);
989             yaz_log (YLOG_WARN, "cannot delete record above (seems new)");
990             return ZEBRA_FAIL;
991         }
992         if (show_progress)
993             yaz_log (YLOG_LOG, "add %s %s %ld", recordType, pr_fname,
994                      (long) recordOffset);
995         rec = rec_new (zh->reg->records);
996
997         *sysno = rec->sysno;
998
999         recordAttr = rec_init_attr (zh->reg->zei, rec);
1000
1001         if (matchStr)
1002         {
1003             dict_insert (zh->reg->matchDict, matchStr,
1004                          sizeof(*sysno), sysno);
1005         }
1006         extract_flushSortKeys (zh, *sysno, 1, &zh->reg->sortKeys);
1007         extract_flushRecordKeys (zh, *sysno, 1, &zh->reg->keys);
1008
1009         zh->records_inserted++;
1010     } 
1011     else
1012     {
1013         /* record already exists */
1014         struct recKeys delkeys;
1015 #if NATTR
1016         struct recKeys sortKeys;
1017 #else
1018         struct sortKeys sortKeys;
1019 #endif
1020
1021         if (!allow_update)
1022         {
1023             if (show_progress)
1024                 yaz_log (YLOG_LOG, "skipped %s %s %ld", 
1025                          recordType, pr_fname, (long) recordOffset);
1026             logRecord(zh);
1027             return ZEBRA_FAIL;
1028         }
1029
1030         rec = rec_get (zh->reg->records, *sysno);
1031         assert (rec);
1032         
1033         recordAttr = rec_init_attr (zh->reg->zei, rec);
1034         
1035         if (!force_update) {
1036             if (recordAttr->runNumber ==
1037                 zebraExplain_runNumberIncrement (zh->reg->zei, 0))
1038             {
1039                 if (show_progress)
1040                     yaz_log (YLOG_LOG, "skipped %s %s %ld", recordType,
1041                              pr_fname, (long) recordOffset);
1042                 extract_flushSortKeys (zh, *sysno, -1, &zh->reg->sortKeys);
1043                 rec_rm (&rec);
1044                 logRecord(zh);
1045                 return ZEBRA_FAIL;
1046             }
1047         }
1048
1049         delkeys.buf_used = rec->size[recInfo_delKeys];
1050         delkeys.buf = rec->info[recInfo_delKeys];
1051
1052         sortKeys.buf_used = rec->size[recInfo_sortKeys];
1053         sortKeys.buf = rec->info[recInfo_sortKeys];
1054
1055         extract_flushSortKeys (zh, *sysno, 0, &sortKeys);
1056         extract_flushRecordKeys (zh, *sysno, 0, &delkeys);
1057         if (delete_flag)
1058         {
1059             /* record going to be deleted */
1060             if (!delkeys.buf_used)
1061             {
1062                 if (show_progress)
1063                 {
1064                     yaz_log (YLOG_LOG, "delete %s %s %ld", recordType,
1065                              pr_fname, (long) recordOffset);
1066                     yaz_log (YLOG_WARN, "cannot delete file above, "
1067                              "storeKeys false");
1068                 }
1069             }
1070             else
1071             {
1072                 if (show_progress)
1073                     yaz_log (YLOG_LOG, "delete %s %s %ld", recordType,
1074                              pr_fname, (long) recordOffset);
1075                 zh->records_deleted++;
1076                 if (matchStr)
1077                     dict_delete (zh->reg->matchDict, matchStr);
1078                 rec_del (zh->reg->records, &rec);
1079             }
1080             rec_rm (&rec);
1081             logRecord(zh);
1082             return ZEBRA_OK;
1083         }
1084         else
1085         {
1086             /* record going to be updated */
1087             if (!delkeys.buf_used)
1088             {
1089                 if (show_progress)
1090                 {
1091                     yaz_log (YLOG_LOG, "update %s %s %ld", recordType,
1092                              pr_fname, (long) recordOffset);
1093                     yaz_log (YLOG_WARN, "cannot update file above, storeKeys false");
1094                 }
1095             }
1096             else
1097             {
1098                 if (show_progress)
1099                     yaz_log (YLOG_LOG, "update %s %s %ld", recordType,
1100                              pr_fname, (long) recordOffset);
1101                 extract_flushSortKeys (zh, *sysno, 1, &zh->reg->sortKeys);
1102                 extract_flushRecordKeys (zh, *sysno, 1, &zh->reg->keys);
1103                 zh->records_updated++;
1104             }
1105         }
1106     }
1107     /* update file type */
1108     xfree (rec->info[recInfo_fileType]);
1109     rec->info[recInfo_fileType] =
1110         rec_strdup (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 (zh->reg->keys.buf_used > 0 && zh->m_store_keys == 1)
1120     {
1121         rec->size[recInfo_delKeys] = zh->reg->keys.buf_used;
1122         rec->info[recInfo_delKeys] = zh->reg->keys.buf;
1123         zh->reg->keys.buf = NULL;
1124         zh->reg->keys.buf_max = 0;
1125     }
1126     else
1127     {
1128         rec->info[recInfo_delKeys] = NULL;
1129         rec->size[recInfo_delKeys] = 0;
1130     }
1131
1132     /* update sort keys */
1133     xfree (rec->info[recInfo_sortKeys]);
1134
1135     rec->size[recInfo_sortKeys] = zh->reg->sortKeys.buf_used;
1136     rec->info[recInfo_sortKeys] = zh->reg->sortKeys.buf;
1137     zh->reg->sortKeys.buf = NULL;
1138     zh->reg->sortKeys.buf_max = 0;
1139
1140     /* save file size of original record */
1141     zebraExplain_recordBytesIncrement (zh->reg->zei,
1142                                        - recordAttr->recordSize);
1143 #if 0
1144     recordAttr->recordSize = fi->file_moffset - recordOffset;
1145     if (!recordAttr->recordSize)
1146         recordAttr->recordSize = fi->file_max - recordOffset;
1147 #else
1148     recordAttr->recordSize = buf_size;
1149 #endif
1150     zebraExplain_recordBytesIncrement (zh->reg->zei,
1151                                        recordAttr->recordSize);
1152
1153     /* set run-number for this record */
1154     recordAttr->runNumber =
1155         zebraExplain_runNumberIncrement (zh->reg->zei, 0);
1156
1157     /* update store data */
1158     xfree (rec->info[recInfo_storeData]);
1159
1160     /* update store data */
1161     if (zh->store_data_buf)
1162     {
1163         rec->size[recInfo_storeData] = zh->store_data_size;
1164         rec->info[recInfo_storeData] = zh->store_data_buf;
1165         zh->store_data_buf = 0;
1166     }
1167     else if (zh->m_store_data)
1168     {
1169         rec->size[recInfo_storeData] = recordAttr->recordSize;
1170         rec->info[recInfo_storeData] = (char *)
1171             xmalloc (recordAttr->recordSize);
1172         memcpy (rec->info[recInfo_storeData], buf, recordAttr->recordSize);
1173     }
1174     else
1175     {
1176         rec->info[recInfo_storeData] = NULL;
1177         rec->size[recInfo_storeData] = 0;
1178     }
1179     /* update database name */
1180     xfree (rec->info[recInfo_databaseName]);
1181     rec->info[recInfo_databaseName] =
1182         rec_strdup (zh->basenames[0], &rec->size[recInfo_databaseName]); 
1183
1184     /* update offset */
1185     recordAttr->recordOffset = recordOffset;
1186     
1187     /* commit this record */
1188     rec_put (zh->reg->records, &rec);
1189     logRecord(zh);
1190     return ZEBRA_OK;
1191 }
1192
1193 int explain_extract (void *handle, Record rec, data1_node *n)
1194 {
1195     ZebraHandle zh = (ZebraHandle) handle;
1196     struct recExtractCtrl extractCtrl;
1197
1198     if (zebraExplain_curDatabase (zh->reg->zei,
1199                                   rec->info[recInfo_databaseName]))
1200     {
1201         abort();
1202         if (zebraExplain_newDatabase (zh->reg->zei,
1203                                       rec->info[recInfo_databaseName], 0))
1204             abort ();
1205     }
1206
1207     create_rec_keys_codec(&zh->reg->keys);
1208 #if NATTR
1209     create_rec_keys_codec(&zh->reg->sortKeys);
1210 #else
1211     zh->reg->sortKeys.buf_used = 0;
1212 #endif
1213     extractCtrl.init = extract_init;
1214     extractCtrl.tokenAdd = extract_token_add;
1215     extractCtrl.schemaAdd = extract_schema_add;
1216     extractCtrl.dh = zh->reg->dh;
1217
1218     init_extractCtrl(zh, &extractCtrl);
1219
1220     extractCtrl.flagShowRecords = 0;
1221     extractCtrl.match_criteria[0] = '\0';
1222     extractCtrl.handle = handle;
1223     extractCtrl.first_record = 1;
1224     
1225     extract_set_store_data_prepare(&extractCtrl);
1226
1227     if (n)
1228         grs_extract_tree(&extractCtrl, n);
1229
1230     if (rec->size[recInfo_delKeys])
1231     {
1232         struct recKeys delkeys;
1233 #if NATTR
1234         struct recKeys sortkeys;
1235 #else
1236         struct sortKeys sortkeys;
1237 #endif
1238
1239         delkeys.buf_used = rec->size[recInfo_delKeys];
1240         delkeys.buf = rec->info[recInfo_delKeys];
1241
1242         sortkeys.buf_used = rec->size[recInfo_sortKeys];
1243         sortkeys.buf = rec->info[recInfo_sortKeys];
1244
1245         extract_flushSortKeys (zh, rec->sysno, 0, &sortkeys);
1246         extract_flushRecordKeys (zh, rec->sysno, 0, &delkeys);
1247     }
1248     extract_flushRecordKeys (zh, rec->sysno, 1, &zh->reg->keys);
1249     extract_flushSortKeys (zh, rec->sysno, 1, &zh->reg->sortKeys);
1250
1251     xfree (rec->info[recInfo_delKeys]);
1252     rec->size[recInfo_delKeys] = zh->reg->keys.buf_used;
1253     rec->info[recInfo_delKeys] = zh->reg->keys.buf;
1254     zh->reg->keys.buf = NULL;
1255     zh->reg->keys.buf_max = 0;
1256
1257     xfree (rec->info[recInfo_sortKeys]);
1258     rec->size[recInfo_sortKeys] = zh->reg->sortKeys.buf_used;
1259     rec->info[recInfo_sortKeys] = zh->reg->sortKeys.buf;
1260     zh->reg->sortKeys.buf = NULL;
1261     zh->reg->sortKeys.buf_max = 0;
1262
1263     return 0;
1264 }
1265
1266 void extract_flushRecordKeys (ZebraHandle zh, SYSNO sysno,
1267                               int cmd, struct recKeys *reckeys)
1268 {
1269     void *decode_handle = iscz1_start();
1270     int off = 0;
1271     int ch = 0;
1272     ZebraExplainInfo zei = zh->reg->zei;
1273
1274     if (!zh->reg->key_buf)
1275     {
1276         int mem= 1024*1024* atoi( res_get_def( zh->res, "memmax", "8"));
1277         if (mem <= 0)
1278         {
1279             yaz_log(YLOG_WARN, "Invalid memory setting, using default 8 MB");
1280             mem= 1024*1024*8;
1281         }
1282         /* FIXME: That "8" should be in a default settings include */
1283         /* not hard-coded here! -H */
1284         zh->reg->key_buf = (char**) xmalloc (mem);
1285         zh->reg->ptr_top = mem/sizeof(char*);
1286         zh->reg->ptr_i = 0;
1287         zh->reg->key_buf_used = 0;
1288         zh->reg->key_file_no = 0;
1289     }
1290     zebraExplain_recordCountIncrement (zei, cmd ? 1 : -1);
1291
1292     while (off < reckeys->buf_used)
1293     {
1294         const char *src = reckeys->buf + off;
1295         struct it_key key;
1296         char *dst = (char*) &key;
1297
1298         iscz1_decode(decode_handle, &dst, &src);
1299         assert(key.len == 4);
1300
1301         if (zh->reg->key_buf_used + 1024 > 
1302             (zh->reg->ptr_top -zh->reg->ptr_i)*sizeof(char*))
1303             extract_flushWriteKeys (zh,0);
1304         ++(zh->reg->ptr_i);
1305         assert(zh->reg->ptr_i > 0);
1306         (zh->reg->key_buf)[zh->reg->ptr_top - zh->reg->ptr_i] =
1307             (char*)zh->reg->key_buf + zh->reg->key_buf_used;
1308
1309         ch = (int) key.mem[0];  /* ordinal for field/use/attribute */
1310
1311         zh->reg->key_buf_used +=
1312             key_SU_encode (ch,((char*)zh->reg->key_buf) +
1313                            zh->reg->key_buf_used);
1314         while (*src)
1315             ((char*)zh->reg->key_buf) [(zh->reg->key_buf_used)++] = *src++;
1316         src++;
1317         ((char*)(zh->reg->key_buf))[(zh->reg->key_buf_used)++] = '\0';
1318         ((char*)(zh->reg->key_buf))[(zh->reg->key_buf_used)++] = cmd;
1319
1320         key.len = 3;
1321         if (key.mem[1]) /* filter specified record ID */
1322             key.mem[0] = key.mem[1];
1323         else
1324             key.mem[0] = sysno;
1325         key.mem[1] = key.mem[2];  /* section_id */
1326         key.mem[2] = key.mem[3];  /* sequence .. */
1327
1328         memcpy ((char*)zh->reg->key_buf + zh->reg->key_buf_used,
1329                 &key, sizeof(key));
1330         (zh->reg->key_buf_used) += sizeof(key);
1331         off = src - reckeys->buf;
1332     }
1333     assert (off == reckeys->buf_used);
1334     iscz1_stop(decode_handle);
1335 }
1336
1337 void extract_flushWriteKeys (ZebraHandle zh, int final)
1338         /* optimizing: if final=1, and no files written yet */
1339         /* push the keys directly to merge, sidestepping the */
1340         /* temp file altogether. Speeds small updates */
1341 {
1342     FILE *outf;
1343     char out_fname[200];
1344     char *prevcp, *cp;
1345     struct encode_info encode_info;
1346     int ptr_i = zh->reg->ptr_i;
1347     int temp_policy;
1348 #if SORT_EXTRA
1349     int i;
1350 #endif
1351     if (!zh->reg->key_buf || ptr_i <= 0)
1352     {
1353         yaz_log (YLOG_DEBUG, "  nothing to flush section=%d buf=%p i=%d",
1354                zh->reg->key_file_no, zh->reg->key_buf, ptr_i);
1355         yaz_log (YLOG_DEBUG, "  buf=%p ",
1356                zh->reg->key_buf);
1357         yaz_log (YLOG_DEBUG, "  ptr=%d ",zh->reg->ptr_i);
1358         yaz_log (YLOG_DEBUG, "  reg=%p ",zh->reg);
1359                
1360         return;
1361     }
1362
1363     (zh->reg->key_file_no)++;
1364     yaz_log (YLOG_LOG, "sorting section %d", (zh->reg->key_file_no));
1365     yaz_log (YLOG_DEBUG, "  sort_buff at %p n=%d",
1366                     zh->reg->key_buf + zh->reg->ptr_top - ptr_i,ptr_i);
1367 #if !SORT_EXTRA
1368     qsort (zh->reg->key_buf + zh->reg->ptr_top - ptr_i, ptr_i,
1369                sizeof(char*), key_qsort_compare);
1370
1371     /* zebra.cfg: tempfiles:  
1372        Y: always use temp files (old way) 
1373        A: use temp files, if more than one (auto) 
1374           = if this is both the last and the first 
1375        N: never bother with temp files (new) */
1376
1377     temp_policy=toupper(res_get_def(zh->res,"tempfiles","auto")[0]);
1378     if (temp_policy != 'Y' && temp_policy != 'N' && temp_policy != 'A') {
1379         yaz_log (YLOG_WARN, "Illegal tempfiles setting '%c'. using 'Auto' ", 
1380                         temp_policy);
1381         temp_policy='A';
1382     }
1383
1384     if (   ( temp_policy =='N' )   ||     /* always from memory */
1385          ( ( temp_policy =='A' ) &&       /* automatic */
1386              (zh->reg->key_file_no == 1) &&  /* this is first time */
1387              (final) ) )                     /* and last (=only) time */
1388     { /* go directly from memory */
1389         zh->reg->key_file_no =0; /* signal not to read files */
1390         zebra_index_merge(zh); 
1391         zh->reg->ptr_i = 0;
1392         zh->reg->key_buf_used = 0; 
1393         return; 
1394     }
1395
1396     /* Not doing directly from memory, write into a temp file */
1397     extract_get_fname_tmp (zh, out_fname, zh->reg->key_file_no);
1398
1399     if (!(outf = fopen (out_fname, "wb")))
1400     {
1401         yaz_log (YLOG_FATAL|YLOG_ERRNO, "fopen %s", out_fname);
1402         exit (1);
1403     }
1404     yaz_log (YLOG_LOG, "writing section %d", zh->reg->key_file_no);
1405     prevcp = cp = (zh->reg->key_buf)[zh->reg->ptr_top - ptr_i];
1406     
1407     encode_key_init (&encode_info);
1408     encode_key_write (cp, &encode_info, outf);
1409     
1410     while (--ptr_i > 0)
1411     {
1412         cp = (zh->reg->key_buf)[zh->reg->ptr_top - ptr_i];
1413         if (strcmp (cp, prevcp))
1414         {
1415             encode_key_flush ( &encode_info, outf);
1416             encode_key_init (&encode_info);
1417             encode_key_write (cp, &encode_info, outf);
1418             prevcp = cp;
1419         }
1420         else
1421             encode_key_write (cp + strlen(cp), &encode_info, outf);
1422     }
1423     encode_key_flush ( &encode_info, outf);
1424 #else
1425     qsort (key_buf + ptr_top-ptr_i, ptr_i, sizeof(char*), key_x_compare);
1426     extract_get_fname_tmp (out_fname, key_file_no);
1427
1428     if (!(outf = fopen (out_fname, "wb")))
1429     {
1430         yaz_log (YLOG_FATAL|YLOG_ERRNO, "fopen %s", out_fname);
1431         exit (1);
1432     }
1433     yaz_log (YLOG_LOG, "writing section %d", key_file_no);
1434     i = ptr_i;
1435     prevcp =  key_buf[ptr_top-i];
1436     while (1)
1437         if (!--i || strcmp (prevcp, key_buf[ptr_top-i]))
1438         {
1439             key_y_len = strlen(prevcp)+1;
1440 #if 0
1441             yaz_log (YLOG_LOG, "key_y_len: %2d %02x %02x %s",
1442                       key_y_len, prevcp[0], prevcp[1], 2+prevcp);
1443 #endif
1444             qsort (key_buf + ptr_top-ptr_i, ptr_i - i,
1445                                    sizeof(char*), key_y_compare);
1446             cp = key_buf[ptr_top-ptr_i];
1447             --key_y_len;
1448             encode_key_init (&encode_info);
1449             encode_key_write (cp, &encode_info, outf);
1450             while (--ptr_i > i)
1451             {
1452                 cp = key_buf[ptr_top-ptr_i];
1453                 encode_key_write (cp+key_y_len, &encode_info, outf);
1454             }
1455             encode_key_flush ( &encode_info, outf);
1456             if (!i)
1457                 break;
1458             prevcp = key_buf[ptr_top-ptr_i];
1459         }
1460 #endif
1461     if (fclose (outf))
1462     {
1463         yaz_log (YLOG_FATAL|YLOG_ERRNO, "fclose %s", out_fname);
1464         exit (1);
1465     }
1466     yaz_log (YLOG_LOG, "finished section %d", zh->reg->key_file_no);
1467     zh->reg->ptr_i = 0;
1468     zh->reg->key_buf_used = 0;
1469 }
1470
1471 void extract_add_it_key (ZebraHandle zh,
1472                          struct recKeys *keys,
1473                          int reg_type,
1474                          const char *str, int slen, struct it_key *key)
1475 {
1476     char *dst;
1477     const char *src = (char*) key;
1478     
1479     if (keys->buf_used+1024 > keys->buf_max)
1480     {
1481         char *b = (char *) xmalloc (keys->buf_max += 128000);
1482         if (keys->buf_used > 0)
1483             memcpy (b, keys->buf, keys->buf_used);
1484         xfree (keys->buf);
1485         keys->buf = b;
1486     }
1487     dst = keys->buf + keys->buf_used;
1488
1489     iscz1_encode(keys->codec_handle, &dst, &src);
1490
1491 #if REG_TYPE_PREFIX
1492     *dst++ = reg_type;
1493 #endif
1494     memcpy (dst, str, slen);
1495     dst += slen;
1496     *dst++ = '\0';
1497     keys->buf_used = dst - keys->buf;
1498 }
1499
1500 ZEBRA_RES zebra_snippets_rec_keys(ZebraHandle zh, struct recKeys *reckeys,
1501                                   zebra_snippets *snippets)
1502 {
1503    void *decode_handle = iscz1_start();
1504     int off = 0;
1505     int seqno = 0;
1506     NMEM nmem = nmem_create();
1507
1508     yaz_log(YLOG_LOG, "zebra_rec_keys_snippets buf=%p sz=%d", reckeys->buf,
1509             reckeys->buf_used);
1510     assert(reckeys->buf);
1511     while (off < reckeys->buf_used)
1512     {
1513         const char *src = reckeys->buf + off;
1514         struct it_key key;
1515         char *dst = (char*) &key;
1516         char dst_buf[IT_MAX_WORD];
1517         char *dst_term = dst_buf;
1518
1519         iscz1_decode(decode_handle, &dst, &src);
1520         assert(key.len <= 4 && key.len > 2);
1521
1522         seqno = (int) key.mem[key.len-1];
1523
1524         zebra_term_untrans_iconv(zh, nmem, src[0], &dst_term, src+1);
1525         zebra_snippets_append(snippets, seqno, src[0], key.mem[0], dst_term);
1526         while (*src++)
1527             ;
1528         off = src - reckeys->buf;
1529         nmem_reset(nmem);
1530     }
1531     nmem_destroy(nmem);
1532     iscz1_stop(decode_handle);
1533     return ZEBRA_OK;
1534 }
1535
1536 void print_rec_keys(ZebraHandle zh, struct recKeys *reckeys)
1537 {
1538     void *decode_handle = iscz1_start();
1539     int off = 0;
1540     int seqno = 0;
1541     NMEM nmem = nmem_create();
1542
1543     yaz_log(YLOG_LOG, "print_rec_keys buf=%p sz=%d", reckeys->buf,
1544             reckeys->buf_used);
1545     assert(reckeys->buf);
1546     while (off < reckeys->buf_used)
1547     {
1548         const char *src = reckeys->buf + off;
1549         struct it_key key;
1550         char *dst = (char*) &key;
1551         int attrSet, attrUse;
1552         char dst_buf[IT_MAX_WORD];
1553         char *dst_term = dst_buf;
1554
1555         iscz1_decode(decode_handle, &dst, &src);
1556         assert(key.len <= 4 && key.len > 2);
1557
1558         attrSet = (int) key.mem[0] >> 16;
1559         attrUse = (int) key.mem[0] & 65535;
1560         seqno = (int) key.mem[key.len-1];
1561
1562         zebra_term_untrans_iconv(zh, nmem, src[0], &dst_term, src+1);
1563         
1564         yaz_log(YLOG_LOG, "ord=" ZINT_FORMAT " seqno=%d term=%s",
1565                 key.mem[0], seqno, dst_term); 
1566         while (*src++)
1567             ;
1568         off = src - reckeys->buf;
1569         nmem_reset(nmem);
1570     }
1571     nmem_destroy(nmem);
1572     iscz1_stop(decode_handle);
1573 }
1574
1575 void extract_add_index_string (RecWord *p, const char *str, int length)
1576 {
1577     struct it_key key;
1578
1579     ZebraHandle zh = p->extractCtrl->handle;
1580     ZebraExplainInfo zei = zh->reg->zei;
1581     int ch;
1582
1583     if (p->index_name)
1584     {
1585         ch = zebraExplain_lookup_attr_str(zei, p->index_type, p->index_name);
1586         if (ch < 0)
1587             ch = zebraExplain_add_attr_str(zei, p->index_type, p->index_name);
1588     }
1589     else
1590     {
1591 #if NATTR
1592         return;
1593 #else
1594         ch = zebraExplain_lookup_attr_su(zei, p->index_type, 
1595                                          p->attrSet, p->attrUse);
1596         if (ch < 0)
1597             ch = zebraExplain_add_attr_su(zei, p->index_type,
1598                                           p->attrSet, p->attrUse);
1599 #endif
1600     }
1601     key.len = 4;
1602     key.mem[0] = ch;
1603     key.mem[1] = p->record_id;
1604     key.mem[2] = p->section_id;
1605     key.mem[3] = p->seqno;
1606
1607 #if 0
1608     /* just for debugging .. */
1609     yaz_log(YLOG_LOG, "add: set=%d use=%d "
1610             "record_id=%lld section_id=%lld seqno=%lld",
1611             p->attrSet, p->attrUse, p->record_id, p->section_id, p->seqno);
1612 #endif
1613
1614     extract_add_it_key(p->extractCtrl->handle, 
1615                        &zh->reg->keys,
1616                        p->index_type, str,
1617                        length, &key);
1618 }
1619
1620 #if NATTR
1621 static void extract_add_sort_string (RecWord *p, const char *str, int length)
1622 {
1623     struct it_key key;
1624
1625     ZebraHandle zh = p->extractCtrl->handle;
1626     ZebraExplainInfo zei = zh->reg->zei;
1627     int ch;
1628
1629     if (p->index_name)
1630     {
1631         ch = zebraExplain_lookup_attr_str(zei, p->index_type, p->index_name);
1632         if (ch < 0)
1633             ch = zebraExplain_add_attr_str(zei, p->index_type, p->index_name);
1634     }
1635     else
1636     {
1637         return;
1638     }
1639     key.len = 4;
1640     key.mem[0] = ch;
1641     key.mem[1] = p->record_id;
1642     key.mem[2] = p->section_id;
1643     key.mem[3] = p->seqno;
1644
1645     extract_add_it_key(p->extractCtrl->handle, 
1646                        &zh->reg->sortKeys,
1647                        p->index_type, str,
1648                        length, &key);
1649 }
1650 #else
1651 static void extract_add_sort_string (RecWord *p, const char *str, int length)
1652 {
1653     ZebraHandle zh = p->extractCtrl->handle;
1654     struct sortKeys *sk = &zh->reg->sortKeys;
1655     int off = 0;
1656
1657     while (off < sk->buf_used)
1658     {
1659         int set, use, slen;
1660
1661         off += key_SU_decode(&set, sk->buf + off);
1662         off += key_SU_decode(&use, sk->buf + off);
1663         off += key_SU_decode(&slen, sk->buf + off);
1664         off += slen;
1665         if (p->attrSet == set && p->attrUse == use)
1666             return;
1667     }
1668     assert (off == sk->buf_used);
1669     
1670     if (sk->buf_used + IT_MAX_WORD > sk->buf_max)
1671     {
1672         char *b;
1673         
1674         b = (char *) xmalloc (sk->buf_max += 128000);
1675         if (sk->buf_used > 0)
1676             memcpy (b, sk->buf, sk->buf_used);
1677         xfree (sk->buf);
1678         sk->buf = b;
1679     }
1680     off += key_SU_encode(p->attrSet, sk->buf + off);
1681     off += key_SU_encode(p->attrUse, sk->buf + off);
1682     off += key_SU_encode(length, sk->buf + off);
1683     memcpy (sk->buf + off, str, length);
1684     sk->buf_used = off + length;
1685 }
1686 #endif
1687
1688 void extract_add_string (RecWord *p, const char *string, int length)
1689 {
1690     assert (length > 0);
1691     if (zebra_maps_is_sort (p->zebra_maps, p->index_type))
1692         extract_add_sort_string (p, string, length);
1693     else
1694         extract_add_index_string (p, string, length);
1695 }
1696
1697 static void extract_add_incomplete_field (RecWord *p)
1698 {
1699     const char *b = p->term_buf;
1700     int remain = p->term_len;
1701     const char **map = 0;
1702     
1703     yaz_log(YLOG_DEBUG, "Incomplete field, w='%.*s'", p->term_len, p->term_buf);
1704
1705     if (remain > 0)
1706         map = zebra_maps_input(p->zebra_maps, p->index_type, &b, remain, 0);
1707
1708     while (map)
1709     {
1710         char buf[IT_MAX_WORD+1];
1711         int i, remain;
1712
1713         /* Skip spaces */
1714         while (map && *map && **map == *CHR_SPACE)
1715         {
1716             remain = p->term_len - (b - p->term_buf);
1717             if (remain > 0)
1718                 map = zebra_maps_input(p->zebra_maps, p->index_type, &b,
1719                                        remain, 0);
1720             else
1721                 map = 0;
1722         }
1723         if (!map)
1724             break;
1725         i = 0;
1726         while (map && *map && **map != *CHR_SPACE)
1727         {
1728             const char *cp = *map;
1729
1730             while (i < IT_MAX_WORD && *cp)
1731                 buf[i++] = *(cp++);
1732             remain = p->term_len - (b - p->term_buf);
1733             if (remain > 0)
1734                 map = zebra_maps_input(p->zebra_maps, p->index_type, &b, remain, 0);
1735             else
1736                 map = 0;
1737         }
1738         if (!i)
1739             return;
1740         extract_add_string (p, buf, i);
1741         p->seqno++;
1742     }
1743 }
1744
1745 static void extract_add_complete_field (RecWord *p)
1746 {
1747     const char *b = p->term_buf;
1748     char buf[IT_MAX_WORD+1];
1749     const char **map = 0;
1750     int i = 0, remain = p->term_len;
1751
1752     yaz_log(YLOG_DEBUG, "Complete field, w='%.*s'",
1753             p->term_len, p->term_buf);
1754
1755     if (remain > 0)
1756         map = zebra_maps_input (p->zebra_maps, p->index_type, &b, remain, 1);
1757
1758     while (remain > 0 && i < IT_MAX_WORD)
1759     {
1760         while (map && *map && **map == *CHR_SPACE)
1761         {
1762             remain = p->term_len - (b - p->term_buf);
1763
1764             if (remain > 0)
1765             {
1766                 int first = i ? 0 : 1;  /* first position */
1767                 map = zebra_maps_input(p->zebra_maps, p->index_type, &b, remain, first);
1768             }
1769             else
1770                 map = 0;
1771         }
1772         if (!map)
1773             break;
1774
1775         if (i && i < IT_MAX_WORD)
1776             buf[i++] = *CHR_SPACE;
1777         while (map && *map && **map != *CHR_SPACE)
1778         {
1779             const char *cp = *map;
1780
1781             if (**map == *CHR_CUT)
1782             {
1783                 i = 0;
1784             }
1785             else
1786             {
1787                 if (i >= IT_MAX_WORD)
1788                     break;
1789                 yaz_log(YLOG_DEBUG, "Adding string to index '%d'", **map);
1790                 while (i < IT_MAX_WORD && *cp)
1791                     buf[i++] = *(cp++);
1792             }
1793             remain = p->term_len  - (b - p->term_buf);
1794             if (remain > 0)
1795             {
1796                 map = zebra_maps_input (p->zebra_maps, p->index_type, &b,
1797                                         remain, 0);
1798             }
1799             else
1800                 map = 0;
1801         }
1802     }
1803     if (!i)
1804         return;
1805     extract_add_string (p, buf, i);
1806 }
1807
1808 void extract_token_add (RecWord *p)
1809 {
1810     WRBUF wrbuf;
1811 #if 0
1812     yaz_log (YLOG_LOG, "token_add "
1813              "reg_type=%c attrSet=%d attrUse=%d seqno=%d s=%.*s",
1814              p->reg_type, p->attrSet, p->attrUse, p->seqno, p->length,
1815              p->string);
1816 #endif
1817     if ((wrbuf = zebra_replace(p->zebra_maps, p->index_type, 0,
1818                                p->term_buf, p->term_len)))
1819     {
1820         p->term_buf = wrbuf_buf(wrbuf);
1821         p->term_len = wrbuf_len(wrbuf);
1822     }
1823     if (zebra_maps_is_complete (p->zebra_maps, p->index_type))
1824         extract_add_complete_field (p);
1825     else
1826         extract_add_incomplete_field(p);
1827 }
1828
1829 static void extract_set_store_data_cb(struct recExtractCtrl *p,
1830                                       void *buf, size_t sz)
1831 {
1832     ZebraHandle zh = (ZebraHandle) p->handle;
1833
1834     xfree(zh->store_data_buf);
1835     zh->store_data_buf = 0;
1836     zh->store_data_size = 0;
1837     if (buf && sz)
1838     {
1839         zh->store_data_buf = xmalloc(sz);
1840         zh->store_data_size = sz;
1841         memcpy(zh->store_data_buf, buf, sz);
1842     }
1843 }
1844
1845 static void extract_set_store_data_prepare(struct recExtractCtrl *p)
1846 {
1847     ZebraHandle zh = (ZebraHandle) p->handle;
1848     xfree(zh->store_data_buf);
1849     zh->store_data_buf = 0;
1850     zh->store_data_size = 0;
1851     p->setStoreData = extract_set_store_data_cb;
1852 }
1853
1854 void extract_schema_add (struct recExtractCtrl *p, Odr_oid *oid)
1855 {
1856     ZebraHandle zh = (ZebraHandle) p->handle;
1857     zebraExplain_addSchema (zh->reg->zei, oid);
1858 }
1859
1860 #if NATTR
1861 void extract_flushSortKeys (ZebraHandle zh, SYSNO sysno,
1862                             int cmd, struct recKeys *reckeys)
1863 {
1864     SortIdx sortIdx = zh->reg->sortIdx;
1865     void *decode_handle = iscz1_start();
1866     int off = 0;
1867     int ch = 0;
1868
1869     while (off < reckeys->buf_used)
1870     {
1871         const char *src = reckeys->buf + off;
1872         struct it_key key;
1873         char *dst = (char*) &key;
1874
1875         iscz1_decode(decode_handle, &dst, &src);
1876         assert(key.len == 4);
1877
1878         ch = (int) key.mem[0];  /* ordinal for field/use/attribute */
1879
1880         sortIdx_type(sortIdx, ch);
1881         if (cmd == 1)
1882             sortIdx_add(sortIdx, src, strlen(src));
1883         else
1884             sortIdx_add(sortIdx, "", 1);
1885         
1886         src += strlen(src);
1887         src++;
1888
1889         off = src - reckeys->buf;
1890     }
1891     assert (off == reckeys->buf_used);
1892     iscz1_stop(decode_handle);
1893 }
1894 #else
1895 void extract_flushSortKeys (ZebraHandle zh, SYSNO sysno,
1896                             int cmd, struct sortKeys *sk)
1897 {
1898     SortIdx sortIdx = zh->reg->sortIdx;
1899     int off = 0;
1900
1901     sortIdx_sysno (sortIdx, sysno);
1902
1903     while (off < sk->buf_used)
1904     {
1905         int set, use, slen;
1906         
1907         off += key_SU_decode(&set, sk->buf + off);
1908         off += key_SU_decode(&use, sk->buf + off);
1909         off += key_SU_decode(&slen, sk->buf + off);
1910         
1911         sortIdx_type(sortIdx, use);
1912         if (cmd == 1)
1913             sortIdx_add(sortIdx, sk->buf + off, slen);
1914         else
1915             sortIdx_add(sortIdx, "", 1);
1916         off += slen;
1917     }
1918 }
1919 #endif
1920
1921 void encode_key_init (struct encode_info *i)
1922 {
1923     i->sysno = 0;
1924     i->seqno = 0;
1925     i->cmd = -1;
1926     i->prevsys=0;
1927     i->prevseq=0;
1928     i->prevcmd=-1;
1929     i->keylen=0;
1930     i->encode_handle = iscz1_start();
1931 }
1932
1933 #define OLDENCODE 1
1934
1935 #ifdef OLDENCODE
1936 /* this is the old encode_key_write 
1937  * may be deleted once we are confident that the new works
1938  * HL 15-oct-2002
1939  */
1940 void encode_key_write (char *k, struct encode_info *i, FILE *outf)
1941 {
1942     struct it_key key;
1943     char *bp = i->buf, *bp0;
1944     const char *src = (char *) &key;
1945
1946     /* copy term to output buf */
1947     while ((*bp++ = *k++))
1948         ;
1949     /* and copy & align key so we can mangle */
1950     memcpy (&key, k+1, sizeof(struct it_key));  /* *k is insert/delete */
1951
1952     bp0 = bp++;
1953     iscz1_encode(i->encode_handle, &bp, &src);
1954     *bp0 = (*k * 128) + bp - bp0 - 1; /* length and insert/delete combined */
1955     if (fwrite (i->buf, bp - i->buf, 1, outf) != 1)
1956     {
1957         yaz_log (YLOG_FATAL|YLOG_ERRNO, "fwrite");
1958         exit (1);
1959     }
1960 }
1961
1962 void encode_key_flush (struct encode_info *i, FILE *outf)
1963 { /* dummy routine */
1964     iscz1_stop(i->encode_handle);
1965 }
1966
1967 #else
1968
1969 /* new encode_key_write
1970  * The idea is to buffer one more key, and compare them
1971  * If we are going to delete and insert the same key, 
1972  * we may as well not bother. Should make a difference in 
1973  * updates with small modifications (appending to a mbox)
1974  */
1975 void encode_key_write (char *k, struct encode_info *i, FILE *outf)
1976 {
1977     struct it_key key;
1978     char *bp; 
1979
1980     if (*k)  /* first time for new key */
1981     {
1982         bp = i->buf;
1983         while ((*bp++ = *k++))
1984             ;
1985         i->keylen= bp - i->buf -1;    
1986         assert(i->keylen+1+sizeof(struct it_key) < ENCODE_BUFLEN);
1987     }
1988     else
1989     {
1990         bp=i->buf + i->keylen;
1991         *bp++=0;
1992         k++;
1993     }
1994
1995     memcpy (&key, k+1, sizeof(struct it_key));
1996     if (0==i->prevsys) /* no previous filter, fill up */
1997     {
1998         i->prevsys=key.sysno;
1999         i->prevseq=key.seqno;
2000         i->prevcmd=*k;
2001     }
2002     else if ( (i->prevsys==key.sysno) &&
2003               (i->prevseq==key.seqno) &&
2004               (i->prevcmd!=*k) )
2005     { /* same numbers, diff cmd, they cancel out */
2006         i->prevsys=0;
2007     }
2008     else 
2009     { /* different stuff, write previous, move buf */
2010         bp = encode_key_int ( (i->prevsys - i->sysno) * 2 + i->prevcmd, bp);
2011         if (i->sysno != i->prevsys)
2012         {
2013             i->sysno = i->prevsys;
2014             i->seqno = 0;
2015         }
2016         else if (!i->seqno && !i->prevseq && i->cmd == i->prevcmd)
2017         {
2018             return; /* ??? Filters some sort of duplicates away */
2019                     /* ??? Can this ever happen   -H 15oct02 */
2020         }
2021         bp = encode_key_int (i->prevseq - i->seqno, bp);
2022         i->seqno = i->prevseq;
2023         i->cmd = i->prevcmd;
2024         if (fwrite (i->buf, bp - i->buf, 1, outf) != 1)
2025         {
2026             yaz_log (YLOG_FATAL|YLOG_ERRNO, "fwrite");
2027             exit (1);
2028         }
2029         i->keylen=0; /* ok, it's written, forget it */
2030         i->prevsys=key.sysno;
2031         i->prevseq=key.seqno;
2032         i->prevcmd=*k;
2033     }
2034 }
2035
2036 void encode_key_flush (struct encode_info *i, FILE *outf)
2037 { /* flush the last key from i */
2038     char *bp =i->buf + i->keylen;
2039     if (0==i->prevsys)
2040     {
2041         return; /* nothing to flush */
2042     }
2043     *bp++=0;
2044     bp = encode_key_int ( (i->prevsys - i->sysno) * 2 + i->prevcmd, bp);
2045     if (i->sysno != i->prevsys)
2046     {
2047         i->sysno = i->prevsys;
2048         i->seqno = 0;
2049     }
2050     else if (!i->seqno && !i->prevseq && i->cmd == i->prevcmd)
2051     {
2052         return; /* ??? Filters some sort of duplicates away */
2053                 /* ??? Can this ever happen   -H 15oct02 */
2054     }
2055     bp = encode_key_int (i->prevseq - i->seqno, bp);
2056     i->seqno = i->prevseq;
2057     i->cmd = i->prevcmd;
2058     if (fwrite (i->buf, bp - i->buf, 1, outf) != 1)
2059     {
2060         yaz_log (YLOG_FATAL|YLOG_ERRNO, "fwrite");
2061         exit (1);
2062     }
2063     i->keylen=0; /* ok, it's written, forget it */
2064     i->prevsys=0; /* forget the values too */
2065     i->prevseq=0;
2066 }
2067 #endif