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