move rrd_lock into rrd_open where the general rrd_file ops are located. -- Daniel...
[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  * get exclusive lock to whole file.
356  * lock gets removed when we close the file
357  *
358  * returns 0 on success
359  */
360 int rrd_lock(
361     rrd_file_t *file)
362 {
363     int       rcstat;
364
365     {
366 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
367         struct _stat st;
368
369         if (_fstat(file->fd, &st) == 0) {
370             rcstat = _locking(file->fd, _LK_NBLCK, st.st_size);
371         } else {
372             rcstat = -1;
373         }
374 #else
375         struct flock lock;
376
377         lock.l_type = F_WRLCK;  /* exclusive write lock */
378         lock.l_len = 0; /* whole file */
379         lock.l_start = 0;   /* start of file */
380         lock.l_whence = SEEK_SET;   /* end of file */
381
382         rcstat = fcntl(file->fd, F_SETLK, &lock);
383 #endif
384     }
385
386     return (rcstat);
387 }
388
389
390 /* drop cache except for the header and the active pages */
391 void rrd_dontneed(
392     rrd_file_t *rrd_file,
393     rrd_t *rrd)
394 {
395 #if defined USE_MADVISE || defined HAVE_POSIX_FADVISE
396     off_t dontneed_start;
397     off_t rra_start;
398     off_t active_block;
399     unsigned long i;
400     ssize_t   _page_size = sysconf(_SC_PAGESIZE);
401
402     if (rrd_file == NULL) {
403 #if defined DEBUG && DEBUG
404             fprintf (stderr, "rrd_dontneed: Argument 'rrd_file' is NULL.\n");
405 #endif
406             return;
407     }
408
409 #if defined DEBUG && DEBUG > 1
410     mincore_print(rrd_file, "before");
411 #endif
412
413     /* ignoring errors from RRDs that are smaller then the file_len+rounding */
414     rra_start = rrd_file->header_len;
415     dontneed_start = PAGE_START(rra_start) + _page_size;
416     for (i = 0; i < rrd->stat_head->rra_cnt; ++i) {
417         active_block =
418             PAGE_START(rra_start
419                        + rrd->rra_ptr[i].cur_row
420                        * rrd->stat_head->ds_cnt * sizeof(rrd_value_t));
421         if (active_block > dontneed_start) {
422 #ifdef USE_MADVISE
423             madvise(rrd_file->file_start + dontneed_start,
424                     active_block - dontneed_start - 1, MADV_DONTNEED);
425 #endif
426 /* in linux at least only fadvise DONTNEED seems to purge pages from cache */
427 #ifdef HAVE_POSIX_FADVISE
428             posix_fadvise(rrd_file->fd, dontneed_start,
429                           active_block - dontneed_start - 1,
430                           POSIX_FADV_DONTNEED);
431 #endif
432         }
433         dontneed_start = active_block;
434         /* do not release 'hot' block if update for this RAA will occur
435          * within 10 minutes */
436         if (rrd->stat_head->pdp_step * rrd->rra_def[i].pdp_cnt -
437             rrd->live_head->last_up % (rrd->stat_head->pdp_step *
438                                        rrd->rra_def[i].pdp_cnt) < 10 * 60) {
439             dontneed_start += _page_size;
440         }
441         rra_start +=
442             rrd->rra_def[i].row_cnt * rrd->stat_head->ds_cnt *
443             sizeof(rrd_value_t);
444     }
445
446     if (dontneed_start < rrd_file->file_len) {
447 #ifdef USE_MADVISE
448             madvise(rrd_file->file_start + dontneed_start,
449                     rrd_file->file_len - dontneed_start, MADV_DONTNEED);
450 #endif
451 #ifdef HAVE_POSIX_FADVISE
452             posix_fadvise(rrd_file->fd, dontneed_start,
453                           rrd_file->file_len - dontneed_start,
454                           POSIX_FADV_DONTNEED);
455 #endif
456     }
457
458 #if defined DEBUG && DEBUG > 1
459     mincore_print(rrd_file, "after");
460 #endif
461 #endif                          /* without madvise and posix_fadvise ist does not make much sense todo anything */
462 }
463
464
465
466
467
468 int rrd_close(
469     rrd_file_t *rrd_file)
470 {
471     int       ret;
472
473 #ifdef HAVE_MMAP
474     ret = msync(rrd_file->file_start, rrd_file->file_len, MS_ASYNC);
475     if (ret != 0)
476         rrd_set_error("msync rrd_file: %s", rrd_strerror(errno));
477     ret = munmap(rrd_file->file_start, rrd_file->file_len);
478     if (ret != 0)
479         rrd_set_error("munmap rrd_file: %s", rrd_strerror(errno));
480 #endif
481     ret = close(rrd_file->fd);
482     if (ret != 0)
483         rrd_set_error("closing file: %s", rrd_strerror(errno));
484     free(rrd_file);
485     rrd_file = NULL;
486     return ret;
487 }
488
489
490 /* Set position of rrd_file.  */
491
492 off_t rrd_seek(
493     rrd_file_t *rrd_file,
494     off_t off,
495     int whence)
496 {
497     off_t     ret = 0;
498
499 #ifdef HAVE_MMAP
500     if (whence == SEEK_SET)
501         rrd_file->pos = off;
502     else if (whence == SEEK_CUR)
503         rrd_file->pos += off;
504     else if (whence == SEEK_END)
505         rrd_file->pos = rrd_file->file_len + off;
506 #else
507     ret = lseek(rrd_file->fd, off, whence);
508     if (ret < 0)
509         rrd_set_error("lseek: %s", rrd_strerror(errno));
510     rrd_file->pos = ret;
511 #endif
512     /* mimic fseek, which returns 0 upon success */
513     return ret < 0;     /*XXX: or just ret to mimic lseek */
514 }
515
516
517 /* Get current position in rrd_file.  */
518
519 off_t rrd_tell(
520     rrd_file_t *rrd_file)
521 {
522     return rrd_file->pos;
523 }
524
525
526 /* Read count bytes into buffer buf, starting at rrd_file->pos.
527  * Returns the number of bytes read or <0 on error.  */
528
529 ssize_t rrd_read(
530     rrd_file_t *rrd_file,
531     void *buf,
532     size_t count)
533 {
534 #ifdef HAVE_MMAP
535     size_t    _cnt = count;
536     ssize_t   _surplus;
537
538     if (rrd_file->pos > rrd_file->file_len || _cnt == 0)    /* EOF */
539         return 0;
540     if (buf == NULL)
541         return -1;      /* EINVAL */
542     _surplus = rrd_file->pos + _cnt - rrd_file->file_len;
543     if (_surplus > 0) { /* short read */
544         _cnt -= _surplus;
545     }
546     if (_cnt == 0)
547         return 0;       /* EOF */
548     buf = memcpy(buf, rrd_file->file_start + rrd_file->pos, _cnt);
549
550     rrd_file->pos += _cnt;  /* mimmic read() semantics */
551     return _cnt;
552 #else
553     ssize_t   ret;
554
555     ret = read(rrd_file->fd, buf, count);
556     if (ret > 0)
557         rrd_file->pos += ret;   /* mimmic read() semantics */
558     return ret;
559 #endif
560 }
561
562
563 /* Write count bytes from buffer buf to the current position
564  * rrd_file->pos of rrd_file->fd.
565  * Returns the number of bytes written or <0 on error.  */
566
567 ssize_t rrd_write(
568     rrd_file_t *rrd_file,
569     const void *buf,
570     size_t count)
571 {
572 #ifdef HAVE_MMAP
573     if (count == 0)
574         return 0;
575     if (buf == NULL)
576         return -1;      /* EINVAL */
577     memcpy(rrd_file->file_start + rrd_file->pos, buf, count);
578     rrd_file->pos += count;
579     return count;       /* mimmic write() semantics */
580 #else
581     ssize_t   _sz = write(rrd_file->fd, buf, count);
582
583     if (_sz > 0)
584         rrd_file->pos += _sz;
585     return _sz;
586 #endif
587 }
588
589
590 /* flush all data pending to be written to FD.  */
591
592 void rrd_flush(
593     rrd_file_t *rrd_file)
594 {
595     if (fdatasync(rrd_file->fd) != 0) {
596         rrd_set_error("flushing fd %d: %s", rrd_file->fd,
597                       rrd_strerror(errno));
598     }
599 }
600
601
602 /* Initialize RRD header.  */
603
604 void rrd_init(
605     rrd_t *rrd)
606 {
607     rrd->stat_head = NULL;
608     rrd->ds_def = NULL;
609     rrd->rra_def = NULL;
610     rrd->live_head = NULL;
611     rrd->legacy_last_up = NULL;
612     rrd->rra_ptr = NULL;
613     rrd->pdp_prep = NULL;
614     rrd->cdp_prep = NULL;
615     rrd->rrd_value = NULL;
616 }
617
618
619 /* free RRD header data.  */
620
621 #ifdef HAVE_MMAP
622 void rrd_free(
623     rrd_t *rrd)
624 {
625     if (rrd->legacy_last_up) {  /* this gets set for version < 3 only */
626         free(rrd->live_head);
627     }
628 }
629 #else
630 void rrd_free(
631     rrd_t *rrd)
632 {
633     free(rrd->live_head);
634     free(rrd->stat_head);
635     free(rrd->ds_def);
636     free(rrd->rra_def);
637     free(rrd->rra_ptr);
638     free(rrd->pdp_prep);
639     free(rrd->cdp_prep);
640     free(rrd->rrd_value);
641 }
642 #endif
643
644
645 /* routine used by external libraries to free memory allocated by
646  * rrd library */
647
648 void rrd_freemem(
649     void *mem)
650 {
651     free(mem);
652 }