943b4f1b3206cca2a194ae45fbc09d75cdcb416e
[idzebra-moved-to-github.git] / index / trav.c
1 /*
2  * Copyright (C) 1994-1998, Index Data I/S 
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: trav.c,v $
7  * Revision 1.34  1998-06-08 14:43:14  adam
8  * Added suport for EXPLAIN Proxy servers - added settings databasePath
9  * and explainDatabase to facilitate this. Increased maximum number
10  * of databases and attributes in one register.
11  *
12  * Revision 1.33  1998/01/12 15:04:08  adam
13  * The test option (-s) only uses read-lock (and not write lock).
14  *
15  * Revision 1.32  1997/09/25 14:56:51  adam
16  * Windows NT interface code to the stat call.
17  *
18  * Revision 1.31  1997/09/17 12:19:17  adam
19  * Zebra version corresponds to YAZ version 1.4.
20  * Changed Zebra server so that it doesn't depend on global common_resource.
21  *
22  * Revision 1.30  1997/09/09 13:38:09  adam
23  * Partial port to WIN95/NT.
24  *
25  * Revision 1.29  1997/02/12 20:39:47  adam
26  * Implemented options -f <n> that limits the log to the first <n>
27  * records.
28  * Changed some log messages also.
29  *
30  * Revision 1.28  1996/11/01 08:58:44  adam
31  * Interface to isamc system now includes update and delete.
32  *
33  * Revision 1.27  1996/10/29 14:06:56  adam
34  * Include zebrautl.h instead of alexutil.h.
35  *
36  * Revision 1.26  1996/06/04 10:19:01  adam
37  * Minor changes - removed include of ctype.h.
38  *
39  * Revision 1.25  1996/05/01  13:46:37  adam
40  * First work on multiple records in one file.
41  * New option, -offset, to the "unread" command in the filter module.
42  *
43  * Revision 1.24  1996/04/26  10:00:23  adam
44  * Added option -V to zebraidx to display version information.
45  * Removed stupid warnings from file update.
46  *
47  * Revision 1.23  1996/04/12  07:02:25  adam
48  * File update of single files.
49  *
50  * Revision 1.22  1996/04/09 06:50:50  adam
51  * Bug fix: bad reference in function fileUpdateR.
52  *
53  * Revision 1.21  1996/03/22 15:34:18  quinn
54  * Fixed bad reference
55  *
56  * Revision 1.20  1996/03/21  14:50:10  adam
57  * File update uses modify-time instead of change-time.
58  *
59  * Revision 1.19  1996/03/20  16:16:55  quinn
60  * Added diagnostic output
61  *
62  * Revision 1.18  1996/03/19  12:43:27  adam
63  * Bug fix: File update traversal didn't handle trailing slashes correctly.
64  * Bug fix: Update of sub directory groups wasn't handled correctly.
65  *
66  * Revision 1.17  1996/02/12  18:45:17  adam
67  * Changed naming of some functions.
68  *
69  * Revision 1.16  1996/02/05  12:30:02  adam
70  * Logging reduced a bit.
71  * The remaining running time is estimated during register merge.
72  *
73  * Revision 1.15  1995/12/07  17:38:48  adam
74  * Work locking mechanisms for concurrent updates/commit.
75  *
76  * Revision 1.14  1995/12/06  12:41:26  adam
77  * New command 'stat' for the index program.
78  * Filenames can be read from stdin by specifying '-'.
79  * Bug fix/enhancement of the transformation from terms to regular
80  * expressons in the search engine.
81  *
82  * Revision 1.13  1995/11/28  09:09:46  adam
83  * Zebra config renamed.
84  * Use setting 'recordId' to identify record now.
85  * Bug fix in recindex.c: rec_release_blocks was invokeded even
86  * though the blocks were already released.
87  * File traversal properly deletes records when needed.
88  *
89  * Revision 1.12  1995/11/24  11:31:37  adam
90  * Commands add & del read filenames from stdin if source directory is
91  * empty.
92  * Match criteria supports 'constant' strings.
93  *
94  * Revision 1.11  1995/11/22  17:19:19  adam
95  * Record management uses the bfile system.
96  *
97  * Revision 1.10  1995/11/21  15:01:16  adam
98  * New general match criteria implemented.
99  * New feature: document groups.
100  *
101  * Revision 1.9  1995/11/21  09:20:32  adam
102  * Yet more work on record match.
103  *
104  * Revision 1.8  1995/11/20  16:59:46  adam
105  * New update method: the 'old' keys are saved for each records.
106  *
107  * Revision 1.7  1995/11/20  11:56:28  adam
108  * Work on new traversal.
109  *
110  * Revision 1.6  1995/11/17  15:54:42  adam
111  * Started work on virtual directory structure.
112  *
113  * Revision 1.5  1995/10/17  18:02:09  adam
114  * New feature: databases. Implemented as prefix to words in dictionary.
115  *
116  * Revision 1.4  1995/09/28  09:19:46  adam
117  * xfree/xmalloc used everywhere.
118  * Extract/retrieve method seems to work for text records.
119  *
120  * Revision 1.3  1995/09/06  16:11:18  adam
121  * Option: only one word key per file.
122  *
123  * Revision 1.2  1995/09/04  12:33:43  adam
124  * Various cleanup. YAZ util used instead.
125  *
126  * Revision 1.1  1995/09/01  14:06:36  adam
127  * Split of work into more files.
128  *
129  */
130
131
132 #include <stdio.h>
133 #include <assert.h>
134 #include <sys/types.h>
135 #include <sys/stat.h>
136 #ifdef WINDOWS
137 #include <io.h>
138 #define S_ISREG(x) (x & _S_IFREG)
139 #define S_ISDIR(x) (x & _S_IFDIR)
140 #else
141 #include <unistd.h>
142 #endif
143 #include <direntz.h>
144 #include <fcntl.h>
145 #include <time.h>
146
147 #include "index.h"
148
149 static int repComp (const char *a, const char *b, size_t len)
150 {
151     if (!len)
152         return 0;
153     return memcmp (a, b, len);
154 }
155
156 static void repositoryExtractR (int deleteFlag, char *rep,
157                                 struct recordGroup *rGroup,
158                                 int level)
159 {
160     struct dir_entry *e;
161     int i;
162     size_t rep_len = strlen (rep);
163
164     e = dir_open (rep);
165     if (!e)
166         return;
167     logf (LOG_LOG, "dir %s", rep);
168     if (rep[rep_len-1] != '/')
169         rep[rep_len] = '/';
170     else
171         --rep_len;
172     
173     for (i=0; e[i].name; i++)
174     {
175         char *ecp;
176         strcpy (rep +rep_len+1, e[i].name);
177         if ((ecp = strrchr (e[i].name, '/')))
178             *ecp = '\0';
179         if (level == 0 && rGroup->databaseNamePath)
180             rGroup->databaseName = e[i].name;
181
182         switch (e[i].kind)
183         {
184         case dirs_file:
185             fileExtract (NULL, rep, rGroup, deleteFlag);
186             break;
187         case dirs_dir:
188             repositoryExtractR (deleteFlag, rep, rGroup, level+1);
189             break;
190         }
191     }
192     dir_free (&e);
193
194 }
195
196 static void fileDeleteR (struct dirs_info *di, struct dirs_entry *dst,
197                                const char *base, char *src,
198                                struct recordGroup *rGroup)
199 {
200     char tmppath[1024];
201     size_t src_len = strlen (src);
202
203     while (dst && !repComp (dst->path, src, src_len+1))
204     {
205         switch (dst->kind)
206         {
207         case dirs_file:
208             sprintf (tmppath, "%s%s", base, dst->path);
209             fileExtract (&dst->sysno, tmppath, rGroup, 1);
210              
211             strcpy (tmppath, dst->path);
212             dst = dirs_read (di); 
213             dirs_del (di, tmppath);
214             break;
215         case dirs_dir:
216             strcpy (tmppath, dst->path);
217             dst = dirs_read (di);
218             dirs_rmdir (di, tmppath);
219             break;
220         default:
221             dst = dirs_read (di);
222         }
223     }
224 }
225
226 static void fileUpdateR (struct dirs_info *di, struct dirs_entry *dst,
227                          const char *base, char *src, 
228                          struct recordGroup *rGroup,
229                          int level)
230 {
231     struct dir_entry *e_src;
232     int i_src = 0;
233     static char tmppath[1024];
234     size_t src_len = strlen (src);
235
236     sprintf (tmppath, "%s%s", base, src);
237     e_src = dir_open (tmppath);
238     logf (LOG_LOG, "dir %s", tmppath);
239
240 #if 0
241     if (!dst || repComp (dst->path, src, src_len))
242 #else
243     if (!dst || strcmp (dst->path, src))
244 #endif
245     {
246         if (!e_src)
247             return;
248
249         if (src_len && src[src_len-1] != '/')
250         {
251             src[src_len] = '/';
252             src[++src_len] = '\0';
253         }
254         dirs_mkdir (di, src, 0);
255         if (dst && repComp (dst->path, src, src_len))
256             dst = NULL;
257     }
258     else if (!e_src)
259     {
260         strcpy (src, dst->path);
261         fileDeleteR (di, dst, base, src, rGroup);
262         return;
263     }
264     else
265     {
266         if (src_len && src[src_len-1] != '/')
267         {
268             src[src_len] = '/';
269             src[++src_len] = '\0';
270         }
271         dst = dirs_read (di); 
272     }
273     dir_sort (e_src);
274
275     while (1)
276     {
277         int sd;
278
279         if (dst && !repComp (dst->path, src, src_len))
280         {
281             if (e_src[i_src].name)
282             {
283                 logf (LOG_DEBUG, "dst=%s src=%s", dst->path + src_len,
284                       e_src[i_src].name);
285                 sd = strcmp (dst->path + src_len, e_src[i_src].name);
286             }
287             else
288                 sd = -1;
289         }
290         else if (e_src[i_src].name)
291             sd = 1;
292         else
293             break;
294         logf (LOG_DEBUG, "trav sd=%d", sd);
295
296         if (level == 0 && rGroup->databaseNamePath)
297             rGroup->databaseName = e_src[i_src].name;
298         if (sd == 0)
299         {
300             strcpy (src + src_len, e_src[i_src].name);
301             sprintf (tmppath, "%s%s", base, src);
302             
303             switch (e_src[i_src].kind)
304             {
305             case dirs_file:
306                 if (e_src[i_src].mtime > dst->mtime)
307                 {
308                     if (fileExtract (&dst->sysno, tmppath, rGroup, 0))
309                     {
310                         dirs_add (di, src, dst->sysno, e_src[i_src].mtime);
311                     }
312                     logf (LOG_DEBUG, "old: %s", ctime (&dst->mtime));
313                     logf (LOG_DEBUG, "new: %s", ctime (&e_src[i_src].mtime));
314                 }
315                 dst = dirs_read (di);
316                 break;
317             case dirs_dir:
318                 fileUpdateR (di, dst, base, src, rGroup, level+1);
319                 dst = dirs_last (di);
320                 logf (LOG_DEBUG, "last is %s", dst ? dst->path : "null");
321                 break;
322             default:
323                 dst = dirs_read (di); 
324             }
325             i_src++;
326         }
327         else if (sd > 0)
328         {
329             SYSNO sysno = 0;
330             strcpy (src + src_len, e_src[i_src].name);
331             sprintf (tmppath, "%s%s", base, src);
332
333             switch (e_src[i_src].kind)
334             {
335             case dirs_file:
336                 if (fileExtract (&sysno, tmppath, rGroup, 0))
337                     dirs_add (di, src, sysno, e_src[i_src].mtime);            
338                 break;
339             case dirs_dir:
340                 fileUpdateR (di, dst, base, src, rGroup, level+1);
341                 if (dst)
342                     dst = dirs_last (di);
343                 break;
344             }
345             i_src++;
346         }
347         else  /* sd < 0 */
348         {
349             strcpy (src, dst->path);
350             sprintf (tmppath, "%s%s", base, dst->path);
351
352             switch (dst->kind)
353             {
354             case dirs_file:
355                 fileExtract (&dst->sysno, tmppath, rGroup, 1);
356                 dirs_del (di, dst->path);
357                 dst = dirs_read (di);
358                 break;
359             case dirs_dir:
360                 fileDeleteR (di, dst, base, src, rGroup);
361                 dst = dirs_last (di);
362             }
363         }
364     }
365     dir_free (&e_src);
366 }
367
368 static void groupRes (struct recordGroup *rGroup)
369 {
370     char resStr[256];
371     char gPrefix[256];
372
373     if (!rGroup->groupName || !*rGroup->groupName)
374         *gPrefix = '\0';
375     else
376         sprintf (gPrefix, "%s.", rGroup->groupName);
377
378     sprintf (resStr, "%srecordId", gPrefix);
379     rGroup->recordId = res_get (common_resource, resStr);
380     sprintf (resStr, "%sdatabasePath", gPrefix);
381     rGroup->databaseNamePath =
382         atoi (res_get_def (common_resource, resStr, "0"));
383 }
384
385 void repositoryShow (struct recordGroup *rGroup)
386 {
387     char src[1024];
388     int src_len;
389     struct dirs_entry *dst;
390     Dict dict;
391     struct dirs_info *di;
392     
393     if (!(dict = dict_open (rGroup->bfs, FMATCH_DICT, 50, 0)))
394     {
395         logf (LOG_FATAL, "dict_open fail of %s", FMATCH_DICT);
396         return;
397     }
398     
399     assert (rGroup->path);    
400     strcpy (src, rGroup->path);
401     src_len = strlen (src);
402     
403     if (src_len && src[src_len-1] != '/')
404     {
405         src[src_len] = '/';
406         src[++src_len] = '\0';
407     }
408     
409     di = dirs_open (dict, src, rGroup->flagRw);
410     
411     while ( (dst = dirs_read (di)) )
412         logf (LOG_LOG, "%s", dst->path);
413     dirs_free (&di);
414     dict_close (dict);
415 }
416
417 static void fileUpdate (Dict dict, struct recordGroup *rGroup,
418                         const char *path)
419 {
420     struct dirs_info *di;
421     struct stat sbuf;
422     char src[1024];
423     char dst[1024];
424     int src_len;
425
426     assert (path);
427     strcpy (src, path);
428     src_len = strlen (src);
429
430     stat (src, &sbuf);
431     if (S_ISREG(sbuf.st_mode))
432     {
433         struct dirs_entry *e_dst;
434         di = dirs_fopen (dict, src);
435
436         e_dst = dirs_read (di);
437         if (e_dst)
438         {
439             if (sbuf.st_mtime > e_dst->mtime)
440                 if (fileExtract (&e_dst->sysno, src, rGroup, 0))
441                     dirs_add (di, src, e_dst->sysno, sbuf.st_mtime);
442         }
443         else
444         {
445             SYSNO sysno = 0;
446             if (fileExtract (&sysno, src, rGroup, 0))
447                  dirs_add (di, src, sysno, sbuf.st_mtime);
448         }
449         dirs_free (&di);
450     }
451     else if (S_ISDIR(sbuf.st_mode))
452     {
453         if (src_len && src[src_len-1] != '/')
454         {
455             src[src_len] = '/';
456             src[++src_len] = '\0';
457         }
458         di = dirs_open (dict, src, rGroup->flagRw);
459         *dst = '\0';
460         fileUpdateR (di, dirs_read (di), src, dst, rGroup, 0);
461         dirs_free (&di);
462     }
463     else
464     {
465         logf (LOG_WARN, "Ignoring path %s", src);
466     }
467 }
468
469
470 static void repositoryExtract (int deleteFlag, struct recordGroup *rGroup,
471                                const char *path)
472 {
473     struct stat sbuf;
474     char src[1024];
475
476     assert (path);
477     strcpy (src, path);
478
479     stat (src, &sbuf);
480     if (S_ISREG(sbuf.st_mode))
481         fileExtract (NULL, src, rGroup, deleteFlag);
482     else if (S_ISDIR(sbuf.st_mode))
483         repositoryExtractR (deleteFlag, src, rGroup, 0);
484     else
485         logf (LOG_WARN, "Ignoring path %s", src);
486 }
487
488 static void repositoryExtractG (int deleteFlag, struct recordGroup *rGroup)
489 {
490     if (*rGroup->path == '\0' || !strcmp(rGroup->path, "-"))
491     {
492         char src[1024];
493
494         while (scanf ("%s", src) == 1)
495             repositoryExtract (deleteFlag, rGroup, src);
496     }
497     else
498         repositoryExtract (deleteFlag, rGroup, rGroup->path);
499 }
500
501 void repositoryUpdate (struct recordGroup *rGroup)
502 {
503     groupRes (rGroup);
504     assert (rGroup->path);
505     if (rGroup->recordId && !strcmp (rGroup->recordId, "file"))
506     {
507         Dict dict;
508         if (!(dict = dict_open (rGroup->bfs, FMATCH_DICT, 50, rGroup->flagRw)))
509         {
510             logf (LOG_FATAL, "dict_open fail of %s", FMATCH_DICT);
511             return ;
512         }
513         if (*rGroup->path == '\0' || !strcmp(rGroup->path, "-"))
514         {
515             char src[1024];
516             while (scanf ("%s", src) == 1)
517                 fileUpdate (dict, rGroup, src);
518         }
519         else
520             fileUpdate (dict, rGroup, rGroup->path);
521         dict_close (dict);
522     }
523     else 
524         repositoryExtractG (0, rGroup);
525 }
526
527 void repositoryDelete (struct recordGroup *rGroup)
528 {
529     repositoryExtractG (1, rGroup);
530 }
531