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