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