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