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