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