0de9b395baefc9fe83b2dda77ea50c6e916298b0
[rrdtool.git] / src / rrd_open.c
1 /*****************************************************************************
2  * RRDtool 1.4.3  Copyright by Tobi Oetiker, 1997-2010
3  *****************************************************************************
4  * rrd_open.c  Open an RRD File
5  *****************************************************************************
6  * $Id$
7  *****************************************************************************/
8
9 #include "rrd_tool.h"
10 #include "unused.h"
11
12 #ifdef WIN32
13 #include <stdlib.h>
14 #include <fcntl.h>
15 #include <sys/stat.h>
16 #endif
17
18
19 #ifdef HAVE_BROKEN_MS_ASYNC
20 #include <sys/types.h>
21 #include <utime.h>
22 #endif
23
24 #define MEMBLK 8192
25
26 #ifdef WIN32
27 #define _LK_UNLCK       0       /* Unlock */
28 #define _LK_LOCK        1       /* Lock */
29 #define _LK_NBLCK       2       /* Non-blocking lock */
30 #define _LK_RLCK        3       /* Lock for read only */
31 #define _LK_NBRLCK      4       /* Non-blocking lock for read only */
32
33
34 #define LK_UNLCK        _LK_UNLCK
35 #define LK_LOCK         _LK_LOCK
36 #define LK_NBLCK        _LK_NBLCK
37 #define LK_RLCK         _LK_RLCK
38 #define LK_NBRLCK       _LK_NBRLCK
39 #endif
40
41 /* DEBUG 2 prints information obtained via mincore(2) */
42 #define DEBUG 1
43 /* do not calculate exact madvise hints but assume 1 page for headers and
44  * set DONTNEED for the rest, which is assumed to be data */
45 /* Avoid calling madvise on areas that were already hinted. May be benefical if
46  * your syscalls are very slow */
47
48 #ifdef HAVE_MMAP
49 /* the cast to void* is there to avoid this warning seen on ia64 with certain
50    versions of gcc: 'cast increases required alignment of target type'
51 */
52 #define __rrd_read(dst, dst_t, cnt) { \
53         size_t wanted = sizeof(dst_t)*(cnt); \
54         if (offset + wanted > rrd_file->file_len) { \
55                 rrd_set_error("reached EOF while loading header " #dst); \
56                 goto out_nullify_head; \
57         } \
58         (dst) = (dst_t*)(void*) (data + offset); \
59         offset += wanted; \
60     }
61 #else
62 #define __rrd_read(dst, dst_t, cnt) { \
63         size_t wanted = sizeof(dst_t)*(cnt); \
64         size_t got; \
65         if ((dst = (dst_t*)malloc(wanted)) == NULL) { \
66                 rrd_set_error(#dst " malloc"); \
67                 goto out_nullify_head; \
68         } \
69         got = read (rrd_simple_file->fd, dst, wanted); \
70         if (got != wanted) { \
71                 rrd_set_error("short read while reading header " #dst); \
72                 goto out_nullify_head; \
73         } \
74         offset += got; \
75     }
76 #endif
77
78 /* get the address of the start of this page */
79 #if defined USE_MADVISE || defined HAVE_POSIX_FADVISE
80 #ifndef PAGE_START
81 #define PAGE_START(addr) ((addr)&(~(_page_size-1)))
82 #endif
83 #endif
84
85 /* Open a database file, return its header and an open filehandle,
86  * positioned to the first cdp in the first rra.
87  * In the error path of rrd_open, only rrd_free(&rrd) has to be called
88  * before returning an error. Do not call rrd_close upon failure of rrd_open.
89  * If creating a new file, the parameter rrd must be initialised with
90  * details of the file content.
91  * If opening an existing file, then use rrd must be initialised by
92  * rrd_init(rrd) prior to invoking rrd_open
93  */
94
95 rrd_file_t *rrd_open(
96     const char *const file_name,
97     rrd_t *rrd,
98     unsigned rdwr)
99 {
100     unsigned long ui;
101     int       flags = 0;
102     int       version;
103
104 #ifdef HAVE_MMAP
105     ssize_t   _page_size = sysconf(_SC_PAGESIZE);
106     char     *data = MAP_FAILED;
107 #endif
108     off_t     offset = 0;
109     struct stat statb;
110     rrd_file_t *rrd_file = NULL;
111     rrd_simple_file_t *rrd_simple_file = NULL;
112     size_t     newfile_size = 0;
113     size_t header_len, value_cnt, data_len;
114
115     /* Are we creating a new file? */
116     if((rdwr & RRD_CREAT) && (rrd->stat_head != NULL))
117     {
118         header_len = rrd_get_header_size(rrd);
119
120         value_cnt = 0;
121         for (ui = 0; ui < rrd->stat_head->rra_cnt; ui++)
122             value_cnt += rrd->stat_head->ds_cnt * rrd->rra_def[ui].row_cnt;
123
124         data_len = sizeof(rrd_value_t) * value_cnt;
125
126         newfile_size = header_len + data_len;
127     }
128     
129     rrd_file = (rrd_file_t*)malloc(sizeof(rrd_file_t));
130     if (rrd_file == NULL) {
131         rrd_set_error("allocating rrd_file descriptor for '%s'", file_name);
132         return NULL;
133     }
134     memset(rrd_file, 0, sizeof(rrd_file_t));
135
136     rrd_file->pvt = malloc(sizeof(rrd_simple_file_t));
137     if(rrd_file->pvt == NULL) {
138         rrd_set_error("allocating rrd_simple_file for '%s'", file_name);
139         return NULL;
140     }
141     memset(rrd_file->pvt, 0, sizeof(rrd_simple_file_t));
142     rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
143
144 #ifdef DEBUG
145     if ((rdwr & (RRD_READONLY | RRD_READWRITE)) ==
146         (RRD_READONLY | RRD_READWRITE)) {
147         /* Both READONLY and READWRITE were given, which is invalid.  */
148         rrd_set_error("in read/write request mask");
149         exit(-1);
150     }
151 #endif
152
153 #ifdef HAVE_MMAP
154     rrd_simple_file->mm_prot = PROT_READ;
155     rrd_simple_file->mm_flags = 0;
156 #endif
157
158     if (rdwr & RRD_READONLY) {
159         flags |= O_RDONLY;
160 #ifdef HAVE_MMAP
161 # if !defined(AIX)
162         rrd_simple_file->mm_flags = MAP_PRIVATE;
163 # endif
164 # ifdef MAP_NORESERVE
165         rrd_simple_file->mm_flags |= MAP_NORESERVE;  /* readonly, so no swap backing needed */
166 # endif
167 #endif
168     } else {
169         if (rdwr & RRD_READWRITE) {
170             flags |= O_RDWR;
171 #ifdef HAVE_MMAP 
172             rrd_simple_file->mm_flags = MAP_SHARED; 
173             rrd_simple_file->mm_prot |= PROT_WRITE; 
174 #endif 
175         }
176         if (rdwr & RRD_CREAT) {
177             flags |= (O_CREAT | O_TRUNC);
178         }
179         if (rdwr & RRD_EXCL) {
180             flags |= O_EXCL;
181         }
182     }
183     if (rdwr & RRD_READAHEAD) {
184 #ifdef MAP_POPULATE
185         rrd_simple_file->mm_flags |= MAP_POPULATE;   /* populate ptes and data */
186 #endif
187 #if defined MAP_NONBLOCK
188         rrd_simple_file->mm_flags |= MAP_NONBLOCK;   /* just populate ptes */
189 #endif
190     }
191 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
192     flags |= O_BINARY;
193 #endif
194
195     if ((rrd_simple_file->fd = open(file_name, flags, 0666)) < 0) {
196         rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
197         goto out_free;
198     }
199
200 #ifdef HAVE_MMAP
201 #ifdef HAVE_BROKEN_MS_ASYNC
202     if (rdwr & RRD_READWRITE) {    
203         /* some unices, the files mtime does not get update    
204            on msync MS_ASYNC, in order to help them,     
205            we update the the timestamp at this point.      
206            The thing happens pretty 'close' to the open    
207            call so the chances of a race should be minimal.    
208                 
209            Maybe ask your vendor to fix your OS ... */    
210            utime(file_name,NULL);  
211     }
212 #endif    
213 #endif
214
215     /* Better try to avoid seeks as much as possible. stat may be heavy but
216      * many concurrent seeks are even worse.  */
217     if (newfile_size == 0 && ((fstat(rrd_simple_file->fd, &statb)) < 0)) {
218         rrd_set_error("fstat '%s': %s", file_name, rrd_strerror(errno));
219         goto out_close;
220     }
221     if (newfile_size == 0) {
222         rrd_file->file_len = statb.st_size;
223     } else {
224         rrd_file->file_len = newfile_size;
225 #ifdef HAVE_POSIX_FALLOCATE
226         if (posix_fallocate(rrd_simple_file->fd, 0, newfile_size) == -1) {
227             rrd_set_error("posix_fallocate '%s': %s", file_name,
228                           rrd_strerror(errno));
229             goto out_close;
230         }
231 #else
232         lseek(rrd_simple_file->fd, newfile_size - 1, SEEK_SET);
233         if ( write(rrd_simple_file->fd, "\0", 1) == -1){    /* poke */
234             rrd_set_error("write '%s': %s", file_name, rrd_strerror(errno));
235             goto out_close;
236         }
237         lseek(rrd_simple_file->fd, 0, SEEK_SET);
238 #endif
239     }
240 #ifdef HAVE_POSIX_FADVISE
241     /* In general we need no read-ahead when dealing with rrd_files.
242        When we stop reading, it is highly unlikely that we start up again.
243        In this manner we actually save time and diskaccess (and buffer cache).
244        Thanks to Dave Plonka for the Idea of using POSIX_FADV_RANDOM here. */
245     posix_fadvise(rrd_simple_file->fd, 0, 0, POSIX_FADV_RANDOM);
246 #endif
247
248 /*
249         if (rdwr & RRD_READWRITE)
250         {
251            if (setvbuf((rrd_simple_file->fd),NULL,_IONBF,2)) {
252                   rrd_set_error("failed to disable the stream buffer\n");
253                   return (-1);
254            }
255         }
256 */
257
258 #ifdef HAVE_MMAP
259 #ifndef HAVE_POSIX_FALLOCATE
260         /* force allocating the file on the underlaying filesystem to prevent any
261          * future bus error when the filesystem is full and attempting to write
262          * trough the file mapping. Filling the file using memset on the file
263          * mapping can also lead some bus error, so we use the old fashioned
264          * write().
265          */
266     if (rdwr & RRD_CREAT) {
267                 char     buf[4096];
268                 unsigned i;
269
270                 memset(buf, DNAN, sizeof buf);
271                 lseek(rrd_simple_file->fd, offset, SEEK_SET);
272         
273                 for (i = 0; i < (newfile_size - 1) / sizeof buf; ++i)
274                 {
275                         if (write(rrd_simple_file->fd, buf, sizeof buf) == -1)
276                         {
277                                 rrd_set_error("write '%s': %s", file_name, rrd_strerror(errno));
278                                 goto out_close;
279                         }
280                 }
281                 
282                 if (write(rrd_simple_file->fd, buf,
283                                         (newfile_size - 1) % sizeof buf) == -1)
284                 {
285                         rrd_set_error("write '%s': %s", file_name, rrd_strerror(errno));
286                         goto out_close;
287                 }
288
289                 lseek(rrd_simple_file->fd, 0, SEEK_SET);
290     }
291 #endif
292
293     data = mmap(0, rrd_file->file_len, 
294         rrd_simple_file->mm_prot, rrd_simple_file->mm_flags,
295         rrd_simple_file->fd, offset);
296
297     /* lets see if the first read worked */
298     if (data == MAP_FAILED) {
299         rrd_set_error("mmaping file '%s': %s", file_name,
300                       rrd_strerror(errno));
301         goto out_close;
302     }
303     rrd_simple_file->file_start = data;
304     if (rdwr & RRD_CREAT) {
305         goto out_done;
306     }
307 #endif
308     if (rdwr & RRD_CREAT)
309         goto out_done;
310 #ifdef USE_MADVISE
311     if (rdwr & RRD_COPY) {
312         /* We will read everything in a moment (copying) */
313         madvise(data, rrd_file->file_len, MADV_WILLNEED );
314         madvise(data, rrd_file->file_len, MADV_SEQUENTIAL );
315     } else {
316         /* We do not need to read anything in for the moment */
317         madvise(data, rrd_file->file_len, MADV_RANDOM);
318         /* the stat_head will be needed soonish, so hint accordingly */
319         madvise(data, sizeof(stat_head_t), MADV_WILLNEED);
320         madvise(data, sizeof(stat_head_t), MADV_RANDOM);
321     }
322 #endif
323
324     __rrd_read(rrd->stat_head, stat_head_t,
325                1);
326
327     /* lets do some test if we are on track ... */
328     if (memcmp(rrd->stat_head->cookie, RRD_COOKIE, sizeof(RRD_COOKIE)) != 0) {
329         rrd_set_error("'%s' is not an RRD file", file_name);
330         goto out_nullify_head;
331     }
332
333     if (rrd->stat_head->float_cookie != FLOAT_COOKIE) {
334         rrd_set_error("This RRD was created on another architecture");
335         goto out_nullify_head;
336     }
337
338     version = atoi(rrd->stat_head->version);
339
340     if (version > atoi(RRD_VERSION)) {
341         rrd_set_error("can't handle RRD file version %s",
342                       rrd->stat_head->version);
343         goto out_nullify_head;
344     }
345 #if defined USE_MADVISE
346     /* the ds_def will be needed soonish, so hint accordingly */
347     madvise(data + PAGE_START(offset),
348             sizeof(ds_def_t) * rrd->stat_head->ds_cnt, MADV_WILLNEED);
349 #endif
350     __rrd_read(rrd->ds_def, ds_def_t,
351                rrd->stat_head->ds_cnt);
352
353 #if defined USE_MADVISE
354     /* the rra_def will be needed soonish, so hint accordingly */
355     madvise(data + PAGE_START(offset),
356             sizeof(rra_def_t) * rrd->stat_head->rra_cnt, MADV_WILLNEED);
357 #endif
358     __rrd_read(rrd->rra_def, rra_def_t,
359                rrd->stat_head->rra_cnt);
360
361     /* handle different format for the live_head */
362     if (version < 3) {
363         rrd->live_head = (live_head_t *) malloc(sizeof(live_head_t));
364         if (rrd->live_head == NULL) {
365             rrd_set_error("live_head_t malloc");
366             goto out_close;
367         }
368 #if defined USE_MADVISE
369         /* the live_head will be needed soonish, so hint accordingly */
370         madvise(data + PAGE_START(offset), sizeof(time_t), MADV_WILLNEED);
371 #endif
372         __rrd_read(rrd->legacy_last_up, time_t,
373                    1);
374
375         rrd->live_head->last_up = *rrd->legacy_last_up;
376         rrd->live_head->last_up_usec = 0;
377     } else {
378 #if defined USE_MADVISE
379         /* the live_head will be needed soonish, so hint accordingly */
380         madvise(data + PAGE_START(offset),
381                 sizeof(live_head_t), MADV_WILLNEED);
382 #endif
383         __rrd_read(rrd->live_head, live_head_t,
384                    1);
385     }
386     __rrd_read(rrd->pdp_prep, pdp_prep_t,
387                rrd->stat_head->ds_cnt);
388     __rrd_read(rrd->cdp_prep, cdp_prep_t,
389                rrd->stat_head->rra_cnt * rrd->stat_head->ds_cnt);
390     __rrd_read(rrd->rra_ptr, rra_ptr_t,
391                rrd->stat_head->rra_cnt);
392
393     rrd_file->header_len = offset;
394     rrd_file->pos = offset;
395
396     {
397       unsigned long row_cnt = 0;
398
399       for (ui=0; ui<rrd->stat_head->rra_cnt; ui++)
400         row_cnt += rrd->rra_def[ui].row_cnt;
401
402       size_t  correct_len = rrd_file->header_len +
403         sizeof(rrd_value_t) * row_cnt * rrd->stat_head->ds_cnt;
404
405       if (correct_len > rrd_file->file_len)
406       {
407         rrd_set_error("'%s' is too small (should be %ld bytes)",
408                       file_name, (long long) correct_len);
409         goto out_nullify_head;
410       }
411     }
412
413   out_done:
414     return (rrd_file);
415   out_nullify_head:
416     rrd->stat_head = NULL;
417   out_close:
418 #ifdef HAVE_MMAP
419     if (data != MAP_FAILED)
420       munmap(data, rrd_file->file_len);
421 #endif
422
423     close(rrd_simple_file->fd);
424   out_free:
425     free(rrd_file->pvt);
426     free(rrd_file);
427     return NULL;
428 }
429
430
431 #if defined DEBUG && DEBUG > 1
432 /* Print list of in-core pages of a the current rrd_file.  */
433 static
434 void mincore_print(
435     rrd_file_t *rrd_file,
436     char *mark)
437 {
438     rrd_simple_file_t *rrd_simple_file;
439     rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
440 #ifdef HAVE_MMAP
441     /* pretty print blocks in core */
442     size_t     off;
443     unsigned char *vec;
444     ssize_t   _page_size = sysconf(_SC_PAGESIZE);
445
446     off = rrd_file->file_len +
447         ((rrd_file->file_len + _page_size - 1) / _page_size);
448     vec = malloc(off);
449     if (vec != NULL) {
450         memset(vec, 0, off);
451         if (mincore(rrd_simple_file->file_start, rrd_file->file_len, vec) == 0) {
452             int       prev;
453             unsigned  is_in = 0, was_in = 0;
454
455             for (off = 0, prev = 0; off < rrd_file->file_len; ++off) {
456                 is_in = vec[off] & 1;   /* if lsb set then is core resident */
457                 if (off == 0)
458                     was_in = is_in;
459                 if (was_in != is_in) {
460                     fprintf(stderr, "%s: %sin core: %p len %ld\n", mark,
461                             was_in ? "" : "not ", vec + prev, off - prev);
462                     was_in = is_in;
463                     prev = off;
464                 }
465             }
466             fprintf(stderr,
467                     "%s: %sin core: %p len %ld\n", mark,
468                     was_in ? "" : "not ", vec + prev, off - prev);
469         } else
470             fprintf(stderr, "mincore: %s", rrd_strerror(errno));
471     }
472 #else
473     fprintf(stderr, "sorry mincore only works with mmap");
474 #endif
475 }
476 #endif                          /* defined DEBUG && DEBUG > 1 */
477
478 /*
479  * get exclusive lock to whole file.
480  * lock gets removed when we close the file
481  *
482  * returns 0 on success
483  */
484 int rrd_lock(
485     rrd_file_t *rrd_file)
486 {
487     int       rcstat;
488     rrd_simple_file_t *rrd_simple_file;
489     rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
490
491     {
492 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
493         struct _stat st;
494
495         if (_fstat(rrd_simple_file->fd, &st) == 0) {
496             rcstat = _locking(rrd_simple_file->fd, _LK_NBLCK, st.st_size);
497         } else {
498             rcstat = -1;
499         }
500 #else
501         struct flock lock;
502
503         lock.l_type = F_WRLCK;  /* exclusive write lock */
504         lock.l_len = 0; /* whole file */
505         lock.l_start = 0;   /* start of file */
506         lock.l_whence = SEEK_SET;   /* end of file */
507
508         rcstat = fcntl(rrd_simple_file->fd, F_SETLK, &lock);
509 #endif
510     }
511
512     return (rcstat);
513 }
514
515
516 /* drop cache except for the header and the active pages */
517 void rrd_dontneed(
518     rrd_file_t *rrd_file,
519     rrd_t *rrd)
520 {
521     rrd_simple_file_t *rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
522 #if defined USE_MADVISE || defined HAVE_POSIX_FADVISE
523     size_t dontneed_start;
524     size_t rra_start;
525     size_t active_block;
526     size_t i;
527     ssize_t   _page_size = sysconf(_SC_PAGESIZE);
528
529     if (rrd_file == NULL) {
530 #if defined DEBUG && DEBUG
531             fprintf (stderr, "rrd_dontneed: Argument 'rrd_file' is NULL.\n");
532 #endif
533             return;
534     }
535
536 #if defined DEBUG && DEBUG > 1
537     mincore_print(rrd_file, "before");
538 #endif
539
540     /* ignoring errors from RRDs that are smaller then the file_len+rounding */
541     rra_start = rrd_file->header_len;
542     dontneed_start = PAGE_START(rra_start) + _page_size;
543     for (i = 0; i < rrd->stat_head->rra_cnt; ++i) {
544         active_block =
545             PAGE_START(rra_start
546                        + rrd->rra_ptr[i].cur_row
547                        * rrd->stat_head->ds_cnt * sizeof(rrd_value_t));
548         if (active_block > dontneed_start) {
549 #ifdef USE_MADVISE
550             madvise(rrd_simple_file->file_start + dontneed_start,
551                     active_block - dontneed_start - 1, MADV_DONTNEED);
552 #endif
553 /* in linux at least only fadvise DONTNEED seems to purge pages from cache */
554 #ifdef HAVE_POSIX_FADVISE
555             posix_fadvise(rrd_simple_file->fd, dontneed_start,
556                           active_block - dontneed_start - 1,
557                           POSIX_FADV_DONTNEED);
558 #endif
559         }
560         dontneed_start = active_block;
561         /* do not release 'hot' block if update for this RAA will occur
562          * within 10 minutes */
563         if (rrd->stat_head->pdp_step * rrd->rra_def[i].pdp_cnt -
564             rrd->live_head->last_up % (rrd->stat_head->pdp_step *
565                                        rrd->rra_def[i].pdp_cnt) < 10 * 60) {
566             dontneed_start += _page_size;
567         }
568         rra_start +=
569             rrd->rra_def[i].row_cnt * rrd->stat_head->ds_cnt *
570             sizeof(rrd_value_t);
571     }
572
573     if (dontneed_start < rrd_file->file_len) {
574 #ifdef USE_MADVISE
575             madvise(rrd_simple_file->file_start + dontneed_start,
576                     rrd_file->file_len - dontneed_start, MADV_DONTNEED);
577 #endif
578 #ifdef HAVE_POSIX_FADVISE
579             posix_fadvise(rrd_simple_file->fd, dontneed_start,
580                           rrd_file->file_len - dontneed_start,
581                           POSIX_FADV_DONTNEED);
582 #endif
583     }
584
585 #if defined DEBUG && DEBUG > 1
586     mincore_print(rrd_file, "after");
587 #endif
588 #endif                          /* without madvise and posix_fadvise it does not make much sense todo anything */
589 }
590
591
592
593
594
595 int rrd_close(
596     rrd_file_t *rrd_file)
597 {
598     rrd_simple_file_t *rrd_simple_file;
599     rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
600     int       ret;
601
602 #ifdef HAVE_MMAP
603     ret = msync(rrd_simple_file->file_start, rrd_file->file_len, MS_ASYNC);
604     if (ret != 0)
605         rrd_set_error("msync rrd_file: %s", rrd_strerror(errno));
606     ret = munmap(rrd_simple_file->file_start, rrd_file->file_len);
607     if (ret != 0)
608         rrd_set_error("munmap rrd_file: %s", rrd_strerror(errno));
609 #endif
610     ret = close(rrd_simple_file->fd);
611     if (ret != 0)
612         rrd_set_error("closing file: %s", rrd_strerror(errno));
613     free(rrd_file->pvt);
614     free(rrd_file);
615     rrd_file = NULL;
616     return ret;
617 }
618
619
620 /* Set position of rrd_file.  */
621
622 off_t rrd_seek(
623     rrd_file_t *rrd_file,
624     off_t off,
625     int whence)
626 {
627     off_t     ret = 0;
628     rrd_simple_file_t *rrd_simple_file;
629     rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
630
631 #ifdef HAVE_MMAP
632     if (whence == SEEK_SET)
633         rrd_file->pos = off;
634     else if (whence == SEEK_CUR)
635         rrd_file->pos += off;
636     else if (whence == SEEK_END)
637         rrd_file->pos = rrd_file->file_len + off;
638 #else
639     ret = lseek(rrd_simple_file->fd, off, whence);
640     if (ret < 0)
641         rrd_set_error("lseek: %s", rrd_strerror(errno));
642     rrd_file->pos = ret;
643 #endif
644     /* mimic fseek, which returns 0 upon success */
645     return ret < 0;     /*XXX: or just ret to mimic lseek */
646 }
647
648
649 /* Get current position in rrd_file.  */
650
651 off_t rrd_tell(
652     rrd_file_t *rrd_file)
653 {
654     return rrd_file->pos;
655 }
656
657
658 /* Read count bytes into buffer buf, starting at rrd_file->pos.
659  * Returns the number of bytes read or <0 on error.  */
660
661 ssize_t rrd_read(
662     rrd_file_t *rrd_file,
663     void *buf,
664     size_t count)
665 {
666     rrd_simple_file_t *rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
667 #ifdef HAVE_MMAP
668     size_t    _cnt = count;
669     ssize_t   _surplus;
670
671     if (rrd_file->pos > rrd_file->file_len || _cnt == 0)    /* EOF */
672         return 0;
673     if (buf == NULL)
674         return -1;      /* EINVAL */
675     _surplus = rrd_file->pos + _cnt - rrd_file->file_len;
676     if (_surplus > 0) { /* short read */
677         _cnt -= _surplus;
678     }
679     if (_cnt == 0)
680         return 0;       /* EOF */
681     buf = memcpy(buf, rrd_simple_file->file_start + rrd_file->pos, _cnt);
682
683     rrd_file->pos += _cnt;  /* mimmic read() semantics */
684     return _cnt;
685 #else
686     ssize_t   ret;
687
688     ret = read(rrd_simple_file->fd, buf, count);
689     if (ret > 0)
690         rrd_file->pos += ret;   /* mimmic read() semantics */
691     return ret;
692 #endif
693 }
694
695
696 /* Write count bytes from buffer buf to the current position
697  * rrd_file->pos of rrd_simple_file->fd.
698  * Returns the number of bytes written or <0 on error.  */
699
700 ssize_t rrd_write(
701     rrd_file_t *rrd_file,
702     const void *buf,
703     size_t count)
704 {
705     rrd_simple_file_t *rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
706 #ifdef HAVE_MMAP
707     size_t old_size = rrd_file->file_len;
708     if (count == 0)
709         return 0;
710     if (buf == NULL)
711         return -1;      /* EINVAL */
712     
713     if((rrd_file->pos + count) > old_size)
714     {
715         rrd_set_error("attempting to write beyond end of file (%ld + %ld > %ld)",rrd_file->pos, count, old_size);
716         return -1;
717     }
718     memcpy(rrd_simple_file->file_start + rrd_file->pos, buf, count);
719     rrd_file->pos += count;
720     return count;       /* mimmic write() semantics */
721 #else
722     ssize_t   _sz = write(rrd_simple_file->fd, buf, count);
723
724     if (_sz > 0)
725         rrd_file->pos += _sz;
726     return _sz;
727 #endif
728 }
729
730
731 /* this is a leftover from the old days, it serves no purpose
732    and is therefore turned into a no-op */
733 void rrd_flush(
734     rrd_file_t UNUSED(*rrd_file))
735 {
736 }
737
738 /* Initialize RRD header.  */
739
740 void rrd_init(
741     rrd_t *rrd)
742 {
743     rrd->stat_head = NULL;
744     rrd->ds_def = NULL;
745     rrd->rra_def = NULL;
746     rrd->live_head = NULL;
747     rrd->legacy_last_up = NULL;
748     rrd->rra_ptr = NULL;
749     rrd->pdp_prep = NULL;
750     rrd->cdp_prep = NULL;
751     rrd->rrd_value = NULL;
752 }
753
754
755 /* free RRD header data.  */
756
757 #ifdef HAVE_MMAP
758 void rrd_free(
759     rrd_t *rrd)
760 {
761     if (rrd->legacy_last_up) {  /* this gets set for version < 3 only */
762         free(rrd->live_head);
763     }
764 }
765 #else
766 void rrd_free(
767     rrd_t *rrd)
768 {
769     free(rrd->live_head);
770     free(rrd->stat_head);
771     free(rrd->ds_def);
772     free(rrd->rra_def);
773     free(rrd->rra_ptr);
774     free(rrd->pdp_prep);
775     free(rrd->cdp_prep);
776     free(rrd->rrd_value);
777 }
778 #endif
779
780
781 /* routine used by external libraries to free memory allocated by
782  * rrd library */
783
784 void rrd_freemem(
785     void *mem)
786 {
787     free(mem);
788 }
789
790 /*
791  * rra_update informs us about the RRAs being updated
792  * The low level storage API may use this information for
793  * aligning RRAs within stripes, or other performance enhancements
794  */
795 void rrd_notify_row(
796     rrd_file_t UNUSED(*rrd_file),
797     int UNUSED(rra_idx),
798     unsigned long UNUSED(rra_row),
799     time_t UNUSED(rra_time))
800 {
801 }
802
803 /*
804  * This function is called when creating a new RRD
805  * The storage implementation can use this opportunity to select
806  * a sensible starting row within the file.
807  * The default implementation is random, to ensure that all RRAs
808  * don't change to a new disk block at the same time
809  */
810 unsigned long rrd_select_initial_row(
811     rrd_file_t UNUSED(*rrd_file),
812     int UNUSED(rra_idx),
813     rra_def_t *rra
814     )
815 {
816     return rrd_random() % rra->row_cnt;
817 }