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