fix off by 1 error
[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 #endif
305     if (rdwr & RRD_CREAT)
306         goto out_done;
307 #ifdef USE_MADVISE
308     if (rdwr & RRD_COPY) {
309         /* We will read everything in a moment (copying) */
310         madvise(data, rrd_file->file_len, MADV_WILLNEED );
311         madvise(data, rrd_file->file_len, MADV_SEQUENTIAL );
312     } else {
313         /* We do not need to read anything in for the moment */
314         madvise(data, rrd_file->file_len, MADV_RANDOM);
315         /* the stat_head will be needed soonish, so hint accordingly */
316         madvise(data, sizeof(stat_head_t), MADV_WILLNEED);
317         madvise(data, sizeof(stat_head_t), MADV_RANDOM);
318     }
319 #endif
320
321     __rrd_read(rrd->stat_head, stat_head_t,
322                1);
323
324     /* lets do some test if we are on track ... */
325     if (memcmp(rrd->stat_head->cookie, RRD_COOKIE, sizeof(RRD_COOKIE)) != 0) {
326         rrd_set_error("'%s' is not an RRD file", file_name);
327         goto out_nullify_head;
328     }
329
330     if (rrd->stat_head->float_cookie != FLOAT_COOKIE) {
331         rrd_set_error("This RRD was created on another architecture");
332         goto out_nullify_head;
333     }
334
335     version = atoi(rrd->stat_head->version);
336
337     if (version > atoi(RRD_VERSION)) {
338         rrd_set_error("can't handle RRD file version %s",
339                       rrd->stat_head->version);
340         goto out_nullify_head;
341     }
342 #if defined USE_MADVISE
343     /* the ds_def will be needed soonish, so hint accordingly */
344     madvise(data + PAGE_START(offset),
345             sizeof(ds_def_t) * rrd->stat_head->ds_cnt, MADV_WILLNEED);
346 #endif
347     __rrd_read(rrd->ds_def, ds_def_t,
348                rrd->stat_head->ds_cnt);
349
350 #if defined USE_MADVISE
351     /* the rra_def will be needed soonish, so hint accordingly */
352     madvise(data + PAGE_START(offset),
353             sizeof(rra_def_t) * rrd->stat_head->rra_cnt, MADV_WILLNEED);
354 #endif
355     __rrd_read(rrd->rra_def, rra_def_t,
356                rrd->stat_head->rra_cnt);
357
358     /* handle different format for the live_head */
359     if (version < 3) {
360         rrd->live_head = (live_head_t *) malloc(sizeof(live_head_t));
361         if (rrd->live_head == NULL) {
362             rrd_set_error("live_head_t malloc");
363             goto out_close;
364         }
365 #if defined USE_MADVISE
366         /* the live_head will be needed soonish, so hint accordingly */
367         madvise(data + PAGE_START(offset), sizeof(time_t), MADV_WILLNEED);
368 #endif
369         __rrd_read(rrd->legacy_last_up, time_t,
370                    1);
371
372         rrd->live_head->last_up = *rrd->legacy_last_up;
373         rrd->live_head->last_up_usec = 0;
374     } else {
375 #if defined USE_MADVISE
376         /* the live_head will be needed soonish, so hint accordingly */
377         madvise(data + PAGE_START(offset),
378                 sizeof(live_head_t), MADV_WILLNEED);
379 #endif
380         __rrd_read(rrd->live_head, live_head_t,
381                    1);
382     }
383     __rrd_read(rrd->pdp_prep, pdp_prep_t,
384                rrd->stat_head->ds_cnt);
385     __rrd_read(rrd->cdp_prep, cdp_prep_t,
386                rrd->stat_head->rra_cnt * rrd->stat_head->ds_cnt);
387     __rrd_read(rrd->rra_ptr, rra_ptr_t,
388                rrd->stat_head->rra_cnt);
389
390     rrd_file->header_len = offset;
391     rrd_file->pos = offset;
392
393     {
394       unsigned long row_cnt = 0;
395
396       for (ui=0; ui<rrd->stat_head->rra_cnt; ui++)
397         row_cnt += rrd->rra_def[ui].row_cnt;
398
399       size_t  correct_len = rrd_file->header_len +
400         sizeof(rrd_value_t) * row_cnt * rrd->stat_head->ds_cnt;
401
402       if (correct_len > rrd_file->file_len)
403       {
404         rrd_set_error("'%s' is too small (should be %ld bytes)",
405                       file_name, (long long) correct_len);
406         goto out_nullify_head;
407       }
408     }
409
410   out_done:
411     return (rrd_file);
412   out_nullify_head:
413     rrd->stat_head = NULL;
414   out_close:
415 #ifdef HAVE_MMAP
416     if (data != MAP_FAILED)
417       munmap(data, rrd_file->file_len);
418 #endif
419
420     close(rrd_simple_file->fd);
421   out_free:
422     free(rrd_file->pvt);
423     free(rrd_file);
424     return NULL;
425 }
426
427
428 #if defined DEBUG && DEBUG > 1
429 /* Print list of in-core pages of a the current rrd_file.  */
430 static
431 void mincore_print(
432     rrd_file_t *rrd_file,
433     char *mark)
434 {
435     rrd_simple_file_t *rrd_simple_file;
436     rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
437 #ifdef HAVE_MMAP
438     /* pretty print blocks in core */
439     size_t     off;
440     unsigned char *vec;
441     ssize_t   _page_size = sysconf(_SC_PAGESIZE);
442
443     off = rrd_file->file_len +
444         ((rrd_file->file_len + _page_size - 1) / _page_size);
445     vec = malloc(off);
446     if (vec != NULL) {
447         memset(vec, 0, off);
448         if (mincore(rrd_simple_file->file_start, rrd_file->file_len, vec) == 0) {
449             int       prev;
450             unsigned  is_in = 0, was_in = 0;
451
452             for (off = 0, prev = 0; off < rrd_file->file_len; ++off) {
453                 is_in = vec[off] & 1;   /* if lsb set then is core resident */
454                 if (off == 0)
455                     was_in = is_in;
456                 if (was_in != is_in) {
457                     fprintf(stderr, "%s: %sin core: %p len %ld\n", mark,
458                             was_in ? "" : "not ", vec + prev, off - prev);
459                     was_in = is_in;
460                     prev = off;
461                 }
462             }
463             fprintf(stderr,
464                     "%s: %sin core: %p len %ld\n", mark,
465                     was_in ? "" : "not ", vec + prev, off - prev);
466         } else
467             fprintf(stderr, "mincore: %s", rrd_strerror(errno));
468     }
469 #else
470     fprintf(stderr, "sorry mincore only works with mmap");
471 #endif
472 }
473 #endif                          /* defined DEBUG && DEBUG > 1 */
474
475 /*
476  * get exclusive lock to whole file.
477  * lock gets removed when we close the file
478  *
479  * returns 0 on success
480  */
481 int rrd_lock(
482     rrd_file_t *rrd_file)
483 {
484     int       rcstat;
485     rrd_simple_file_t *rrd_simple_file;
486     rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
487
488     {
489 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
490         struct _stat st;
491
492         if (_fstat(rrd_simple_file->fd, &st) == 0) {
493             rcstat = _locking(rrd_simple_file->fd, _LK_NBLCK, st.st_size);
494         } else {
495             rcstat = -1;
496         }
497 #else
498         struct flock lock;
499
500         lock.l_type = F_WRLCK;  /* exclusive write lock */
501         lock.l_len = 0; /* whole file */
502         lock.l_start = 0;   /* start of file */
503         lock.l_whence = SEEK_SET;   /* end of file */
504
505         rcstat = fcntl(rrd_simple_file->fd, F_SETLK, &lock);
506 #endif
507     }
508
509     return (rcstat);
510 }
511
512
513 /* drop cache except for the header and the active pages */
514 void rrd_dontneed(
515     rrd_file_t *rrd_file,
516     rrd_t *rrd)
517 {
518     rrd_simple_file_t *rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
519 #if defined USE_MADVISE || defined HAVE_POSIX_FADVISE
520     size_t dontneed_start;
521     size_t rra_start;
522     size_t active_block;
523     size_t i;
524     ssize_t   _page_size = sysconf(_SC_PAGESIZE);
525
526     if (rrd_file == NULL) {
527 #if defined DEBUG && DEBUG
528             fprintf (stderr, "rrd_dontneed: Argument 'rrd_file' is NULL.\n");
529 #endif
530             return;
531     }
532
533 #if defined DEBUG && DEBUG > 1
534     mincore_print(rrd_file, "before");
535 #endif
536
537     /* ignoring errors from RRDs that are smaller then the file_len+rounding */
538     rra_start = rrd_file->header_len;
539     dontneed_start = PAGE_START(rra_start) + _page_size;
540     for (i = 0; i < rrd->stat_head->rra_cnt; ++i) {
541         active_block =
542             PAGE_START(rra_start
543                        + rrd->rra_ptr[i].cur_row
544                        * rrd->stat_head->ds_cnt * sizeof(rrd_value_t));
545         if (active_block > dontneed_start) {
546 #ifdef USE_MADVISE
547             madvise(rrd_simple_file->file_start + dontneed_start,
548                     active_block - dontneed_start - 1, MADV_DONTNEED);
549 #endif
550 /* in linux at least only fadvise DONTNEED seems to purge pages from cache */
551 #ifdef HAVE_POSIX_FADVISE
552             posix_fadvise(rrd_simple_file->fd, dontneed_start,
553                           active_block - dontneed_start - 1,
554                           POSIX_FADV_DONTNEED);
555 #endif
556         }
557         dontneed_start = active_block;
558         /* do not release 'hot' block if update for this RAA will occur
559          * within 10 minutes */
560         if (rrd->stat_head->pdp_step * rrd->rra_def[i].pdp_cnt -
561             rrd->live_head->last_up % (rrd->stat_head->pdp_step *
562                                        rrd->rra_def[i].pdp_cnt) < 10 * 60) {
563             dontneed_start += _page_size;
564         }
565         rra_start +=
566             rrd->rra_def[i].row_cnt * rrd->stat_head->ds_cnt *
567             sizeof(rrd_value_t);
568     }
569
570     if (dontneed_start < rrd_file->file_len) {
571 #ifdef USE_MADVISE
572             madvise(rrd_simple_file->file_start + dontneed_start,
573                     rrd_file->file_len - dontneed_start, MADV_DONTNEED);
574 #endif
575 #ifdef HAVE_POSIX_FADVISE
576             posix_fadvise(rrd_simple_file->fd, dontneed_start,
577                           rrd_file->file_len - dontneed_start,
578                           POSIX_FADV_DONTNEED);
579 #endif
580     }
581
582 #if defined DEBUG && DEBUG > 1
583     mincore_print(rrd_file, "after");
584 #endif
585 #endif                          /* without madvise and posix_fadvise it does not make much sense todo anything */
586 }
587
588
589
590
591
592 int rrd_close(
593     rrd_file_t *rrd_file)
594 {
595     rrd_simple_file_t *rrd_simple_file;
596     rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
597     int       ret;
598
599 #ifdef HAVE_MMAP
600     ret = msync(rrd_simple_file->file_start, rrd_file->file_len, MS_ASYNC);
601     if (ret != 0)
602         rrd_set_error("msync rrd_file: %s", rrd_strerror(errno));
603     ret = munmap(rrd_simple_file->file_start, rrd_file->file_len);
604     if (ret != 0)
605         rrd_set_error("munmap rrd_file: %s", rrd_strerror(errno));
606 #endif
607     ret = close(rrd_simple_file->fd);
608     if (ret != 0)
609         rrd_set_error("closing file: %s", rrd_strerror(errno));
610     free(rrd_file->pvt);
611     free(rrd_file);
612     rrd_file = NULL;
613     return ret;
614 }
615
616
617 /* Set position of rrd_file.  */
618
619 off_t rrd_seek(
620     rrd_file_t *rrd_file,
621     off_t off,
622     int whence)
623 {
624     off_t     ret = 0;
625 #ifndef HAVE_MMAP
626     rrd_simple_file_t *rrd_simple_file;
627     rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
628 #endif
629  
630 #ifdef HAVE_MMAP
631     if (whence == SEEK_SET)
632         rrd_file->pos = off;
633     else if (whence == SEEK_CUR)
634         rrd_file->pos += off;
635     else if (whence == SEEK_END)
636         rrd_file->pos = rrd_file->file_len + off;
637 #else
638     ret = lseek(rrd_simple_file->fd, off, whence);
639     if (ret < 0)
640         rrd_set_error("lseek: %s", rrd_strerror(errno));
641     rrd_file->pos = ret;
642 #endif
643     /* mimic fseek, which returns 0 upon success */
644     return ret < 0;     /*XXX: or just ret to mimic lseek */
645 }
646
647
648 /* Get current position in rrd_file.  */
649
650 off_t rrd_tell(
651     rrd_file_t *rrd_file)
652 {
653     return rrd_file->pos;
654 }
655
656
657 /* Read count bytes into buffer buf, starting at rrd_file->pos.
658  * Returns the number of bytes read or <0 on error.  */
659
660 ssize_t rrd_read(
661     rrd_file_t *rrd_file,
662     void *buf,
663     size_t count)
664 {
665     rrd_simple_file_t *rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
666 #ifdef HAVE_MMAP
667     size_t    _cnt = count;
668     ssize_t   _surplus;
669
670     if (rrd_file->pos > rrd_file->file_len || _cnt == 0)    /* EOF */
671         return 0;
672     if (buf == NULL)
673         return -1;      /* EINVAL */
674     _surplus = rrd_file->pos + _cnt - rrd_file->file_len;
675     if (_surplus > 0) { /* short read */
676         _cnt -= _surplus;
677     }
678     if (_cnt == 0)
679         return 0;       /* EOF */
680     buf = memcpy(buf, rrd_simple_file->file_start + rrd_file->pos, _cnt);
681
682     rrd_file->pos += _cnt;  /* mimmic read() semantics */
683     return _cnt;
684 #else
685     ssize_t   ret;
686
687     ret = read(rrd_simple_file->fd, buf, count);
688     if (ret > 0)
689         rrd_file->pos += ret;   /* mimmic read() semantics */
690     return ret;
691 #endif
692 }
693
694
695 /* Write count bytes from buffer buf to the current position
696  * rrd_file->pos of rrd_simple_file->fd.
697  * Returns the number of bytes written or <0 on error.  */
698
699 ssize_t rrd_write(
700     rrd_file_t *rrd_file,
701     const void *buf,
702     size_t count)
703 {
704     rrd_simple_file_t *rrd_simple_file = (rrd_simple_file_t *)rrd_file->pvt;
705 #ifdef HAVE_MMAP
706     size_t old_size = rrd_file->file_len;
707     if (count == 0)
708         return 0;
709     if (buf == NULL)
710         return -1;      /* EINVAL */
711     
712     if((rrd_file->pos + count) > old_size)
713     {
714         rrd_set_error("attempting to write beyond end of file (%ld + %ld > %ld)",rrd_file->pos, count, old_size);
715         return -1;
716     }
717     memcpy(rrd_simple_file->file_start + rrd_file->pos, buf, count);
718     rrd_file->pos += count;
719     return count;       /* mimmic write() semantics */
720 #else
721     ssize_t   _sz = write(rrd_simple_file->fd, buf, count);
722
723     if (_sz > 0)
724         rrd_file->pos += _sz;
725     return _sz;
726 #endif
727 }
728
729
730 /* this is a leftover from the old days, it serves no purpose
731    and is therefore turned into a no-op */
732 void rrd_flush(
733     rrd_file_t UNUSED(*rrd_file))
734 {
735 }
736
737 /* Initialize RRD header.  */
738
739 void rrd_init(
740     rrd_t *rrd)
741 {
742     rrd->stat_head = NULL;
743     rrd->ds_def = NULL;
744     rrd->rra_def = NULL;
745     rrd->live_head = NULL;
746     rrd->legacy_last_up = NULL;
747     rrd->rra_ptr = NULL;
748     rrd->pdp_prep = NULL;
749     rrd->cdp_prep = NULL;
750     rrd->rrd_value = NULL;
751 }
752
753
754 /* free RRD header data.  */
755
756 #ifdef HAVE_MMAP
757 void rrd_free(
758     rrd_t *rrd)
759 {
760     if (rrd->legacy_last_up) {  /* this gets set for version < 3 only */
761         free(rrd->live_head);
762     }
763 }
764 #else
765 void rrd_free(
766     rrd_t *rrd)
767 {
768     free(rrd->live_head);
769     free(rrd->stat_head);
770     free(rrd->ds_def);
771     free(rrd->rra_def);
772     free(rrd->rra_ptr);
773     free(rrd->pdp_prep);
774     free(rrd->cdp_prep);
775     free(rrd->rrd_value);
776 }
777 #endif
778
779
780 /* routine used by external libraries to free memory allocated by
781  * rrd library */
782
783 void rrd_freemem(
784     void *mem)
785 {
786     free(mem);
787 }
788
789 /*
790  * rra_update informs us about the RRAs being updated
791  * The low level storage API may use this information for
792  * aligning RRAs within stripes, or other performance enhancements
793  */
794 void rrd_notify_row(
795     rrd_file_t UNUSED(*rrd_file),
796     int UNUSED(rra_idx),
797     unsigned long UNUSED(rra_row),
798     time_t UNUSED(rra_time))
799 {
800 }
801
802 /*
803  * This function is called when creating a new RRD
804  * The storage implementation can use this opportunity to select
805  * a sensible starting row within the file.
806  * The default implementation is random, to ensure that all RRAs
807  * don't change to a new disk block at the same time
808  */
809 unsigned long rrd_select_initial_row(
810     rrd_file_t UNUSED(*rrd_file),
811     int UNUSED(rra_idx),
812     rra_def_t *rra
813     )
814 {
815     return rrd_random() % rra->row_cnt;
816 }