70e0ab9f5d6b38dd3b4339a438943f848e8c25d1
[rrdtool.git] / src / rrd_open.c
1 /*****************************************************************************
2  * RRDtool 1.2.99907080300  Copyright by Tobi Oetiker, 1997-2007
3  *****************************************************************************
4  * rrd_open.c  Open an RRD File
5  *****************************************************************************
6  * $Id$
7  *****************************************************************************/
8
9 #include "rrd_tool.h"
10 #include "unused.h"
11 #define MEMBLK 8192
12
13 /* DEBUG 2 prints information obtained via mincore(2) */
14 #define DEBUG 1
15 /* do not calculate exact madvise hints but assume 1 page for headers and
16  * set DONTNEED for the rest, which is assumed to be data */
17 /* Avoid calling madvise on areas that were already hinted. May be benefical if
18  * your syscalls are very slow */
19
20 #ifdef HAVE_MMAP
21 /* the cast to void* is there to avoid this warning seen on ia64 with certain
22    versions of gcc: 'cast increases required alignment of target type'
23 */
24 #define __rrd_read(dst, dst_t, cnt) \
25         (dst) = (dst_t*)(void*) (data + offset); \
26         offset += sizeof(dst_t) * (cnt)
27 #else
28 #define __rrd_read(dst, dst_t, cnt) \
29         if ((dst = malloc(sizeof(dst_t)*(cnt))) == NULL) { \
30                 rrd_set_error(#dst " malloc"); \
31                 goto out_nullify_head; \
32         } \
33         offset += read (rrd_file->fd, dst, sizeof(dst_t)*(cnt))
34 #endif
35
36 /* get the address of the start of this page */
37 #ifndef PAGE_START
38 #define PAGE_START(addr) ((addr)&(~(_page_size-1)))
39 #endif
40
41
42 /* Open a database file, return its header and an open filehandle,
43  * positioned to the first cdp in the first rra.
44  * In the error path of rrd_open, only rrd_free(&rrd) has to be called
45  * before returning an error. Do not call rrd_close upon failure of rrd_open.
46  */
47
48 rrd_file_t *rrd_open(
49     const char *const file_name,
50     rrd_t *rrd,
51     unsigned rdwr)
52 {
53     int       flags = 0;
54     mode_t    mode = S_IRUSR;
55     int       version;
56
57 #ifdef HAVE_MMAP
58     ssize_t   _page_size = sysconf(_SC_PAGESIZE);
59     int       mm_prot = PROT_READ, mm_flags = 0;
60     char     *data;
61 #endif
62     off_t     offset = 0;
63     struct stat statb;
64     rrd_file_t *rrd_file = NULL;
65     off_t     newfile_size = 0;
66
67     if (rdwr & RRD_CREAT) {
68         /* yes bad inline signaling alert, we are using the
69            floatcookie to pass the size in ... only used in resize */
70         newfile_size = (off_t) rrd->stat_head->float_cookie;
71         free(rrd->stat_head);
72     }
73     rrd_init(rrd);
74     rrd_file = malloc(sizeof(rrd_file_t));
75     if (rrd_file == NULL) {
76         rrd_set_error("allocating rrd_file descriptor for '%s'", file_name);
77         return NULL;
78     }
79     memset(rrd_file, 0, sizeof(rrd_file_t));
80
81 #ifdef DEBUG
82     if ((rdwr & (RRD_READONLY | RRD_READWRITE)) ==
83         (RRD_READONLY | RRD_READWRITE)) {
84         /* Both READONLY and READWRITE were given, which is invalid.  */
85         rrd_set_error("in read/write request mask");
86         exit(-1);
87     }
88 #endif
89     if (rdwr & RRD_READONLY) {
90         flags |= O_RDONLY;
91 #ifdef HAVE_MMAP
92         mm_flags = MAP_PRIVATE;
93 # ifdef MAP_NORESERVE
94         mm_flags |= MAP_NORESERVE;  /* readonly, so no swap backing needed */
95 # endif
96 #endif
97     } else {
98         if (rdwr & RRD_READWRITE) {
99             mode |= S_IWUSR;
100             flags |= O_RDWR;
101 #ifdef HAVE_MMAP
102             mm_flags = MAP_SHARED;
103             mm_prot |= PROT_WRITE;
104 #endif
105         }
106         if (rdwr & RRD_CREAT) {
107             flags |= (O_CREAT | O_TRUNC);
108         }
109     }
110     if (rdwr & RRD_READAHEAD) {
111 #ifdef MAP_POPULATE
112         mm_flags |= MAP_POPULATE;   /* populate ptes and data */
113 #endif
114 #if defined MAP_NONBLOCK
115         mm_flags |= MAP_NONBLOCK;   /* just populate ptes */
116 #endif
117     }
118
119     if ((rrd_file->fd = open(file_name, flags, mode)) < 0) {
120         rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
121         goto out_free;
122     }
123
124     /* Better try to avoid seeks as much as possible. stat may be heavy but
125      * many concurrent seeks are even worse.  */
126     if (newfile_size == 0 && ((fstat(rrd_file->fd, &statb)) < 0)) {
127         rrd_set_error("fstat '%s': %s", file_name, rrd_strerror(errno));
128         goto out_close;
129     }
130     if (newfile_size == 0) {
131         rrd_file->file_len = statb.st_size;
132     } else {
133         rrd_file->file_len = newfile_size;
134         lseek(rrd_file->fd, newfile_size - 1, SEEK_SET);
135         write(rrd_file->fd, "\0", 1);   /* poke */
136         lseek(rrd_file->fd, 0, SEEK_SET);
137     }
138 #ifdef HAVE_POSIX_FADVISE
139     /* In general we need no read-ahead when dealing with rrd_files.
140        When we stop reading, it is highly unlikely that we start up again.
141        In this manner we actually save time and diskaccess (and buffer cache).
142        Thanks to Dave Plonka for the Idea of using POSIX_FADV_RANDOM here. */
143     if (0 != posix_fadvise(rrd_file->fd, 0, 0, POSIX_FADV_RANDOM)) {
144         rrd_set_error("setting POSIX_FADV_RANDOM on '%s': %s", file_name,
145                       rrd_strerror(errno));
146         goto out_close;
147     }
148 #endif
149
150 /*
151         if (rdwr & RRD_READWRITE)
152         {
153            if (setvbuf((rrd_file->fd),NULL,_IONBF,2)) {
154                   rrd_set_error("failed to disable the stream buffer\n");
155                   return (-1);
156            }
157         }
158 */
159 #ifdef HAVE_MMAP
160     data = mmap(0, rrd_file->file_len, mm_prot, mm_flags,
161                 rrd_file->fd, offset);
162
163     /* lets see if the first read worked */
164     if (data == MAP_FAILED) {
165         rrd_set_error("mmaping file '%s': %s", file_name,
166                       rrd_strerror(errno));
167         goto out_close;
168     }
169     rrd_file->file_start = data;
170     if (rdwr & RRD_CREAT) {
171         memset(data, DNAN, newfile_size - 1);
172         goto out_done;
173     }
174 #endif
175     if (rdwr & RRD_CREAT)
176         goto out_done;
177 #ifdef USE_MADVISE
178     if (rdwr & RRD_COPY) {
179         /* We will read everything in a moment (copying) */
180         madvise(data, rrd_file->file_len, MADV_WILLNEED | MADV_SEQUENTIAL);
181     } else {
182         /* We do not need to read anything in for the moment */
183         madvise(data, rrd_file->file_len, MADV_RANDOM);
184         /* the stat_head will be needed soonish, so hint accordingly */
185         madvise(data, sizeof(stat_head_t), MADV_WILLNEED | MADV_RANDOM);
186     }
187 #endif
188
189     __rrd_read(rrd->stat_head, stat_head_t,
190                1);
191
192     /* lets do some test if we are on track ... */
193     if (memcmp(rrd->stat_head->cookie, RRD_COOKIE, sizeof(RRD_COOKIE)) != 0) {
194         rrd_set_error("'%s' is not an RRD file", file_name);
195         goto out_nullify_head;
196     }
197
198     if (rrd->stat_head->float_cookie != FLOAT_COOKIE) {
199         rrd_set_error("This RRD was created on another architecture");
200         goto out_nullify_head;
201     }
202
203     version = atoi(rrd->stat_head->version);
204
205     if (version > atoi(RRD_VERSION)) {
206         rrd_set_error("can't handle RRD file version %s",
207                       rrd->stat_head->version);
208         goto out_nullify_head;
209     }
210 #if defined USE_MADVISE
211     /* the ds_def will be needed soonish, so hint accordingly */
212     madvise(data + PAGE_START(offset),
213             sizeof(ds_def_t) * rrd->stat_head->ds_cnt, MADV_WILLNEED);
214 #endif
215     __rrd_read(rrd->ds_def, ds_def_t,
216                rrd->stat_head->ds_cnt);
217
218 #if defined USE_MADVISE
219     /* the rra_def will be needed soonish, so hint accordingly */
220     madvise(data + PAGE_START(offset),
221             sizeof(rra_def_t) * rrd->stat_head->rra_cnt, MADV_WILLNEED);
222 #endif
223     __rrd_read(rrd->rra_def, rra_def_t,
224                rrd->stat_head->rra_cnt);
225
226     /* handle different format for the live_head */
227     if (version < 3) {
228         rrd->live_head = (live_head_t *) malloc(sizeof(live_head_t));
229         if (rrd->live_head == NULL) {
230             rrd_set_error("live_head_t malloc");
231             goto out_close;
232         }
233 #ifdef HAVE_MMAP
234         memmove(&rrd->live_head->last_up, data + offset, sizeof(long));
235         offset += sizeof(long);
236 #else
237         offset += read(rrd_file->fd, &rrd->live_head->last_up, sizeof(long));
238 #endif
239         rrd->live_head->last_up_usec = 0;
240     } else {
241 #if defined USE_MADVISE
242         /* the live_head will be needed soonish, so hint accordingly */
243         madvise(data + PAGE_START(offset),
244                 sizeof(live_head_t), MADV_WILLNEED);
245 #endif
246         __rrd_read(rrd->live_head, live_head_t,
247                    1);
248     }
249 //XXX: This doesn't look like it needs madvise
250     __rrd_read(rrd->pdp_prep, pdp_prep_t,
251                rrd->stat_head->ds_cnt);
252
253 //XXX: This could benefit from madvise()ing
254     __rrd_read(rrd->cdp_prep, cdp_prep_t,
255                rrd->stat_head->rra_cnt * rrd->stat_head->ds_cnt);
256
257 //XXX: This could benefit from madvise()ing
258     __rrd_read(rrd->rra_ptr, rra_ptr_t,
259                rrd->stat_head->rra_cnt);
260
261     rrd_file->header_len = offset;
262     rrd_file->pos = offset;
263   out_done:
264     return (rrd_file);
265   out_nullify_head:
266     rrd->stat_head = NULL;
267   out_close:
268     close(rrd_file->fd);
269   out_free:
270     free(rrd_file);
271     return NULL;
272 }
273
274
275 /* Close a reference to an rrd_file.  */
276 static
277 void mincore_print(
278     rrd_file_t *rrd_file,
279     char *mark)
280 {
281 #ifdef HAVE_MMAP
282     /* pretty print blocks in core */
283     off_t     off;
284     unsigned char *vec;
285     ssize_t   _page_size = sysconf(_SC_PAGESIZE);
286
287     off = rrd_file->file_len +
288         ((rrd_file->file_len + _page_size - 1) / _page_size);
289     vec = malloc(off);
290     if (vec != NULL) {
291         memset(vec, 0, off);
292         if (mincore(rrd_file->file_start, rrd_file->file_len, vec) == 0) {
293             int       prev;
294             unsigned  is_in = 0, was_in = 0;
295
296             for (off = 0, prev = 0; off < rrd_file->file_len; ++off) {
297                 is_in = vec[off] & 1;   /* if lsb set then is core resident */
298                 if (off == 0)
299                     was_in = is_in;
300                 if (was_in != is_in) {
301                     fprintf(stderr, "%s: %sin core: %p len %ld\n", mark,
302                             was_in ? "" : "not ", vec + prev, off - prev);
303                     was_in = is_in;
304                     prev = off;
305                 }
306             }
307             fprintf(stderr,
308                     "%s: %sin core: %p len %ld\n", mark,
309                     was_in ? "" : "not ", vec + prev, off - prev);
310         } else
311             fprintf(stderr, "mincore: %s", rrd_strerror(errno));
312     }
313 #else
314     fprintf(stderr, "sorry mincore only works with mmap");
315 #endif
316 }
317
318
319 /* drop cache except for the header and the active pages */
320 void rrd_dontneed(
321     rrd_file_t *rrd_file,
322     rrd_t *rrd)
323 {
324     unsigned long dontneed_start;
325     unsigned long rra_start;
326     unsigned long active_block;
327     unsigned long i;
328     ssize_t   _page_size = sysconf(_SC_PAGESIZE);
329
330 #if defined DEBUG && DEBUG > 1
331     mincore_print(rrd_file, "before");
332 #endif
333
334     /* ignoring errors from RRDs that are smaller then the file_len+rounding */
335     rra_start = rrd_file->header_len;
336     dontneed_start = PAGE_START(rra_start) + _page_size;
337     for (i = 0; i < rrd->stat_head->rra_cnt; ++i) {
338         active_block =
339             PAGE_START(rra_start
340                        + rrd->rra_ptr[i].cur_row
341                        * rrd->stat_head->ds_cnt * sizeof(rrd_value_t));
342         if (active_block > dontneed_start) {
343 #ifdef USE_MADVISE
344             madvise(rrd_file->file_start + dontneed_start,
345                     active_block - dontneed_start - 1, MADV_DONTNEED);
346 #endif
347 /* in linux at least only fadvise DONTNEED seems to purge pages from cache */
348 #ifdef HAVE_POSIX_FADVISE
349             posix_fadvise(rrd_file->fd, dontneed_start,
350                           active_block - dontneed_start - 1,
351                           POSIX_FADV_DONTNEED);
352 #endif
353         }
354         dontneed_start = active_block;
355         /* do not relase 'hot' block if update for this RAA will occure within 10 minutes */
356         if (rrd->stat_head->pdp_step * rrd->rra_def[i].pdp_cnt -
357             rrd->live_head->last_up % (rrd->stat_head->pdp_step *
358                                        rrd->rra_def[i].pdp_cnt) < 10 * 60) {
359             dontneed_start += _page_size;
360         }
361         rra_start +=
362             rrd->rra_def[i].row_cnt * rrd->stat_head->ds_cnt *
363             sizeof(rrd_value_t);
364     }
365 #ifdef USE_MADVISE
366     madvise(rrd_file->file_start + dontneed_start,
367             rrd_file->file_len - dontneed_start, MADV_DONTNEED);
368 #endif
369 #ifdef HAVE_POSIX_FADVISE
370     posix_fadvise(rrd_file->fd, dontneed_start,
371                   rrd_file->file_len - dontneed_start, POSIX_FADV_DONTNEED);
372 #endif
373 #if defined DEBUG && DEBUG > 1
374     mincore_print(rrd_file, "after");
375 #endif
376 }
377
378 int rrd_close(
379     rrd_file_t *rrd_file)
380 {
381     int       ret;
382
383 #ifdef HAVE_MMAP
384     ret = msync(rrd_file->file_start, rrd_file->file_len,MS_ASYNC);
385     if (ret != 0)
386         rrd_set_error("msync rrd_file: %s", rrd_strerror(errno));
387     ret = munmap(rrd_file->file_start, rrd_file->file_len);
388     if (ret != 0)
389         rrd_set_error("munmap rrd_file: %s", rrd_strerror(errno));
390 #endif
391     ret = close(rrd_file->fd);
392     if (ret != 0)
393         rrd_set_error("closing file: %s", rrd_strerror(errno));
394     free(rrd_file);
395     rrd_file = NULL;
396     return ret;
397 }
398
399
400 /* Set position of rrd_file.  */
401
402 off_t rrd_seek(
403     rrd_file_t *rrd_file,
404     off_t off,
405     int whence)
406 {
407     off_t     ret = 0;
408
409 #ifdef HAVE_MMAP
410     if (whence == SEEK_SET)
411         rrd_file->pos = off;
412     else if (whence == SEEK_CUR)
413         rrd_file->pos += off;
414     else if (whence == SEEK_END)
415         rrd_file->pos = rrd_file->file_len + off;
416 #else
417     ret = lseek(rrd_file->fd, off, whence);
418     if (ret < 0)
419         rrd_set_error("lseek: %s", rrd_strerror(errno));
420     rrd_file->pos = ret;
421 #endif
422 //XXX: mimic fseek, which returns 0 upon success
423     return ret == -1;   //XXX: or just ret to mimic lseek
424 }
425
426
427 /* Get current position in rrd_file.  */
428
429 inline off_t rrd_tell(
430     rrd_file_t *rrd_file)
431 {
432     return rrd_file->pos;
433 }
434
435
436 /* read count bytes into buffer buf, starting at rrd_file->pos.
437  * Returns the number of bytes read or <0 on error.  */
438
439 inline ssize_t rrd_read(
440     rrd_file_t *rrd_file,
441     void *buf,
442     size_t count)
443 {
444 #ifdef HAVE_MMAP
445     size_t    _cnt = count;
446     ssize_t   _surplus;
447
448     if (rrd_file->pos > rrd_file->file_len || _cnt == 0) /* EOF */
449        return 0;
450     if (buf == NULL)
451        return -1; /* EINVAL */
452     _surplus = rrd_file->pos + _cnt - rrd_file->file_len;
453     if (_surplus > 0) { /* short read */
454         _cnt -= _surplus;
455     }
456     if (_cnt == 0)
457         return 0;       /* EOF */
458     buf = memcpy(buf, rrd_file->file_start + rrd_file->pos, _cnt);
459
460     rrd_file->pos += _cnt;  /* mimmic read() semantics */
461     return _cnt;
462 #else
463     ssize_t   ret;
464
465     ret = read(rrd_file->fd, buf, count);
466     if (ret > 0)
467         rrd_file->pos += ret;   /* mimmic read() semantics */
468     return ret;
469 #endif
470 }
471
472
473 /* write count bytes from buffer buf to the current position
474  * rrd_file->pos of rrd_file->fd.
475  * Returns the number of bytes written or <0 on error.  */
476
477 inline ssize_t rrd_write(
478     rrd_file_t *rrd_file,
479     const void *buf,
480     size_t count)
481 {
482 #ifdef HAVE_MMAP
483     if (count == 0)
484        return 0;
485     if (buf == NULL)
486        return -1; /* EINVAL */
487     memcpy(rrd_file->file_start + rrd_file->pos, buf, count);
488     rrd_file->pos += count;
489     return count;       /* mimmic write() semantics */
490 #else
491     ssize_t   _sz = write(rrd_file->fd, buf, count);
492
493     if (_sz > 0)
494         rrd_file->pos += _sz;
495     return _sz;
496 #endif
497 }
498
499
500 /* flush all data pending to be written to FD.  */
501
502 inline void rrd_flush(
503     rrd_file_t *rrd_file)
504 {
505     if (fdatasync(rrd_file->fd) != 0) {
506         rrd_set_error("flushing fd %d: %s", rrd_file->fd,
507                       rrd_strerror(errno));
508     }
509 }
510
511
512 /* Initialize RRD header.  */
513
514 void rrd_init(
515     rrd_t *rrd)
516 {
517     rrd->stat_head = NULL;
518     rrd->ds_def = NULL;
519     rrd->rra_def = NULL;
520     rrd->live_head = NULL;
521     rrd->rra_ptr = NULL;
522     rrd->pdp_prep = NULL;
523     rrd->cdp_prep = NULL;
524     rrd->rrd_value = NULL;
525 }
526
527
528 /* free RRD header data.  */
529
530 #ifdef HAVE_MMAP
531 inline void rrd_free(
532     rrd_t UNUSED(*rrd))
533 {
534 }
535 #else
536 void rrd_free(
537     rrd_t *rrd)
538 {
539     free(rrd->live_head);
540     free(rrd->stat_head);
541     free(rrd->ds_def);
542     free(rrd->rra_def);
543     free(rrd->rra_ptr);
544     free(rrd->pdp_prep);
545     free(rrd->cdp_prep);
546     free(rrd->rrd_value);
547 }
548 #endif
549
550
551 /* routine used by external libraries to free memory allocated by
552  * rrd library */
553
554 void rrd_freemem(
555     void *mem)
556 {
557     free(mem);
558 }
559
560
561 /* XXX: FIXME: missing documentation.  */
562 /*XXX: FIXME should be renamed to rrd_readfile or _rrd_readfile */
563
564 int /*_rrd_*/ readfile(
565     const char *file_name,
566     char **buffer,
567     int skipfirst)
568 {
569     long      writecnt = 0, totalcnt = MEMBLK;
570     long      offset = 0;
571     FILE     *input = NULL;
572     char      c;
573
574     if ((strcmp("-", file_name) == 0)) {
575         input = stdin;
576     } else {
577         if ((input = fopen(file_name, "rb")) == NULL) {
578             rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
579             return (-1);
580         }
581     }
582     if (skipfirst) {
583         do {
584             c = getc(input);
585             offset++;
586         } while (c != '\n' && !feof(input));
587     }
588     if (strcmp("-", file_name)) {
589         fseek(input, 0, SEEK_END);
590         /* have extra space for detecting EOF without realloc */
591         totalcnt = (ftell(input) + 1) / sizeof(char) - offset;
592         if (totalcnt < MEMBLK)
593             totalcnt = MEMBLK;  /* sanitize */
594         fseek(input, offset * sizeof(char), SEEK_SET);
595     }
596     if (((*buffer) = (char *) malloc((totalcnt + 4) * sizeof(char))) == NULL) {
597         perror("Allocate Buffer:");
598         exit(1);
599     };
600     do {
601         writecnt +=
602             fread((*buffer) + writecnt, 1,
603                   (totalcnt - writecnt) * sizeof(char), input);
604         if (writecnt >= totalcnt) {
605             totalcnt += MEMBLK;
606             if (((*buffer) =
607                  rrd_realloc((*buffer),
608                              (totalcnt + 4) * sizeof(char))) == NULL) {
609                 perror("Realloc Buffer:");
610                 exit(1);
611             };
612         }
613     } while (!feof(input));
614     (*buffer)[writecnt] = '\0';
615     if (strcmp("-", file_name) != 0) {
616         fclose(input);
617     };
618     return writecnt;
619 }