Fixed problem with close of lock file for WIN32.
[idzebra-moved-to-github.git] / bfile / mfile.c
1 /*
2  * Copyright (C) 1994-1999, Index Data
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: mfile.c,v $
7  * Revision 1.39  2000-05-05 13:48:03  adam
8  * Fixed locking for metafiles.
9  *
10  * Revision 1.38  2000/03/20 19:08:35  adam
11  * Added remote record import using Z39.50 extended services and Segment
12  * Requests.
13  *
14  * Revision 1.37  2000/03/15 15:00:30  adam
15  * First work on threaded version.
16  *
17  * Revision 1.36  1999/12/08 15:03:11  adam
18  * Implemented bf_reset.
19  *
20  * Revision 1.35  1999/10/14 14:33:50  adam
21  * Added truncation 5=106.
22  *
23  * Revision 1.34  1999/05/26 07:49:12  adam
24  * C++ compilation.
25  *
26  * Revision 1.33  1999/05/12 13:08:06  adam
27  * First version of ISAMS.
28  *
29  * Revision 1.32  1999/04/28 14:53:07  adam
30  * Fixed stupid bug regarding split-files.
31  *
32  * Revision 1.31  1999/02/18 12:49:33  adam
33  * Changed file naming scheme for register files as well as record
34  * store/index files.
35  *
36  * Revision 1.30  1999/02/02 14:50:02  adam
37  * Updated WIN32 code specific sections. Changed header.
38  *
39  * Revision 1.29  1998/05/27 14:28:34  adam
40  * Fixed bug in mf_write. 'Cap off' byte written at wrong offset.
41  *
42  * Revision 1.28  1998/05/20 10:00:35  adam
43  * Fixed register spec so that colon isn't treated as size separator
44  * unless followed by [0-9+-] in order to allow DOS drive specifications.
45  *
46  * Revision 1.27  1998/02/10 11:55:07  adam
47  * Minor changes.
48  *
49  * Revision 1.26  1997/10/27 14:25:38  adam
50  * Fixed memory leaks.
51  *
52  * Revision 1.25  1997/09/18 08:59:16  adam
53  * Extra generic handle for the character mapping routines.
54  *
55  * Revision 1.24  1997/09/17 12:19:06  adam
56  * Zebra version corresponds to YAZ version 1.4.
57  * Changed Zebra server so that it doesn't depend on global common_resource.
58  *
59  * Revision 1.23  1997/09/09 13:37:53  adam
60  * Partial port to WIN95/NT.
61  *
62  * Revision 1.22  1997/09/04 13:56:39  adam
63  * Added O_BINARY to open calls.
64  *
65  * Revision 1.21  1996/10/29 13:56:18  adam
66  * Include of zebrautl.h instead of alexutil.h.
67  *
68  * Revision 1.20  1996/05/14 12:10:16  quinn
69  * Bad areadef scan
70  *
71  * Revision 1.19  1996/05/01  07:16:30  quinn
72  * Fixed ancient bug.
73  *
74  * Revision 1.18  1996/04/09  06:47:30  adam
75  * Function scan_areadef doesn't use sscanf (%n fails on this Linux).
76  *
77  * Revision 1.17  1996/03/20 13:29:11  quinn
78  * Bug-fix
79  *
80  * Revision 1.16  1995/12/12  15:57:57  adam
81  * Implemented mf_unlink. cf_unlink uses mf_unlink.
82  *
83  * Revision 1.15  1995/12/08  16:21:14  adam
84  * Work on commit/update.
85  *
86  * Revision 1.14  1995/12/05  13:12:37  quinn
87  * Added <errno.h>
88  *
89  * Revision 1.13  1995/11/30  17:00:50  adam
90  * Several bug fixes. Commit system runs now.
91  *
92  * Revision 1.12  1995/11/24  17:26:11  quinn
93  * Mostly about making some ISAM stuff in the config file optional.
94  *
95  * Revision 1.11  1995/11/13  09:32:43  quinn
96  * Comment work.
97  *
98  * Revision 1.10  1995/09/04  12:33:22  adam
99  * Various cleanup. YAZ util used instead.
100  *
101  * Revision 1.9  1994/11/04  14:26:39  quinn
102  * bug-fix.
103  *
104  * Revision 1.8  1994/10/05  16:56:42  quinn
105  * Minor.
106  *
107  * Revision 1.7  1994/09/19  14:12:37  quinn
108  * dunno.
109  *
110  * Revision 1.6  1994/09/14  13:10:15  quinn
111  * Corrected some bugs in the init-phase
112  *
113  * Revision 1.5  1994/09/12  08:01:51  quinn
114  * Small
115  *
116  * Revision 1.4  1994/09/01  14:51:07  quinn
117  * Allowed mf_write to write beyond eof+1.
118  *
119  * Revision 1.3  1994/08/24  09:37:17  quinn
120  * Changed reaction to read return values.
121  *
122  * Revision 1.2  1994/08/23  14:50:48  quinn
123  * Fixed mf_close().
124  *
125  * Revision 1.1  1994/08/23  14:41:33  quinn
126  * First functional version.
127  *
128  */
129
130
131  /*
132   * TODO: The size estimates in init may not be accurate due to
133   * only partially written final blocks.
134   */
135
136 #include <sys/types.h>
137 #include <fcntl.h>
138 #ifdef WIN32
139 #include <io.h>
140 #else
141 #include <unistd.h>
142 #endif
143 #include <direntz.h>
144 #include <string.h>
145 #include <stdlib.h>
146 #include <stdio.h>
147 #include <assert.h>
148 #include <errno.h>
149
150 #include <zebra-lock.h>
151 #include <zebrautl.h>
152 #include <mfile.h>
153
154 static int scan_areadef(MFile_area ma, const char *name, const char *ad)
155 {
156     /*
157      * If no definition is given, use current directory, unlimited.
158      */
159     char dirname[FILENAME_MAX+1]; 
160     mf_dir **dp = &ma->dirs, *dir = *dp;
161
162     if (!ad)
163         ad = ".:-1b";
164     for (;;)
165     {
166         const char *ad0 = ad;
167         int i = 0, fact = 1, multi;
168         off_t size = 0;
169
170         while (*ad == ' ' || *ad == '\t')
171             ad++;
172         if (!*ad)
173             break;
174         while (*ad)
175         {
176             if (*ad == ':' && strchr ("+-0123456789", ad[1]))
177                 break;
178             if (i < FILENAME_MAX)
179                 dirname[i++] = *ad;
180             ad++;
181         }
182         dirname[i] = '\0';
183         if (*ad++ != ':')
184         {
185             logf (LOG_WARN, "Missing colon after path: %s", ad0);
186             return -1;
187         }
188         if (i == 0)
189         {
190             logf (LOG_WARN, "Empty path: %s", ad0);
191             return -1;
192         }
193         while (*ad == ' ' || *ad == '\t')
194             ad++;
195         if (*ad == '-')
196         {
197             fact = -1;
198             ad++;
199         }
200         else if (*ad == '+')
201             ad++;
202         size = 0;
203         if (*ad < '0' || *ad > '9')
204         {
205             logf (LOG_FATAL, "Missing size after path: %s", ad0);
206             return -1;
207         }
208         size = 0;
209         while (*ad >= '0' && *ad <= '9')
210             size = size*10 + (*ad++ - '0');
211         switch (*ad)
212         {
213         case 'B': case 'b': multi = 1; break;
214         case 'K': case 'k': multi = 1024; break;
215         case 'M': case 'm': multi = 1048576; break;
216         case 'G': case 'g': multi = 1073741824; break;
217             case '\0':
218                 logf (LOG_FATAL, "Missing unit: %s", ad0);
219                 return -1;
220             default:
221                 logf (LOG_FATAL, "Illegal unit: %c in %s", *ad, ad0);
222                 return -1;
223         }
224         ad++;
225         *dp = dir = (mf_dir *) xmalloc(sizeof(mf_dir));
226         dir->next = 0;
227         strcpy(dir->name, dirname);
228         dir->max_bytes = dir->avail_bytes = fact * size * multi;
229         dp = &dir->next;
230     }
231     return 0;
232 }
233
234 static int file_position(MFile mf, int pos, int offset)
235 {
236     int off = 0, c = mf->cur_file, ps;
237
238     if ((c > 0 && pos <= mf->files[c-1].top) ||
239         (c < mf->no_files -1 && pos > mf->files[c].top))
240     {
241         c = 0;
242         while (c + 1 < mf->no_files && mf->files[c].top < pos)
243         {
244             off += mf->files[c].blocks;
245             c++;
246         }
247         assert(c < mf->no_files);
248     }
249     else
250         off = c ? (mf->files[c-1].top + 1) : 0;
251     if (mf->files[c].fd < 0 && (mf->files[c].fd = open(mf->files[c].path,
252         mf->wr ? (O_BINARY|O_RDWR|O_CREAT) : (O_BINARY|O_RDONLY), 0666)) < 0)
253     {
254         if (!mf->wr && errno == ENOENT && off == 0)
255             return -2;
256         logf (LOG_WARN|LOG_ERRNO, "Failed to open %s", mf->files[c].path);
257         return -1;
258     }
259     if (lseek(mf->files[c].fd, (ps = pos - off) * mf->blocksize + offset,
260         SEEK_SET) < 0)
261     {
262         logf (LOG_WARN|LOG_ERRNO, "Failed to seek in %s", mf->files[c].path);
263         return -1;
264     }
265     mf->cur_file = c;
266     return ps;
267 }
268
269 static int cmp_part_file(const void *p1, const void *p2)
270 {
271     return ((part_file *)p1)->number - ((part_file *)p2)->number;
272 }
273
274 /*
275  * Create a new area, cotaining metafiles in directories.
276  * Find the part-files in each directory, and inventory the existing metafiles.
277  */
278 MFile_area mf_init(const char *name, const char *spec)
279 {
280     MFile_area ma = (MFile_area) xmalloc(sizeof(*ma));
281     mf_dir *dirp;
282     meta_file *meta_f;
283     part_file *part_f = 0;
284     DIR *dd;
285     struct dirent *dent;
286     int fd, number;
287     char metaname[FILENAME_MAX+1], tmpnam[FILENAME_MAX+1];
288     
289     logf (LOG_DEBUG, "mf_init(%s)", name);
290     strcpy(ma->name, name);
291     ma->mfiles = 0;
292     ma->dirs = 0;
293     if (scan_areadef(ma, name, spec) < 0)
294     {
295         logf (LOG_WARN, "Failed to access description of '%s'", name);
296         return 0;
297     }
298     /* look at each directory */
299     for (dirp = ma->dirs; dirp; dirp = dirp->next)
300     {
301         if (!(dd = opendir(dirp->name)))
302         {
303             logf (LOG_WARN|LOG_ERRNO, "Failed to open directory %s",
304                                      dirp->name);
305             return 0;
306         }
307         /* look at each file */
308         while ((dent = readdir(dd)))
309         {
310             if (*dent->d_name == '.')
311                 continue;
312             if (sscanf(dent->d_name, "%[^-]-%d.mf", metaname, &number) != 2)
313             {
314                 logf (LOG_DEBUG, "bf: %s is not a part-file.", dent->d_name);
315                 continue;
316             }
317             for (meta_f = ma->mfiles; meta_f; meta_f = meta_f->next)
318             {
319                 /* known metafile */
320                 if (!strcmp(meta_f->name, metaname))
321                 {
322                     part_f = &meta_f->files[meta_f->no_files++];
323                     break;
324                 }
325             }
326             /* new metafile */
327             if (!meta_f)
328             {
329                 meta_f = (meta_file *) xmalloc(sizeof(*meta_f));
330                 zebra_mutex_init (&meta_f->mutex);
331                 meta_f->ma = ma;
332                 meta_f->next = ma->mfiles;
333                 meta_f->open = 0;
334                 meta_f->cur_file = -1;
335                 ma->mfiles = meta_f;
336                 strcpy(meta_f->name, metaname);
337                 part_f = &meta_f->files[0];
338                 meta_f->no_files = 1;
339             }
340             part_f->number = number;
341             part_f->dir = dirp;
342             part_f->fd = -1;
343             sprintf(tmpnam, "%s/%s", dirp->name, dent->d_name);
344             part_f->path = xstrdup(tmpnam);
345             /* get size */
346             if ((fd = open(part_f->path, O_BINARY|O_RDONLY)) < 0)
347             {
348                 logf (LOG_FATAL|LOG_ERRNO, "Failed to access %s",
349                       dent->d_name);
350                 return 0;
351             }
352             if ((part_f->bytes = lseek(fd, 0, SEEK_END)) < 0)
353             {
354                 logf (LOG_FATAL|LOG_ERRNO, "Failed to seek in %s",
355                       dent->d_name);
356                 return 0;
357             }
358             close(fd);
359             if (dirp->max_bytes >= 0)
360                 dirp->avail_bytes -= part_f->bytes;
361         }
362         closedir(dd);
363     }
364     for (meta_f = ma->mfiles; meta_f; meta_f = meta_f->next)
365     {
366         logf (LOG_DEBUG, "mf_init: %s consists of %d part(s)", meta_f->name,
367               meta_f->no_files);
368         qsort(meta_f->files, meta_f->no_files, sizeof(part_file),
369               cmp_part_file);
370     }
371     return ma;
372 }
373
374 void mf_destroy(MFile_area ma)
375 {
376     mf_dir *dp;
377     meta_file *meta_f;
378
379     if (!ma)
380         return;
381     dp = ma->dirs;
382     while (dp)
383     {
384         mf_dir *d = dp;
385         dp = dp->next;
386         xfree (d);
387     }
388     meta_f = ma->mfiles;
389     while (meta_f)
390     {
391         int i;
392         meta_file *m = meta_f;
393         
394         for (i = 0; i<m->no_files; i++)
395         {
396             xfree (m->files[i].path);
397         }
398         zebra_mutex_destroy (&meta_f->mutex);
399         meta_f = meta_f->next;
400         xfree (m);
401     }
402     xfree (ma);
403 }
404
405 void mf_reset(MFile_area ma)
406 {
407     meta_file *meta_f;
408
409     if (!ma)
410         return;
411     meta_f = ma->mfiles;
412     while (meta_f)
413     {
414         int i;
415         meta_file *m = meta_f;
416
417         assert (!m->open);
418         for (i = 0; i<m->no_files; i++)
419         {
420             unlink (m->files[i].path);
421             xfree (m->files[i].path);
422         }
423         meta_f = meta_f->next;
424         xfree (m);
425     }
426     ma->mfiles = 0;
427 }
428
429 /*
430  * Open a metafile.
431  * If !ma, Use MF_DEFAULT_AREA.
432  */
433 MFile mf_open(MFile_area ma, const char *name, int block_size, int wflag)
434 {
435     meta_file *mnew;
436     int i;
437     char tmp[FILENAME_MAX+1];
438     mf_dir *dp;
439
440     logf(LOG_DEBUG, "mf_open(%s bs=%d, %s)", name, block_size,
441          wflag ? "RW" : "RDONLY");
442     assert (ma);
443     for (mnew = ma->mfiles; mnew; mnew = mnew->next)
444         if (!strcmp(name, mnew->name))
445         {
446             if (mnew->open)
447                 abort();
448             else
449                 break;
450         }
451     if (!mnew)
452     {
453         mnew = (meta_file *) xmalloc(sizeof(*mnew));
454         strcpy(mnew->name, name);
455         /* allocate one, empty file */
456         zebra_mutex_init (&mnew->mutex);
457         mnew->no_files = 1;
458         mnew->files[0].bytes = 0;
459         mnew->files[0].blocks = 0;
460         mnew->files[0].top = -1;
461         mnew->files[0].number = 0;
462         mnew->files[0].fd = -1;
463         mnew->min_bytes_creat = MF_MIN_BLOCKS_CREAT * block_size;
464         for (dp = ma->dirs; dp && dp->max_bytes >= 0 && dp->avail_bytes <
465             mnew->min_bytes_creat; dp = dp->next);
466         if (!dp)
467         {
468             logf (LOG_FATAL, "Insufficient space for new mfile.");
469             return 0;
470         }
471         mnew->files[0].dir = dp;
472         sprintf(tmp, "%s/%s-%d.mf", dp->name, mnew->name, 0);
473         mnew->files[0].path = xstrdup(tmp);
474         mnew->ma = ma;
475         mnew->next = ma->mfiles;
476         ma->mfiles = mnew;
477     }
478     else
479     {
480         for (i = 0; i < mnew->no_files; i++)
481         {
482             if (mnew->files[i].bytes % block_size)
483                 mnew->files[i].bytes += block_size - mnew->files[i].bytes %
484                     block_size;
485             mnew->files[i].blocks = mnew->files[i].bytes / block_size;
486         }
487         assert(!mnew->open);
488     }
489     mnew->blocksize = block_size;
490     mnew->min_bytes_creat = MF_MIN_BLOCKS_CREAT * block_size;
491     mnew->wr=wflag;
492     mnew->cur_file = 0;
493     mnew->open = 1;
494
495     for (i = 0; i < mnew->no_files; i++)
496     {
497         mnew->files[i].blocks = mnew->files[i].bytes / mnew->blocksize;
498         if (i == mnew->no_files - 1)
499             mnew->files[i].top = -1;
500         else
501             mnew->files[i].top =
502                 i ? (mnew->files[i-1].top + mnew->files[i].blocks)
503                 : (mnew->files[i].blocks - 1);
504     }
505     return mnew;
506 }
507
508 /*
509  * Close a metafile.
510  */
511 int mf_close(MFile mf)
512 {
513     int i;
514
515     logf (LOG_DEBUG, "mf_close(%s)", mf->name);
516     assert(mf->open);
517     for (i = 0; i < mf->no_files; i++)
518         if (mf->files[i].fd >= 0)
519         {
520             close(mf->files[i].fd);
521             mf->files[i].fd = -1;
522         }
523     mf->open = 0;
524     return 0;
525 }
526
527 /*
528  * Read one block from a metafile. Interface mirrors bfile.
529  */
530 int mf_read(MFile mf, int no, int offset, int nbytes, void *buf)
531 {
532     int rd, toread;
533
534     zebra_mutex_lock (&mf->mutex);
535     if ((rd = file_position(mf, no, offset)) < 0)
536     {
537         if (rd == -2)
538         {
539             zebra_mutex_unlock (&mf->mutex);
540             return 0;
541         }
542         else
543             exit(1);
544     }
545     toread = nbytes ? nbytes : mf->blocksize;
546     if ((rd = read(mf->files[mf->cur_file].fd, buf, toread)) < 0)
547     {
548         logf (LOG_FATAL|LOG_ERRNO, "mf_read: Read failed (%s)",
549               mf->files[mf->cur_file].path);
550         exit(1);
551     }
552     zebra_mutex_unlock (&mf->mutex);
553     if (rd < toread)
554         return 0;
555     else
556         return 1;
557 }
558
559 /*
560  * Write.
561  */
562 int mf_write(MFile mf, int no, int offset, int nbytes, const void *buf)
563 {
564     int ps, nblocks, towrite;
565     mf_dir *dp;
566     char tmp[FILENAME_MAX+1];
567     unsigned char dummych = '\xff';
568
569     zebra_mutex_lock (&mf->mutex);
570     if ((ps = file_position(mf, no, offset)) < 0)
571         exit(1);
572     /* file needs to grow */
573     while (ps >= mf->files[mf->cur_file].blocks)
574     {
575         /* file overflow - allocate new file */
576         if (mf->files[mf->cur_file].dir->max_bytes >= 0 &&
577             (ps - mf->files[mf->cur_file].blocks + 1) * mf->blocksize >
578             mf->files[mf->cur_file].dir->avail_bytes)
579         {
580             /* cap off file? */
581             if ((nblocks = mf->files[mf->cur_file].dir->avail_bytes /
582                 mf->blocksize) > 0)
583             {
584                 logf (LOG_DEBUG, "Capping off file %s at pos %d",
585                     mf->files[mf->cur_file].path, nblocks);
586                 if ((ps = file_position(mf,
587                     (mf->cur_file ? mf->files[mf->cur_file-1].top : 0) +
588                     mf->files[mf->cur_file].blocks + nblocks - 1, 0)) < 0)
589                         exit(1);
590                 logf (LOG_DEBUG, "ps = %d", ps);
591                 if (write(mf->files[mf->cur_file].fd, &dummych, 1) < 1)
592                 {
593                     logf (LOG_ERRNO|LOG_FATAL, "write dummy");
594                     exit(1);
595                 }
596                 mf->files[mf->cur_file].blocks += nblocks;
597                 mf->files[mf->cur_file].bytes += nblocks * mf->blocksize;
598                 mf->files[mf->cur_file].dir->avail_bytes -= nblocks *
599                     mf->blocksize;
600             }
601             /* get other bit */
602             logf (LOG_DEBUG, "Creating new file.");
603             for (dp = mf->ma->dirs; dp && dp->max_bytes >= 0 &&
604                 dp->avail_bytes < mf->min_bytes_creat; dp = dp->next);
605             if (!dp)
606             {
607                 logf (LOG_FATAL, "Cannot allocate more space for %s",
608                       mf->name);
609                 exit(1);
610             }
611             mf->files[mf->cur_file].top = (mf->cur_file ?
612                 mf->files[mf->cur_file-1].top : -1) +
613                 mf->files[mf->cur_file].blocks;
614             mf->files[++(mf->cur_file)].top = -1;
615             mf->files[mf->cur_file].dir = dp;
616             mf->files[mf->cur_file].number =
617                 mf->files[mf->cur_file-1].number + 1;
618             mf->files[mf->cur_file].blocks =
619                 mf->files[mf->cur_file].bytes = 0;
620             mf->files[mf->cur_file].fd = -1;
621             sprintf(tmp, "%s/%s-%d.mf", dp->name, mf->name,
622                 mf->files[mf->cur_file].number);
623             mf->files[mf->cur_file].path = xstrdup(tmp);
624             mf->no_files++;
625             /* open new file and position at beginning */
626             if ((ps = file_position(mf, no, offset)) < 0)
627                 exit(1);
628         }
629         else
630         {
631             nblocks = ps - mf->files[mf->cur_file].blocks + 1;
632             mf->files[mf->cur_file].blocks += nblocks;
633             mf->files[mf->cur_file].bytes += nblocks * mf->blocksize;
634             if (mf->files[mf->cur_file].dir->max_bytes >= 0)
635                 mf->files[mf->cur_file].dir->avail_bytes -=
636                 nblocks * mf->blocksize;
637         }
638     }
639     towrite = nbytes ? nbytes : mf->blocksize;
640     if (write(mf->files[mf->cur_file].fd, buf, towrite) < towrite)
641     {
642         logf (LOG_FATAL|LOG_ERRNO, "Write failed for file %s part %d",
643                 mf->name, mf->cur_file);
644         exit(1);
645     }
646     zebra_mutex_unlock (&mf->mutex);
647     return 0;
648 }
649
650 /*
651  * Destroy a metafile, unlinking component files. File must be open.
652  */
653 int mf_unlink(MFile mf)
654 {
655     int i;
656
657     for (i = 0; i < mf->no_files; i++)
658         unlink (mf->files[i].path);
659     return 0;
660 }
661
662 /*
663  * Unlink the file by name, rather than MFile-handle. File should be closed.
664  */
665 int mf_unlink_name(MFile_area ma, const char *name)
666 {
667     abort();
668     return 0;
669 }