9d8a3fbed5036f9693c42c245757f51b6f535e98
[idzebra-moved-to-github.git] / bfile / mfile.c
1 /* $Id: mfile.c,v 1.70 2006-11-08 22:08:27 adam Exp $
2    Copyright (C) 1995-2006
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 this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
21 */
22
23 #include <sys/types.h>
24 #include <fcntl.h>
25 #ifdef WIN32
26 #include <io.h>
27 #endif
28 #if HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
31 #include <direntz.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <assert.h>
36 #include <errno.h>
37
38 #include <zebra-lock.h>
39 #include <idzebra/util.h>
40 #include <yaz/yaz-util.h>
41 #include "mfile.h"
42
43 static int scan_areadef(MFile_area ma, const char *ad, const char *base)
44 {
45     /*
46      * If no definition is given, use current directory, unlimited.
47      */
48     char dirname[FILENAME_MAX+1]; 
49     mf_dir **dp = &ma->dirs, *dir = *dp;
50
51     if (!ad)
52         ad = ".:-1b";
53     for (;;)
54     {
55         const char *ad0 = ad;
56         int i = 0, fact = 1, multi;
57         mfile_off_t size = 0;
58
59         while (*ad == ' ' || *ad == '\t')
60             ad++;
61         if (!*ad)
62             break;
63         if (!yaz_is_abspath(ad) && base)
64         {
65             strcpy(dirname, base);
66             i = strlen(dirname);
67             dirname[i++] = '/';
68         }
69         while (*ad)
70         {
71             if (*ad == ':' && strchr("+-0123456789", ad[1]))
72                 break;
73             if (i < FILENAME_MAX)
74                 dirname[i++] = *ad;
75             ad++;
76         }
77         dirname[i] = '\0';
78         if (*ad++ != ':')
79         {
80             yaz_log(YLOG_WARN, "Missing colon after path: %s", ad0);
81             return -1;
82         }
83         if (i == 0)
84         {
85             yaz_log(YLOG_WARN, "Empty path: %s", ad0);
86             return -1;
87         }
88         while (*ad == ' ' || *ad == '\t')
89             ad++;
90         if (*ad == '-')
91         {
92             fact = -1;
93             ad++;
94         }
95         else if (*ad == '+')
96             ad++;
97         size = 0;
98         if (*ad < '0' || *ad > '9')
99         {
100             yaz_log(YLOG_FATAL, "Missing size after path: %s", ad0);
101             return -1;
102         }
103         size = 0;
104         while (*ad >= '0' && *ad <= '9')
105             size = size*10 + (*ad++ - '0');
106         switch (*ad)
107         {
108         case 'B': case 'b': multi = 1; break;
109         case 'K': case 'k': multi = 1024; break;
110         case 'M': case 'm': multi = 1048576; break;
111         case 'G': case 'g': multi = 1073741824; break;
112         case '\0':
113             yaz_log(YLOG_FATAL, "Missing unit: %s", ad0);
114             return -1;
115         default:
116             yaz_log(YLOG_FATAL, "Illegal unit: %c in %s", *ad, ad0);
117             return -1;
118         }
119         ad++;
120         *dp = dir = (mf_dir *) xmalloc(sizeof(mf_dir));
121         dir->next = 0;
122         strcpy(dir->name, dirname);
123         dir->max_bytes = dir->avail_bytes = fact * size * multi;
124         dp = &dir->next;
125     }
126     return 0;
127 }
128
129 /** \brief position within metafile (perform seek)
130     \param mf metafile handle
131     \param pos block position
132     \param offset offset within block
133     \retval 0 OK
134     \retval -1 ERROR
135     \retval -2 OK, but file does not created (read-only)
136 */
137    
138 static zint file_position(MFile mf, zint pos, int offset)
139 {
140     zint off = 0, ps;
141     int c = mf->cur_file;
142
143     if ((c > 0 && pos <= mf->files[c-1].top) ||
144         (c < mf->no_files -1 && pos > mf->files[c].top))
145     {
146         c = 0;
147         while (c + 1 < mf->no_files && mf->files[c].top < pos)
148         {
149             off += mf->files[c].blocks;
150             c++;
151         }
152         assert(c < mf->no_files);
153     }
154     else
155         off = c ? (mf->files[c-1].top + 1) : 0;
156     if (mf->files[c].fd < 0)
157     {
158         if ((mf->files[c].fd = open(mf->files[c].path,
159                                     mf->wr ?
160                                         (O_BINARY|O_RDWR|O_CREAT) :
161                                         (O_BINARY|O_RDONLY), 0666)) < 0)
162         {
163             if (!mf->wr && errno == ENOENT && off == 0)
164             {
165                 /* we can't open it for reading. But not really an error */
166                 return -2;
167             }
168             yaz_log(YLOG_WARN|YLOG_ERRNO, "Failed to open %s", mf->files[c].path);
169             return -1;
170         }
171     }
172     ps = pos - off;
173     if (mfile_seek(mf->files[c].fd, ps *(mfile_off_t) mf->blocksize + offset,
174         SEEK_SET) < 0)
175     {
176         yaz_log(YLOG_WARN|YLOG_ERRNO, "Failed to seek in %s", mf->files[c].path);
177         yaz_log(YLOG_WARN, "pos=" ZINT_FORMAT " off=" ZINT_FORMAT " blocksize=%d offset=%d",
178                        pos, off, mf->blocksize, offset);
179         return -1;
180     }
181     mf->cur_file = c;
182     return ps;
183 }
184
185 static int cmp_part_file(const void *p1, const void *p2)
186 {
187     zint d = ((part_file *)p1)->number - ((part_file *)p2)->number;
188     if (d > 0)
189         return 1;
190     if (d < 0)
191         return -1;
192     return 0;
193 }
194
195 /** \brief creates a metafile area
196     \param name of area (does not show up on disk - purely for notation)
197     \param spec area specification (e.g. "/a:1G dir /b:2000M"
198     \param base base directory (NULL for no base)
199     \returns metafile area handle or NULL if error occurs
200 */
201 MFile_area mf_init(const char *name, const char *spec, const char *base)
202 {
203     MFile_area ma = (MFile_area) xmalloc(sizeof(*ma));
204     mf_dir *dirp;
205     meta_file *meta_f;
206     part_file *part_f = 0;
207     DIR *dd;
208     struct dirent *dent;
209     int fd, number;
210     char metaname[FILENAME_MAX+1], tmpnam[FILENAME_MAX+1];
211     
212     yaz_log(YLOG_DEBUG, "mf_init(%s)", name);
213     strcpy(ma->name, name);
214     ma->mfiles = 0;
215     ma->dirs = 0;
216     if (scan_areadef(ma, spec, base) < 0)
217     {
218         yaz_log(YLOG_WARN, "Failed to access description of '%s'", name);
219         return 0;
220     }
221     /* look at each directory */
222     for (dirp = ma->dirs; dirp; dirp = dirp->next)
223     {
224         if (!(dd = opendir(dirp->name)))
225         {
226             yaz_log(YLOG_WARN|YLOG_ERRNO, "Failed to open directory %s",
227                                      dirp->name);
228             return 0;
229         }
230         /* look at each file */
231         while ((dent = readdir(dd)))
232         {
233             int len = strlen(dent->d_name);
234             const char *cp = strrchr(dent->d_name, '-');
235             if (strchr(".-", *dent->d_name))
236                 continue;
237             if (len < 5 || !cp || strcmp(dent->d_name + len - 3, ".mf"))
238                 continue;
239             number = atoi(cp+1);
240             memcpy(metaname, dent->d_name, cp - dent->d_name);
241             metaname[ cp - dent->d_name] = '\0';
242
243             for (meta_f = ma->mfiles; meta_f; meta_f = meta_f->next)
244             {
245                 /* known metafile */
246                 if (!strcmp(meta_f->name, metaname))
247                 {
248                     part_f = &meta_f->files[meta_f->no_files++];
249                     break;
250                 }
251             }
252             /* new metafile */
253             if (!meta_f)
254             {
255                 meta_f = (meta_file *) xmalloc(sizeof(*meta_f));
256                 zebra_mutex_init(&meta_f->mutex);
257                 meta_f->ma = ma;
258                 meta_f->next = ma->mfiles;
259                 meta_f->open = 0;
260                 meta_f->cur_file = -1;
261                 ma->mfiles = meta_f;
262                 strcpy(meta_f->name, metaname);
263                 part_f = &meta_f->files[0];
264                 meta_f->no_files = 1;
265             }
266             part_f->number = number;
267             part_f->dir = dirp;
268             part_f->fd = -1;
269             sprintf(tmpnam, "%s/%s", dirp->name, dent->d_name);
270             part_f->path = xstrdup(tmpnam);
271             /* get size */
272             if ((fd = open(part_f->path, O_BINARY|O_RDONLY)) < 0)
273             {
274                 yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to access %s",
275                       dent->d_name);
276                 return 0;
277             }
278             if ((part_f->bytes = mfile_seek(fd, 0, SEEK_END)) < 0)
279             {
280                 yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to seek in %s",
281                       dent->d_name);
282                 return 0;
283             }
284 #ifndef WIN32
285             fsync(fd);
286 #endif
287             close(fd);
288             if (dirp->max_bytes >= 0)
289                 dirp->avail_bytes -= part_f->bytes;
290         }
291         closedir(dd);
292     }
293     for (meta_f = ma->mfiles; meta_f; meta_f = meta_f->next)
294     {
295         yaz_log(YLOG_DEBUG, "mf_init: %s consists of %d part(s)", meta_f->name,
296               meta_f->no_files);
297         qsort(meta_f->files, meta_f->no_files, sizeof(part_file),
298               cmp_part_file);
299     }
300     return ma;
301 }
302
303 /** \brief destroys metafile area handle
304     \param ma metafile area handle
305 */
306 void mf_destroy(MFile_area ma)
307 {
308     mf_dir *dp;
309
310     if (!ma)
311         return;
312     dp = ma->dirs;
313     while (dp)
314     {
315         mf_dir *d = dp;
316         dp = dp->next;
317         xfree(d);
318     }
319     mf_reset(ma, 0);
320     xfree(ma);
321 }
322
323 /** \brief reset all files in a metafile area (optionally delete them as well)
324     \param ma metafile area
325     \param unlink_flag if unlink_flag=1 all files are removed from FS
326 */
327 void mf_reset(MFile_area ma, int unlink_flag)
328 {
329     meta_file *meta_f;
330
331     if (!ma)
332         return;
333     meta_f = ma->mfiles;
334     while (meta_f)
335     {
336         int i;
337         meta_file *m = meta_f;
338
339         meta_f = meta_f->next;
340
341         assert(!m->open);
342         for (i = 0; i<m->no_files; i++)
343         {
344             if (unlink_flag)
345                 unlink(m->files[i].path);
346             xfree(m->files[i].path);
347         }
348         zebra_mutex_destroy(&m->mutex);
349         xfree(m);
350     }
351     ma->mfiles = 0;
352 }
353
354 /** \brief opens metafile
355     \param ma metafile area handle
356     \param name pseudo filename (name*.mf)
357     \param block_size block size for this file
358     \param wflag write flag, 0=read, 1=write&read
359     \returns metafile handle, or NULL for error (could not be opened)
360  */
361 MFile mf_open(MFile_area ma, const char *name, int block_size, int wflag)
362 {
363     meta_file *mnew;
364     int i;
365     char tmp[FILENAME_MAX+1];
366     mf_dir *dp;
367
368     yaz_log(YLOG_DEBUG, "mf_open(%s bs=%d, %s)", name, block_size,
369          wflag ? "RW" : "RDONLY");
370     assert(ma);
371     for (mnew = ma->mfiles; mnew; mnew = mnew->next)
372         if (!strcmp(name, mnew->name))
373         {
374             if (mnew->open)
375             {
376                 yaz_log(YLOG_WARN, "metafile %s already open", name);
377                 return 0;
378             }
379         }
380     if (!mnew)
381     {
382         mnew = (meta_file *) xmalloc(sizeof(*mnew));
383         strcpy(mnew->name, name);
384         /* allocate one, empty file */
385         zebra_mutex_init(&mnew->mutex);
386         mnew->no_files = 1;
387         mnew->files[0].bytes = 0;
388         mnew->files[0].blocks = 0;
389         mnew->files[0].top = -1;
390         mnew->files[0].number = 0;
391         mnew->files[0].fd = -1;
392         mnew->min_bytes_creat = MF_MIN_BLOCKS_CREAT * block_size;
393         for (dp = ma->dirs; dp && dp->max_bytes >= 0 && dp->avail_bytes <
394             mnew->min_bytes_creat; dp = dp->next);
395         if (!dp)
396         {
397             yaz_log(YLOG_FATAL, "Insufficient space for new mfile.");
398             return 0;
399         }
400         mnew->files[0].dir = dp;
401         sprintf(tmp, "%s/%s-%d.mf", dp->name, mnew->name, 0);
402         mnew->files[0].path = xstrdup(tmp);
403         mnew->ma = ma;
404         mnew->next = ma->mfiles;
405         ma->mfiles = mnew;
406     }
407     else
408     {
409         for (i = 0; i < mnew->no_files; i++)
410         {
411             if (mnew->files[i].bytes % block_size)
412                 mnew->files[i].bytes += block_size - mnew->files[i].bytes %
413                     block_size;
414             mnew->files[i].blocks = (int) (mnew->files[i].bytes / block_size);
415         }
416         assert(!mnew->open);
417     }
418     mnew->blocksize = block_size;
419     mnew->min_bytes_creat = MF_MIN_BLOCKS_CREAT * block_size;
420     mnew->wr=wflag;
421     mnew->cur_file = 0;
422     mnew->open = 1;
423
424     for (i = 0; i < mnew->no_files; i++)
425     {
426         mnew->files[i].blocks = (int)(mnew->files[i].bytes / mnew->blocksize);
427         if (i == mnew->no_files - 1)
428             mnew->files[i].top = -1;
429         else
430             mnew->files[i].top =
431                 i ? (mnew->files[i-1].top + mnew->files[i].blocks)
432                 : (mnew->files[i].blocks - 1);
433     }
434     return mnew;
435 }
436
437 /** \brief closes metafile
438     \param mf metafile handle
439     \retval 0 OK
440 */
441 int mf_close(MFile mf)
442 {
443     int i;
444
445     yaz_log(YLOG_DEBUG, "mf_close(%s)", mf->name);
446     assert(mf->open);
447     for (i = 0; i < mf->no_files; i++)
448     {
449         if (mf->files[i].fd >= 0)
450         {
451 #ifndef WIN32
452             fsync(mf->files[i].fd);
453 #endif
454             close(mf->files[i].fd);
455             mf->files[i].fd = -1;
456         }
457     }
458     mf->open = 0;
459     return 0;
460 }
461
462 /** \brief reads block from metafile
463     \param mf metafile handle
464     \param no block position
465     \param offset offset within block
466     \param nbytes no of bytes to read (0 for whole block)
467     \param buf content (filled with data if OK)
468     \retval 0 block partially read
469     \retval 1 block fully read
470     \retval -1 block could not be read due to error
471  */
472 int mf_read(MFile mf, zint no, int offset, int nbytes, void *buf)
473 {
474     zint rd;
475     int toread;
476
477     zebra_mutex_lock(&mf->mutex);
478     if ((rd = file_position(mf, no, offset)) < 0)
479     {
480         if (rd == -2)
481         {
482             zebra_mutex_unlock(&mf->mutex);
483             return 0;
484         }
485         else
486         {
487             yaz_log(YLOG_FATAL, "mf_read2 %s internal error", mf->name);
488             return -1;
489         }
490     }
491     toread = nbytes ? nbytes : mf->blocksize;
492     if ((rd = read(mf->files[mf->cur_file].fd, buf, toread)) < 0)
493     {
494         yaz_log(YLOG_FATAL|YLOG_ERRNO, "mf_read2: Read failed (%s)",
495               mf->files[mf->cur_file].path);
496         return -1;
497     }
498     zebra_mutex_unlock(&mf->mutex);
499     if (rd < toread)
500         return 0;
501     else
502         return 1;
503 }
504
505
506 /** \brief writes block to metafile
507     \param mf metafile handle
508     \param no block position
509     \param offset offset within block
510     \param nbytes no of bytes to write (0 for whole block)
511     \param buf content to be written
512     \retval 0 block written
513     \retval -1 error (block not written)
514  */
515 int mf_write(MFile mf, zint no, int offset, int nbytes, const void *buf)
516 {
517     zint ps;
518     zint nblocks;
519     int towrite;
520     mf_dir *dp;
521     char tmp[FILENAME_MAX+1];
522     unsigned char dummych = '\xff';
523
524     zebra_mutex_lock(&mf->mutex);
525     if ((ps = file_position(mf, no, offset)) < 0)
526     {
527         yaz_log(YLOG_FATAL, "mf_write %s internal error (1)", mf->name);
528         return -1;
529     }
530     /* file needs to grow */
531     while (ps >= mf->files[mf->cur_file].blocks)
532     {
533         mfile_off_t needed = (ps - mf->files[mf->cur_file].blocks + 1) *
534                        mf->blocksize;
535         /* file overflow - allocate new file */
536         if (mf->files[mf->cur_file].dir->max_bytes >= 0 &&
537             needed > mf->files[mf->cur_file].dir->avail_bytes)
538         {
539             /* cap off file? */
540             if ((nblocks = (int) (mf->files[mf->cur_file].dir->avail_bytes /
541                 mf->blocksize)) > 0)
542             {
543                 yaz_log(YLOG_DEBUG, "Capping off file %s at pos " ZINT_FORMAT,
544                     mf->files[mf->cur_file].path, nblocks);
545                 if ((ps = file_position(mf,
546                     (mf->cur_file ? mf->files[mf->cur_file-1].top : 0) +
547                     mf->files[mf->cur_file].blocks + nblocks - 1, 0)) < 0)
548                 {
549                     yaz_log(YLOG_FATAL, "mf_write %s internal error (2)",
550                                  mf->name);
551                     return -1;
552                 }
553                 yaz_log(YLOG_DEBUG, "ps = " ZINT_FORMAT, ps);
554                 if (write(mf->files[mf->cur_file].fd, &dummych, 1) < 1)
555                 {
556                     yaz_log(YLOG_ERRNO|YLOG_FATAL, "mf_write %s internal error (3)",
557                                       mf->name);
558                     return -1;
559                 }
560                 mf->files[mf->cur_file].blocks += nblocks;
561                 mf->files[mf->cur_file].bytes += nblocks * mf->blocksize;
562                 mf->files[mf->cur_file].dir->avail_bytes -= nblocks *
563                     mf->blocksize;
564             }
565             /* get other bit */
566             yaz_log(YLOG_DEBUG, "Creating new file.");
567             for (dp = mf->ma->dirs; dp && dp->max_bytes >= 0 &&
568                 dp->avail_bytes < needed; dp = dp->next);
569             if (!dp)
570             {
571                 yaz_log(YLOG_FATAL, "Cannot allocate more space for %s",
572                       mf->name);
573                 return -1;
574             }
575             mf->files[mf->cur_file].top = (mf->cur_file ?
576                 mf->files[mf->cur_file-1].top : -1) +
577                 mf->files[mf->cur_file].blocks;
578             mf->files[++(mf->cur_file)].top = -1;
579             mf->files[mf->cur_file].dir = dp;
580             mf->files[mf->cur_file].number =
581                 mf->files[mf->cur_file-1].number + 1;
582             mf->files[mf->cur_file].blocks = 0;
583             mf->files[mf->cur_file].bytes = 0;
584             mf->files[mf->cur_file].fd = -1;
585             sprintf(tmp, "%s/%s-" ZINT_FORMAT ".mf", dp->name, mf->name,
586                 mf->files[mf->cur_file].number);
587             mf->files[mf->cur_file].path = xstrdup(tmp);
588             mf->no_files++;
589             /* open new file and position at beginning */
590             if ((ps = file_position(mf, no, offset)) < 0)
591             {
592                 yaz_log(YLOG_FATAL, "mf_write %s internal error (4)",
593                                  mf->name);
594                 return -1;
595             }   
596         }
597         else
598         {
599             nblocks = ps - mf->files[mf->cur_file].blocks + 1;
600             mf->files[mf->cur_file].blocks += nblocks;
601             mf->files[mf->cur_file].bytes += nblocks * mf->blocksize;
602             if (mf->files[mf->cur_file].dir->max_bytes >= 0)
603                 mf->files[mf->cur_file].dir->avail_bytes -=
604                 nblocks * mf->blocksize;
605         }
606     }
607     towrite = nbytes ? nbytes : mf->blocksize;
608     if (write(mf->files[mf->cur_file].fd, buf, towrite) < towrite)
609     {
610         yaz_log(YLOG_FATAL|YLOG_ERRNO, "Write failed for file %s part %d",
611                 mf->name, mf->cur_file);
612         return -1;
613     }
614     zebra_mutex_unlock(&mf->mutex);
615     return 0;
616 }
617
618 /** \brief metafile area statistics 
619     \param ma metafile area handle
620     \param no area number (0=first, 1=second, ..)
621     \param directory holds directory upon completion (if non-NULL)
622     \param used_bytes holds used bytes upon completion (if non-NULL)
623     \param max_bytes holds max size bytes upon completion (if non-NULL)
624     \retval 0 area number does not exist
625     \retval 1 area number exists (and directory,used_bytes,.. are set)
626 */
627 int mf_area_directory_stat(MFile_area ma, int no, const char **directory,
628                            double *used_bytes, double *max_bytes)
629 {
630     int i;
631     mf_dir *d = ma->dirs;
632     for (i = 0; d && i<no; i++, d = d->next)
633         ;
634     if (!d)
635         return 0;
636     if (directory)
637         *directory = d->name;
638     if (max_bytes)
639     {
640         /* possible loss of data. But it's just statistics and lies */
641         *max_bytes = (double) d->max_bytes;
642     }
643     if (used_bytes)
644     {
645         /* possible loss of data. But it's just statistics and lies */
646         *used_bytes = (double) (d->max_bytes - d->avail_bytes);
647     }
648     return 1;
649 }
650 /*
651  * Local variables:
652  * c-basic-offset: 4
653  * indent-tabs-mode: nil
654  * End:
655  * vim: shiftwidth=4 tabstop=8 expandtab
656  */
657