ecd795d6dd7931e1df8f44854d8f9952ac76a3d3
[idzebra-moved-to-github.git] / bfile / mfile.c
1 /* $Id: mfile.c,v 1.61 2005-05-17 08:50:48 adam Exp $
2    Copyright (C) 1995-2005
3    Index Data ApS
4
5 This file is part of the Zebra server.
6
7 Zebra is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
11
12 Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Zebra; see the file LICENSE.zebra.  If not, write to the
19 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20 02111-1307, USA.
21 */
22
23
24
25
26  /*
27   * TODO: The size estimates in init may not be accurate due to
28   * only partially written final blocks.
29   */
30
31 #include <sys/types.h>
32 #include <fcntl.h>
33 #ifdef WIN32
34 #include <io.h>
35 #else
36 #include <unistd.h>
37 #endif
38 #include <direntz.h>
39 #include <string.h>
40 #include <stdlib.h>
41 #include <stdio.h>
42 #include <assert.h>
43 #include <errno.h>
44
45 #include <zebra-lock.h>
46 #include <idzebra/util.h>
47 #include <yaz/yaz-util.h>
48 #include "mfile.h"
49
50 static int scan_areadef(MFile_area ma, const char *ad, const char *base)
51 {
52     /*
53      * If no definition is given, use current directory, unlimited.
54      */
55     char dirname[FILENAME_MAX+1]; 
56     mf_dir **dp = &ma->dirs, *dir = *dp;
57
58     if (!ad)
59         ad = ".:-1b";
60     for (;;)
61     {
62         const char *ad0 = ad;
63         int i = 0, fact = 1, multi;
64         mfile_off_t size = 0;
65
66         while (*ad == ' ' || *ad == '\t')
67             ad++;
68         if (!*ad)
69             break;
70         if (!yaz_is_abspath(ad) && base)
71         {
72             strcpy (dirname, base);
73             i = strlen(dirname);
74             dirname[i++] = '/';
75         }
76         while (*ad)
77         {
78             if (*ad == ':' && strchr ("+-0123456789", ad[1]))
79                 break;
80             if (i < FILENAME_MAX)
81                 dirname[i++] = *ad;
82             ad++;
83         }
84         dirname[i] = '\0';
85         if (*ad++ != ':')
86         {
87             yaz_log (YLOG_WARN, "Missing colon after path: %s", ad0);
88             return -1;
89         }
90         if (i == 0)
91         {
92             yaz_log (YLOG_WARN, "Empty path: %s", ad0);
93             return -1;
94         }
95         while (*ad == ' ' || *ad == '\t')
96             ad++;
97         if (*ad == '-')
98         {
99             fact = -1;
100             ad++;
101         }
102         else if (*ad == '+')
103             ad++;
104         size = 0;
105         if (*ad < '0' || *ad > '9')
106         {
107             yaz_log (YLOG_FATAL, "Missing size after path: %s", ad0);
108             return -1;
109         }
110         size = 0;
111         while (*ad >= '0' && *ad <= '9')
112             size = size*10 + (*ad++ - '0');
113         switch (*ad)
114         {
115         case 'B': case 'b': multi = 1; break;
116         case 'K': case 'k': multi = 1024; break;
117         case 'M': case 'm': multi = 1048576; break;
118         case 'G': case 'g': multi = 1073741824; break;
119         case '\0':
120             yaz_log (YLOG_FATAL, "Missing unit: %s", ad0);
121             return -1;
122         default:
123             yaz_log (YLOG_FATAL, "Illegal unit: %c in %s", *ad, ad0);
124             return -1;
125         }
126         ad++;
127         *dp = dir = (mf_dir *) xmalloc(sizeof(mf_dir));
128         dir->next = 0;
129         strcpy(dir->name, dirname);
130         dir->max_bytes = dir->avail_bytes = fact * size * multi;
131         dp = &dir->next;
132     }
133     return 0;
134 }
135
136 static zint file_position(MFile mf, zint pos, int offset)
137 {
138     zint off = 0, ps;
139     int c = mf->cur_file;
140
141     if ((c > 0 && pos <= mf->files[c-1].top) ||
142         (c < mf->no_files -1 && pos > mf->files[c].top))
143     {
144         c = 0;
145         while (c + 1 < mf->no_files && mf->files[c].top < pos)
146         {
147             off += mf->files[c].blocks;
148             c++;
149         }
150         assert(c < mf->no_files);
151     }
152     else
153         off = c ? (mf->files[c-1].top + 1) : 0;
154     if (mf->files[c].fd < 0)
155     {
156         if ((mf->files[c].fd = open(mf->files[c].path,
157                                     mf->wr ?
158                                         (O_BINARY|O_RDWR|O_CREAT) :
159                                         (O_BINARY|O_RDONLY), 0666)) < 0)
160         {
161             if (!mf->wr && errno == ENOENT && off == 0)
162                 return -2;
163             yaz_log (YLOG_WARN|YLOG_ERRNO, "Failed to open %s", mf->files[c].path);
164             return -1;
165         }
166     }
167     ps = pos - off;
168     if (mfile_seek(mf->files[c].fd, ps * (mfile_off_t) mf->blocksize + offset,
169         SEEK_SET) < 0)
170     {
171         yaz_log (YLOG_WARN|YLOG_ERRNO, "Failed to seek in %s", mf->files[c].path);
172         yaz_log(YLOG_WARN, "pos=" ZINT_FORMAT " off=" ZINT_FORMAT " blocksize=%d offset=%d",
173                        pos, off, mf->blocksize, offset);
174         return -1;
175     }
176     mf->cur_file = c;
177     return ps;
178 }
179
180 static int cmp_part_file(const void *p1, const void *p2)
181 {
182     zint d = ((part_file *)p1)->number - ((part_file *)p2)->number;
183     if (d > 0)
184         return 1;
185     if (d < 0)
186         return -1;
187     return 0;
188 }
189
190 /*
191  * Create a new area, cotaining metafiles in directories.
192  * Find the part-files in each directory, and inventory the existing metafiles.
193  */
194 MFile_area mf_init(const char *name, const char *spec, const char *base)
195 {
196     MFile_area ma = (MFile_area) xmalloc(sizeof(*ma));
197     mf_dir *dirp;
198     meta_file *meta_f;
199     part_file *part_f = 0;
200     DIR *dd;
201     struct dirent *dent;
202     int fd, number;
203     char metaname[FILENAME_MAX+1], tmpnam[FILENAME_MAX+1];
204     
205     yaz_log (YLOG_DEBUG, "mf_init(%s)", name);
206     strcpy(ma->name, name);
207     ma->mfiles = 0;
208     ma->dirs = 0;
209     if (scan_areadef(ma, spec, base) < 0)
210     {
211         yaz_log (YLOG_WARN, "Failed to access description of '%s'", name);
212         return 0;
213     }
214     /* look at each directory */
215     for (dirp = ma->dirs; dirp; dirp = dirp->next)
216     {
217         if (!(dd = opendir(dirp->name)))
218         {
219             yaz_log (YLOG_WARN|YLOG_ERRNO, "Failed to open directory %s",
220                                      dirp->name);
221             return 0;
222         }
223         /* look at each file */
224         while ((dent = readdir(dd)))
225         {
226             int len = strlen(dent->d_name);
227             const char *cp = strrchr (dent->d_name, '-');
228             if (strchr (".-", *dent->d_name))
229                 continue;
230             if (len < 5 || !cp || strcmp (dent->d_name + len - 3, ".mf"))
231                 continue;
232             number = atoi(cp+1);
233             memcpy (metaname, dent->d_name, cp - dent->d_name);
234             metaname[ cp - dent->d_name] = '\0';
235
236             for (meta_f = ma->mfiles; meta_f; meta_f = meta_f->next)
237             {
238                 /* known metafile */
239                 if (!strcmp(meta_f->name, metaname))
240                 {
241                     part_f = &meta_f->files[meta_f->no_files++];
242                     break;
243                 }
244             }
245             /* new metafile */
246             if (!meta_f)
247             {
248                 meta_f = (meta_file *) xmalloc(sizeof(*meta_f));
249                 zebra_mutex_init (&meta_f->mutex);
250                 meta_f->ma = ma;
251                 meta_f->next = ma->mfiles;
252                 meta_f->open = 0;
253                 meta_f->cur_file = -1;
254                 meta_f->unlink_flag = 0;
255                 ma->mfiles = meta_f;
256                 strcpy(meta_f->name, metaname);
257                 part_f = &meta_f->files[0];
258                 meta_f->no_files = 1;
259             }
260             part_f->number = number;
261             part_f->dir = dirp;
262             part_f->fd = -1;
263             sprintf(tmpnam, "%s/%s", dirp->name, dent->d_name);
264             part_f->path = xstrdup(tmpnam);
265             /* get size */
266             if ((fd = open(part_f->path, O_BINARY|O_RDONLY)) < 0)
267             {
268                 yaz_log (YLOG_FATAL|YLOG_ERRNO, "Failed to access %s",
269                       dent->d_name);
270                 return 0;
271             }
272             if ((part_f->bytes = mfile_seek(fd, 0, SEEK_END)) < 0)
273             {
274                 yaz_log (YLOG_FATAL|YLOG_ERRNO, "Failed to seek in %s",
275                       dent->d_name);
276                 return 0;
277             }
278 #ifndef WIN32
279             fsync(fd);
280 #endif
281             close(fd);
282             if (dirp->max_bytes >= 0)
283                 dirp->avail_bytes -= part_f->bytes;
284         }
285         closedir(dd);
286     }
287     for (meta_f = ma->mfiles; meta_f; meta_f = meta_f->next)
288     {
289         yaz_log (YLOG_DEBUG, "mf_init: %s consists of %d part(s)", meta_f->name,
290               meta_f->no_files);
291         qsort(meta_f->files, meta_f->no_files, sizeof(part_file),
292               cmp_part_file);
293     }
294     return ma;
295 }
296
297 void mf_destroy(MFile_area ma)
298 {
299     mf_dir *dp;
300     meta_file *meta_f;
301
302     if (!ma)
303         return;
304     dp = ma->dirs;
305     while (dp)
306     {
307         mf_dir *d = dp;
308         dp = dp->next;
309         xfree (d);
310     }
311     meta_f = ma->mfiles;
312     while (meta_f)
313     {
314         int i;
315         meta_file *m = meta_f;
316         
317         for (i = 0; i<m->no_files; i++)
318         {
319             xfree (m->files[i].path);
320         }
321         zebra_mutex_destroy (&meta_f->mutex);
322         meta_f = meta_f->next;
323         xfree (m);
324     }
325     xfree (ma);
326 }
327
328 void mf_reset(MFile_area ma)
329 {
330     meta_file *meta_f;
331
332     if (!ma)
333         return;
334     meta_f = ma->mfiles;
335     while (meta_f)
336     {
337         int i;
338         meta_file *m = meta_f;
339
340         assert (!m->open);
341         for (i = 0; i<m->no_files; i++)
342         {
343             unlink (m->files[i].path);
344             xfree (m->files[i].path);
345         }
346         meta_f = meta_f->next;
347         xfree (m);
348     }
349     ma->mfiles = 0;
350 }
351
352 /*
353  * Open a metafile.
354  * If !ma, Use MF_DEFAULT_AREA.
355  */
356 MFile mf_open(MFile_area ma, const char *name, int block_size, int wflag)
357 {
358     meta_file *mnew;
359     int i;
360     char tmp[FILENAME_MAX+1];
361     mf_dir *dp;
362
363     yaz_log(YLOG_DEBUG, "mf_open(%s bs=%d, %s)", name, block_size,
364          wflag ? "RW" : "RDONLY");
365     assert (ma);
366     for (mnew = ma->mfiles; mnew; mnew = mnew->next)
367         if (!strcmp(name, mnew->name))
368         {
369             if (mnew->open)
370                 abort();
371             else
372                 break;
373         }
374     if (!mnew)
375     {
376         mnew = (meta_file *) xmalloc(sizeof(*mnew));
377         strcpy(mnew->name, name);
378         /* allocate one, empty file */
379         zebra_mutex_init (&mnew->mutex);
380         mnew->no_files = 1;
381         mnew->files[0].bytes = 0;
382         mnew->files[0].blocks = 0;
383         mnew->files[0].top = -1;
384         mnew->files[0].number = 0;
385         mnew->files[0].fd = -1;
386         mnew->unlink_flag = 0;
387         mnew->min_bytes_creat = MF_MIN_BLOCKS_CREAT * block_size;
388         for (dp = ma->dirs; dp && dp->max_bytes >= 0 && dp->avail_bytes <
389             mnew->min_bytes_creat; dp = dp->next);
390         if (!dp)
391         {
392             yaz_log (YLOG_FATAL, "Insufficient space for new mfile.");
393             return 0;
394         }
395         mnew->files[0].dir = dp;
396         sprintf(tmp, "%s/%s-%d.mf", dp->name, mnew->name, 0);
397         mnew->files[0].path = xstrdup(tmp);
398         mnew->ma = ma;
399         mnew->next = ma->mfiles;
400         ma->mfiles = mnew;
401     }
402     else
403     {
404         for (i = 0; i < mnew->no_files; i++)
405         {
406             if (mnew->files[i].bytes % block_size)
407                 mnew->files[i].bytes += block_size - mnew->files[i].bytes %
408                     block_size;
409             mnew->files[i].blocks = (int) (mnew->files[i].bytes / block_size);
410         }
411         assert(!mnew->open);
412     }
413     mnew->blocksize = block_size;
414     mnew->min_bytes_creat = MF_MIN_BLOCKS_CREAT * block_size;
415     mnew->wr=wflag;
416     mnew->cur_file = 0;
417     mnew->open = 1;
418
419     for (i = 0; i < mnew->no_files; i++)
420     {
421         mnew->files[i].blocks = (int)(mnew->files[i].bytes / mnew->blocksize);
422         if (i == mnew->no_files - 1)
423             mnew->files[i].top = -1;
424         else
425             mnew->files[i].top =
426                 i ? (mnew->files[i-1].top + mnew->files[i].blocks)
427                 : (mnew->files[i].blocks - 1);
428     }
429     return mnew;
430 }
431
432 /*
433  * Close a metafile.
434  */
435 int mf_close(MFile mf)
436 {
437     int i;
438
439     yaz_log (YLOG_DEBUG, "mf_close(%s)", mf->name);
440     assert(mf->open);
441     for (i = 0; i < mf->no_files; i++)
442     {
443         if (mf->files[i].fd >= 0)
444         {
445 #ifndef WIN32
446             fsync(mf->files[i].fd);
447 #endif
448             close(mf->files[i].fd);
449             mf->files[i].fd = -1;
450         }
451         if (mf->unlink_flag)
452             unlink(mf->files[i].path);
453     }
454     mf->open = 0;
455     return 0;
456 }
457
458 /*
459  * Read one block from a metafile. Interface mirrors bfile.
460  */
461 int mf_read(MFile mf, zint no, int offset, int nbytes, void *buf)
462 {
463     zint rd;
464     int toread;
465
466     zebra_mutex_lock (&mf->mutex);
467     if ((rd = file_position(mf, no, offset)) < 0)
468     {
469         if (rd == -2)
470         {
471             zebra_mutex_unlock (&mf->mutex);
472             return 0;
473         }
474         else
475         {
476             yaz_log (YLOG_FATAL, "mf_read %s internal error", mf->name);
477             exit(1);
478         }
479     }
480     toread = nbytes ? nbytes : mf->blocksize;
481     if ((rd = read(mf->files[mf->cur_file].fd, buf, toread)) < 0)
482     {
483         yaz_log (YLOG_FATAL|YLOG_ERRNO, "mf_read: Read failed (%s)",
484               mf->files[mf->cur_file].path);
485         exit(1);
486     }
487     zebra_mutex_unlock (&mf->mutex);
488     if (rd < toread)
489         return 0;
490     else
491         return 1;
492 }
493
494 /*
495  * Write.
496  */
497 int mf_write(MFile mf, zint no, int offset, int nbytes, const void *buf)
498 {
499     zint ps;
500     zint nblocks;
501     int towrite;
502     mf_dir *dp;
503     char tmp[FILENAME_MAX+1];
504     unsigned char dummych = '\xff';
505
506     zebra_mutex_lock (&mf->mutex);
507     if ((ps = file_position(mf, no, offset)) < 0)
508     {
509         yaz_log (YLOG_FATAL, "mf_write %s internal error (1)", mf->name);
510         exit(1);
511     }
512     /* file needs to grow */
513     while (ps >= mf->files[mf->cur_file].blocks)
514     {
515         mfile_off_t needed = (ps - mf->files[mf->cur_file].blocks + 1) *
516                        mf->blocksize;
517         /* file overflow - allocate new file */
518         if (mf->files[mf->cur_file].dir->max_bytes >= 0 &&
519             needed > mf->files[mf->cur_file].dir->avail_bytes)
520         {
521             /* cap off file? */
522             if ((nblocks = (int) (mf->files[mf->cur_file].dir->avail_bytes /
523                 mf->blocksize)) > 0)
524             {
525                 yaz_log (YLOG_DEBUG, "Capping off file %s at pos " ZINT_FORMAT,
526                     mf->files[mf->cur_file].path, nblocks);
527                 if ((ps = file_position(mf,
528                     (mf->cur_file ? mf->files[mf->cur_file-1].top : 0) +
529                     mf->files[mf->cur_file].blocks + nblocks - 1, 0)) < 0)
530                 {
531                     yaz_log (YLOG_FATAL, "mf_write %s internal error (2)",
532                                  mf->name);
533                     exit(1);
534                 }
535                 yaz_log (YLOG_DEBUG, "ps = " ZINT_FORMAT, ps);
536                 if (write(mf->files[mf->cur_file].fd, &dummych, 1) < 1)
537                 {
538                     yaz_log (YLOG_ERRNO|YLOG_FATAL, "mf_write %s internal error (3)",
539                                       mf->name);
540                     exit(1);
541                 }
542                 mf->files[mf->cur_file].blocks += nblocks;
543                 mf->files[mf->cur_file].bytes += nblocks * mf->blocksize;
544                 mf->files[mf->cur_file].dir->avail_bytes -= nblocks *
545                     mf->blocksize;
546             }
547             /* get other bit */
548             yaz_log (YLOG_DEBUG, "Creating new file.");
549             for (dp = mf->ma->dirs; dp && dp->max_bytes >= 0 &&
550                 dp->avail_bytes < needed; dp = dp->next);
551             if (!dp)
552             {
553                 yaz_log (YLOG_FATAL, "Cannot allocate more space for %s",
554                       mf->name);
555                 exit(1);
556             }
557             mf->files[mf->cur_file].top = (mf->cur_file ?
558                 mf->files[mf->cur_file-1].top : -1) +
559                 mf->files[mf->cur_file].blocks;
560             mf->files[++(mf->cur_file)].top = -1;
561             mf->files[mf->cur_file].dir = dp;
562             mf->files[mf->cur_file].number =
563                 mf->files[mf->cur_file-1].number + 1;
564             mf->files[mf->cur_file].blocks = 0;
565             mf->files[mf->cur_file].bytes = 0;
566             mf->files[mf->cur_file].fd = -1;
567             sprintf(tmp, "%s/%s-" ZINT_FORMAT ".mf", dp->name, mf->name,
568                 mf->files[mf->cur_file].number);
569             mf->files[mf->cur_file].path = xstrdup(tmp);
570             mf->no_files++;
571             /* open new file and position at beginning */
572             if ((ps = file_position(mf, no, offset)) < 0)
573             {
574                 yaz_log (YLOG_FATAL, "mf_write %s internal error (4)",
575                                  mf->name);
576                 exit(1);
577             }   
578         }
579         else
580         {
581             nblocks = ps - mf->files[mf->cur_file].blocks + 1;
582             mf->files[mf->cur_file].blocks += nblocks;
583             mf->files[mf->cur_file].bytes += nblocks * mf->blocksize;
584             if (mf->files[mf->cur_file].dir->max_bytes >= 0)
585                 mf->files[mf->cur_file].dir->avail_bytes -=
586                 nblocks * mf->blocksize;
587         }
588     }
589     towrite = nbytes ? nbytes : mf->blocksize;
590     if (write(mf->files[mf->cur_file].fd, buf, towrite) < towrite)
591     {
592         yaz_log (YLOG_FATAL|YLOG_ERRNO, "Write failed for file %s part %d",
593                 mf->name, mf->cur_file);
594         exit(1);
595     }
596     zebra_mutex_unlock (&mf->mutex);
597     return 0;
598 }
599
600 /*
601  * Destroy a metafile, unlinking component files. File must be open.
602  */
603 int mf_unlink(MFile mf)
604 {
605     if (mf->open)
606         mf->unlink_flag = 1;
607     else
608     {
609         int i;
610         for (i = 0; i<mf->no_files; i++)
611             unlink(mf->files[i].path);
612     }
613     return 0;
614 }
615
616 /*
617  * Unlink the file by name, rather than MFile-handle. File should be closed.
618  */
619 int mf_unlink_name(MFile_area ma, const char *name)
620 {
621     abort();
622     return 0;
623 }