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