Several bug fixes. Commit system runs now.
[idzebra-moved-to-github.git] / bfile / mfile.c
1 /*
2  * Copyright (C) 1994, Index Data I/S 
3  * All rights reserved.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: mfile.c,v $
7  * Revision 1.13  1995-11-30 17:00:50  adam
8  * Several bug fixes. Commit system runs now.
9  *
10  * Revision 1.12  1995/11/24  17:26:11  quinn
11  * Mostly about making some ISAM stuff in the config file optional.
12  *
13  * Revision 1.11  1995/11/13  09:32:43  quinn
14  * Comment work.
15  *
16  * Revision 1.10  1995/09/04  12:33:22  adam
17  * Various cleanup. YAZ util used instead.
18  *
19  * Revision 1.9  1994/11/04  14:26:39  quinn
20  * bug-fix.
21  *
22  * Revision 1.8  1994/10/05  16:56:42  quinn
23  * Minor.
24  *
25  * Revision 1.7  1994/09/19  14:12:37  quinn
26  * dunno.
27  *
28  * Revision 1.6  1994/09/14  13:10:15  quinn
29  * Corrected some bugs in the init-phase
30  *
31  * Revision 1.5  1994/09/12  08:01:51  quinn
32  * Small
33  *
34  * Revision 1.4  1994/09/01  14:51:07  quinn
35  * Allowed mf_write to write beyond eof+1.
36  *
37  * Revision 1.3  1994/08/24  09:37:17  quinn
38  * Changed reaction to read return values.
39  *
40  * Revision 1.2  1994/08/23  14:50:48  quinn
41  * Fixed mf_close().
42  *
43  * Revision 1.1  1994/08/23  14:41:33  quinn
44  * First functional version.
45  *
46  */
47
48
49  /*
50   * TODO: The size estimates in init may not be accurate due to
51   * only partially written final blocks.
52   */
53
54 #include <sys/types.h>
55 #include <fcntl.h>
56 #include <unistd.h>
57 #include <dirent.h>
58 #include <stdlib.h>
59 #include <stdio.h>
60 #include <assert.h>
61
62 #include <alexutil.h>
63 #include <mfile.h>
64
65 static MFile_area_struct *open_areas = 0;
66 static MFile_area_struct *default_area = 0;
67
68 static int scan_areadef(MFile_area ma, const char *name)
69 {
70     /*
71      * If no definition is given, use current directory, unlimited.
72      */
73     const char *ad = res_get_def(common_resource, name, ".:-1b");
74     int offset = 0, rs, size, multi, rd;
75     char dirname[FILENAME_MAX+1], unit; 
76     mf_dir **dp = &ma->dirs, *dir = *dp;
77
78     if (!ad)
79     {
80         logf (LOG_FATAL, "Could not find resource '%s'", name);
81         return -1;
82     }
83     for (;;)
84     {
85         rs = sscanf(ad + offset, "%[^:]:%d%c %n", dirname, &size, &unit, &rd);
86         if (rs <= 1)
87             break;
88         if (rs != 3)
89         {
90             logf (LOG_FATAL, "Illegal directory description: %s", ad + offset);
91             return -1;
92         }
93         switch (unit)
94         {
95             case 'B': case 'b': multi = 1; break;
96             case 'K': case 'k': multi = 1024; break;
97             case 'M': case 'm': multi = 1048576; break;
98             default:
99                 logf (LOG_FATAL, "Illegal unit: %c in %s", unit, ad + offset);
100                 return -1;
101         }
102         *dp = dir = xmalloc(sizeof(mf_dir));
103         dir->next = 0;
104         strcpy(dir->name, dirname);
105         dir->max_bytes = dir->avail_bytes = size * multi;
106         dp = &dir->next;
107         offset += rd;
108     }
109     return 0;
110 }
111
112 static int file_position(MFile mf, int pos, int offset)
113 {
114     int off = 0, c = mf->cur_file, ps;
115
116     if ((c > 0 && pos <= mf->files[c-1].top) ||
117         (c < mf->no_files -1 && pos > mf->files[c].top))
118     {
119         c = 0;
120         while (mf->files[c].top >= 0 && mf->files[c].top < pos)
121         {
122             off += mf->files[c].blocks;
123             c++;
124         }
125         assert(c < mf->no_files);
126     }
127     else
128         off = c ? (mf->files[c-1].top + 1) : 0;
129     if (mf->files[c].fd < 0 && (mf->files[c].fd = open(mf->files[c].path,
130         mf->wr ? O_RDWR|O_CREAT : O_RDONLY, 0666)) < 0)
131     {
132         if (!mf->wr && errno == ENOENT && off == 0)
133             return -2;
134         logf (LOG_FATAL|LOG_ERRNO, "Failed to open %s", mf->files[c].path);
135         return -1;
136     }
137     if (lseek(mf->files[c].fd, (ps = pos - off) * mf->blocksize + offset,
138         SEEK_SET) < 0)
139     {
140         logf (LOG_FATAL|LOG_ERRNO, "Failed to seek in %s", mf->files[c].path);
141         return -1;
142     }
143     mf->cur_file = c;
144     return ps;
145 }
146
147 static int cmp_part_file(const void *p1, const void *p2)
148 {
149     return ((part_file *)p1)->number - ((part_file *)p2)->number;
150 }
151
152 /*
153  * Create a new area, cotaining metafiles in directories.
154  * Find the part-files in each directory, and inventory the existing metafiles.
155  */
156 MFile_area mf_init(const char *name)
157 {
158     MFile_area ma = xmalloc(sizeof(MFile_area_struct)), mp;
159     mf_dir *dirp;
160     meta_file *meta_f;
161     part_file *part_f = 0;
162     DIR *dd;
163     struct dirent *dent;
164     int fd, number;
165     char metaname[FILENAME_MAX+1], tmpnam[FILENAME_MAX+1];
166
167     logf (LOG_DEBUG, "mf_init(%s)", name);
168     for (mp = open_areas; mp; mp = mp->next)
169         if (!strcmp(name, mp->name))
170             abort();
171     strcpy(ma->name, name);
172     ma->next = open_areas;
173     open_areas = ma;
174     ma->mfiles = 0;
175     ma->dirs = 0;
176     if (scan_areadef(ma, name) < 0)
177     {
178         logf (LOG_FATAL, "Failed to access description of '%s'", name);
179         return 0;
180     }
181     /* look at each directory */
182     for (dirp = ma->dirs; dirp; dirp = dirp->next)
183     {
184         if (!(dd = opendir(dirp->name)))
185         {
186             logf (LOG_FATAL|LOG_ERRNO, "Failed to open %s", dirp->name);
187             return 0;
188         }
189         /* look at each file */
190         while ((dent = readdir(dd)))
191         {
192             if (*dent->d_name == '.')
193                 continue;
194             if (sscanf(dent->d_name, "%[^.].mf.%d", metaname, &number) != 2)
195             {
196                 logf (LOG_DEBUG, "bf: %s is not a part-file.", dent->d_name);
197                 continue;
198             }
199             for (meta_f = ma->mfiles; meta_f; meta_f = meta_f->next)
200             {
201                 /* known metafile */
202                 if (!strcmp(meta_f->name, metaname))
203                 {
204                     part_f = &meta_f->files[meta_f->no_files++];
205                     break;
206                 }
207             }
208             /* new metafile */
209             if (!meta_f)
210             {
211                 meta_f = xmalloc(sizeof(*meta_f));
212                 meta_f->ma = ma;
213                 meta_f->next = ma->mfiles;
214                 meta_f->open = 0;
215                 meta_f->cur_file = -1;
216                 ma->mfiles = meta_f;
217                 strcpy(meta_f->name, metaname);
218                 part_f = &meta_f->files[0];
219                 meta_f->no_files = 1;
220             }
221             part_f->number = number;
222             part_f->dir = dirp;
223             part_f->fd = -1;
224             sprintf(tmpnam, "%s/%s", dirp->name, dent->d_name);
225             part_f->path = xstrdup(tmpnam);
226             /* get size */
227             if ((fd = open(part_f->path, O_RDONLY)) < 0)
228             {
229                 logf (LOG_FATAL|LOG_ERRNO, "Failed to access %s",
230                       dent->d_name);
231                 return 0;
232             }
233             if ((part_f->bytes = lseek(fd, 0, SEEK_END)) < 0)
234             {
235                 logf (LOG_FATAL|LOG_ERRNO, "Failed to seek in %s",
236                       dent->d_name);
237                 return 0;
238             }
239             close(fd);
240             if (dirp->max_bytes >= 0)
241                 dirp->avail_bytes -= part_f->bytes;
242         }
243         closedir(dd);
244     }
245     for (meta_f = ma->mfiles; meta_f; meta_f = meta_f->next)
246     {
247         logf (LOG_DEBUG, "mf_init: %s consists of %d part(s)", meta_f->name,
248               meta_f->no_files);
249         qsort(meta_f->files, meta_f->no_files, sizeof(part_file),
250               cmp_part_file);
251     }
252     return ma;
253 }
254
255 /*
256  * Open a metafile.
257  * If !ma, Use MF_DEFAULT_AREA.
258  */
259 MFile mf_open(MFile_area ma, const char *name, int block_size, int wflag)
260 {
261     struct meta_file *new;
262     int i;
263     char tmp[FILENAME_MAX+1];
264     mf_dir *dp;
265
266     logf(LOG_DEBUG, "mf_open(%s bs=%d, %s)", name, block_size,
267          wflag ? "RW" : "RDONLY");
268     if (!ma)
269     {
270         if (!default_area && !(default_area = mf_init(MF_DEFAULT_AREA)))
271         {
272             logf (LOG_FATAL, "Failed to open default area.");
273             return 0;
274         }
275         ma = default_area;
276     }
277     for (new = ma->mfiles; new; new = new->next)
278         if (!strcmp(name, new->name))
279             if (new->open)
280                 abort();
281             else
282                 break;
283     if (!new)
284     {
285         new = xmalloc(sizeof(*new));
286         strcpy(new->name, name);
287         /* allocate one, empty file */
288         new->no_files = 1;
289         new->files[0].bytes = new->files[0].blocks = 0;
290         new->files[0].top = -1;
291         new->files[0].number = 0;
292         new->files[0].fd = -1;
293         new->min_bytes_creat = MF_MIN_BLOCKS_CREAT * block_size;
294         for (dp = ma->dirs; dp && dp->max_bytes >= 0 && dp->avail_bytes <
295             new->min_bytes_creat; dp = dp->next);
296         if (!dp)
297         {
298             logf (LOG_FATAL, "Insufficient space for new mfile.");
299             return 0;
300         }
301         new->files[0].dir = dp;
302         sprintf(tmp, "%s/%s.mf.%d", dp->name, new->name, 0);
303         new->files[0].path = xstrdup(tmp);
304         new->ma = ma;
305     }
306     else
307     {
308         for (i = 0; i < new->no_files; i++)
309         {
310             if (new->files[i].bytes % block_size)
311                 new->files[i].bytes += block_size - new->files[i].bytes %
312                     block_size;
313             new->files[i].blocks = new->files[i].bytes / block_size;
314         }
315         assert(!new->open);
316     }
317     new->blocksize = block_size;
318     new->min_bytes_creat = MF_MIN_BLOCKS_CREAT * block_size;
319     new->wr=wflag;
320     new->cur_file = 0;
321     new->open = 1;
322
323     for (i = 0; i < new->no_files; i++)
324     {
325         new->files[i].blocks = new->files[i].bytes / new->blocksize;
326         if (i == new->no_files)
327             new->files[i].top = -1;
328         else
329             new->files[i].top = i ? (new->files[i-1].top + new->files[i].blocks)
330                 : (new->files[i].blocks - 1);
331     }
332     return new;
333 }
334
335 /*
336  * Close a metafile.
337  */
338 int mf_close(MFile mf)
339 {
340     int i;
341
342     logf (LOG_DEBUG, "mf_close(%s)", mf->name);
343     assert(mf->open);
344     for (i = 0; i < mf->no_files; i++)
345         if (mf->files[i].fd >= 0)
346         {
347             close(mf->files[i].fd);
348             mf->files[i].fd = -1;
349         }
350     mf->open = 0;
351     return 0;
352 }
353
354 /*
355  * Read one block from a metafile. Interface mirrors bfile.
356  */
357 int mf_read(MFile mf, int no, int offset, int num, void *buf)
358 {
359     int rd, toread;
360
361     if ((rd = file_position(mf, no, offset)) < 0)
362         if (rd == -2)
363             return 0;
364         else
365             exit(1);
366     toread = num ? num : mf->blocksize;
367     if ((rd = read(mf->files[mf->cur_file].fd, buf, toread)) < 0)
368     {
369         logf (LOG_FATAL|LOG_ERRNO, "mf_read: Read failed (%s)",
370               mf->files[mf->cur_file].path);
371         exit(1);
372     }
373     else if (rd < toread)
374         return 0;
375     else
376         return 1;
377 }
378
379 /*
380  * Write.
381  */
382 int mf_write(MFile mf, int no, int offset, int num, const void *buf)
383 {
384     int ps, nblocks, towrite;
385     mf_dir *dp;
386     char tmp[FILENAME_MAX+1];
387     unsigned char dummych = '\xff';
388
389     if ((ps = file_position(mf, no, offset)) < 0)
390         exit(1);
391     /* file needs to grow */
392     while (ps >= mf->files[mf->cur_file].blocks)
393     {
394         logf (LOG_DEBUG, "File grows");
395         /* file overflow - allocate new file */
396         if (mf->files[mf->cur_file].dir->max_bytes >= 0 &&
397             (ps - mf->files[mf->cur_file].blocks + 1) * mf->blocksize >
398             mf->files[mf->cur_file].dir->avail_bytes)
399         {
400             /* cap off file? */
401             if ((nblocks = mf->files[mf->cur_file].dir->avail_bytes /
402                 mf->blocksize) > 0)
403             {
404                 logf (LOG_DEBUG, "Capping off file %s at pos %d",
405                     mf->files[mf->cur_file].path, nblocks);
406                 if ((ps = file_position(mf,
407                     (mf->cur_file ? mf->files[mf->cur_file-1].top : 0) +
408                     mf->files[mf->cur_file].blocks + nblocks, 0)) < 0)
409                         exit(1);
410                 if (write(mf->files[mf->cur_file].fd, &dummych, 1) < 1)
411                 {
412                     logf (LOG_ERRNO|LOG_FATAL, "write dummy");
413                     exit(1);
414                 }
415                 mf->files[mf->cur_file].blocks += nblocks;
416                 mf->files[mf->cur_file].bytes += nblocks * mf->blocksize;
417                 mf->files[mf->cur_file].dir->avail_bytes -= nblocks *
418                     mf->blocksize;
419             }
420             /* get other bit */
421             logf (LOG_DEBUG, "Creating new file.");
422             for (dp = mf->ma->dirs; dp && dp->max_bytes >= 0 &&
423                 dp->avail_bytes < mf->min_bytes_creat; dp = dp->next);
424             if (!dp)
425             {
426                 logf (LOG_FATAL, "Cannot allocate more space for %s",
427                       mf->name);
428                 exit(1);
429             }
430             mf->files[mf->cur_file].top = (mf->cur_file ?
431                 mf->files[mf->cur_file-1].top : -1) +
432                 mf->files[mf->cur_file].blocks;
433             mf->files[++(mf->cur_file)].top = -1;
434             mf->files[mf->cur_file].dir = dp;
435             mf->files[mf->cur_file].number =
436                 mf->files[mf->cur_file-1].number + 1;
437             mf->files[mf->cur_file].blocks =
438                 mf->files[mf->cur_file].bytes = 0;
439             mf->files[mf->cur_file].fd = -1;
440             sprintf(tmp, "%s/%s.mf.%d", dp->name, mf->name,
441                 mf->files[mf->cur_file].number);
442             mf->files[mf->cur_file].path = xstrdup(tmp);
443             mf->no_files++;
444             /* open new file and position at beginning */
445             if ((ps = file_position(mf, no, offset)) < 0)
446                 exit(1);
447         }
448         else
449         {
450             nblocks = ps - mf->files[mf->cur_file].blocks + 1;
451             mf->files[mf->cur_file].blocks += nblocks;
452             mf->files[mf->cur_file].bytes += nblocks * mf->blocksize;
453             if (mf->files[mf->cur_file].dir->max_bytes >= 0)
454                 mf->files[mf->cur_file].dir->avail_bytes -=
455                 nblocks * mf->blocksize;
456         }
457     }
458     towrite = num ? num : mf->blocksize;
459     if (write(mf->files[mf->cur_file].fd, buf, towrite) < towrite)
460     {
461         logf (LOG_FATAL|LOG_ERRNO, "Write failed");
462         exit(1);
463     }
464     return 0;
465 }
466
467 /*
468  * Destroy a metafile, unlinking component files. File must be open.
469  */
470 int mf_unlink(MFile mf)
471 {
472     abort();
473     return 0;
474 }
475
476 /*
477  * Unlink the file by name, rather than MFile-handle. File should be closed.
478  */
479 int mf_unlink_name(MFile_area ma, const char *name)
480 {
481     abort();
482     return 0;
483 }