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