Updates from Bernhard Fischer rep dot nop gmail com
[rrdtool.git] / src / rrd_open.c
1 /*****************************************************************************
2  * RRDtool 1.2.23  Copyright by Tobi Oetiker, 1997-2007
3  *****************************************************************************
4  * rrd_open.c  Open an RRD File
5  *****************************************************************************
6  * $Id$
7  * $Log$
8  * Revision 1.10  2004/05/26 22:11:12  oetiker
9  * reduce compiler warnings. Many small fixes. -- Mike Slifcak <slif@bellsouth.net>
10  *
11  * Revision 1.9  2003/04/29 21:56:49  oetiker
12  * readline in rrd_open.c reads the file in 8 KB blocks, and calls realloc for
13  * each block. realloc is very slow in Mac OS X for huge blocks, e.g. when
14  * restoring databases from huge xml files. This patch finds the size of the
15  * file, and starts out with malloc'ing the full size.
16  * -- Peter Speck <speck@ruc.dk>
17  *
18  * Revision 1.8  2003/04/11 19:43:44  oetiker
19  * New special value COUNT which allows calculations based on the position of a
20  * value within a data set. Bug fix in rrd_rpncalc.c. PREV returned erroneus
21  * value for the second value. Bug fix in rrd_restore.c. Bug causing seek error
22  * when accesing an RRD restored from an xml that holds an RRD version <3.
23  * --  Ruben Justo <ruben@ainek.com>
24  *
25  * Revision 1.7  2003/03/31 21:22:12  oetiker
26  * enables RRDtool updates with microsecond or in case of windows millisecond
27  * precision. This is needed to reduce time measurement error when archive step
28  * is small. (<30s) --  Sasha Mikheev <sasha@avalon-net.co.il>
29  *
30  * Revision 1.6  2003/02/13 07:05:27  oetiker
31  * Find attached the patch I promised to send to you. Please note that there
32  * are three new source files (src/rrd_is_thread_safe.h, src/rrd_thread_safe.c
33  * and src/rrd_not_thread_safe.c) and the introduction of librrd_th. This
34  * library is identical to librrd, but it contains support code for per-thread
35  * global variables currently used for error information only. This is similar
36  * to how errno per-thread variables are implemented.  librrd_th must be linked
37  * alongside of libpthred
38  *
39  * There is also a new file "THREADS", holding some documentation.
40  *
41  * -- Peter Stamfest <peter@stamfest.at>
42  *
43  * Revision 1.5  2002/06/20 00:21:03  jake
44  * More Win32 build changes; thanks to Kerry Calvert.
45  *
46  * Revision 1.4  2002/02/01 20:34:49  oetiker
47  * fixed version number and date/time
48  *
49  * Revision 1.3  2001/03/04 13:01:55  oetiker
50  * Aberrant Behavior Detection support. A brief overview added to rrdtool.pod.
51  * Major updates to rrd_update.c, rrd_create.c. Minor update to other core files.
52  * This is backwards compatible! But new files using the Aberrant stuff are not readable
53  * by old rrdtool versions. See http://cricket.sourceforge.net/aberrant/rrd_hw.htm
54  * -- Jake Brutlag <jakeb@corp.webtv.net>
55  *
56  * Revision 1.2  2001/03/04 10:29:20  oetiker
57  * fixed filedescriptor leak
58  * -- Mike Franusich <mike@franusich.com>
59  *
60  * Revision 1.1.1.1  2001/02/25 22:25:05  oetiker
61  * checkin
62  *
63  *****************************************************************************/
64
65 #include "rrd_tool.h"
66 #include "unused.h"
67 #define MEMBLK 8192
68
69 #ifdef HAVE_MMAP
70 #define __rrd_read(dst, dst_t, cnt) \
71         (dst) = (dst_t*) (data + offset); \
72         offset += sizeof(dst_t) * (cnt)
73 #else
74 #define __rrd_read(dst, dst_t, cnt) \
75         if ((dst = malloc(sizeof(dst_t)*(cnt))) == NULL) { \
76                 rrd_set_error(#dst " malloc"); \
77                 goto out_nullify_head; \
78         } \
79         offset += read (rrd_file->fd, dst, sizeof(dst_t)*(cnt))
80 #endif
81
82 /* open a database file, return its header and an open filehandle */
83 /* positioned to the first cdp in the first rra */
84
85 rrd_file_t *rrd_open(
86     const char *const file_name,
87     rrd_t *rrd,
88     unsigned rdwr)
89 {
90     int       flags = 0;
91     mode_t    mode = S_IRUSR;
92     int       version;
93
94 #ifdef HAVE_MMAP
95     int       mm_prot = PROT_READ, mm_flags = 0;
96     char     *data;
97 #endif
98     off_t     offset = 0;
99     struct stat statb;
100     rrd_file_t *rrd_file = malloc(sizeof(rrd_file_t));
101
102     if (rrd_file == NULL) {
103         rrd_set_error("allocating rrd_file descriptor for '%s'", file_name);
104         return NULL;
105     }
106     memset(rrd_file, 0, sizeof(rrd_file_t));
107     rrd_init(rrd);
108 #ifdef DEBUG
109     if ((rdwr & (RRD_READONLY | RRD_READWRITE)) ==
110         (RRD_READONLY | RRD_READWRITE)) {
111         /* Both READONLY and READWRITE were given, which is invalid.  */
112         rrd_set_error("in read/write request mask");
113         exit(-1);
114     }
115 #endif
116     if (rdwr & RRD_READONLY) {
117         flags |= O_RDONLY;
118 #ifdef HAVE_MMAP
119         mm_flags = MAP_PRIVATE;
120 # ifdef MAP_NORESERVE
121         mm_flags |= MAP_NORESERVE;
122 # endif
123         mm_flags |= MAP_PRIVATE;
124 #endif
125     } else {
126         if (rdwr & RRD_READWRITE) {
127             mode |= S_IWUSR;
128             flags |= O_RDWR;
129 #ifdef HAVE_MMAP
130             mm_flags = MAP_SHARED;
131             mm_prot |= PROT_WRITE;
132 #endif
133         }
134         if (rdwr & RRD_CREAT) {
135             flags |= (O_CREAT | O_TRUNC);
136         }
137     }
138     if (rdwr & RRD_READAHEAD) {
139 #ifdef MAP_POPULATE
140         mm_flags |= MAP_POPULATE;
141 #endif
142 #if defined MAP_NONBLOCK && !defined USE_DIRECT_IO
143         mm_flags |= MAP_NONBLOCK;   /* just populage ptes */
144 #endif
145     } else {
146 #ifdef USE_DIRECT_IO
147         flags |= O_DIRECT;
148 #endif
149 #if 0                   //def O_NONBLOCK
150         flags |= O_NONBLOCK;
151 #endif
152     }
153
154     if ((rrd_file->fd = open(file_name, flags, mode)) < 0) {
155         rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
156         return NULL;
157     }
158
159     /* Better try to avoid seeks as much as possible. stat may be heavy but
160      * many concurrent seeks are even worse.  */
161     if ((fstat(rrd_file->fd, &statb)) < 0) {
162         rrd_set_error("fstat '%s': %s", file_name, rrd_strerror(errno));
163         goto out_close;
164     }
165     rrd_file->file_len = statb.st_size;
166
167 #ifdef HAVE_POSIX_FADVISE
168     /* In general we need no read-ahead when dealing with rrd_files.
169        When we stop reading, it is highly unlikely that we start up again.
170        In this manner we actually save time and diskaccess (and buffer cache).
171        Thanks to Dave Plonka for the Idea of using POSIX_FADV_RANDOM here. */
172     if (0 != posix_fadvise(rrd_file->fd, 0, 0, POSIX_FADV_RANDOM)) {
173         rrd_set_error("setting POSIX_FADV_RANDOM on '%s': %s", file_name,
174                       rrd_strerror(errno));
175         goto out_close;
176     }
177 #endif
178
179 /*
180         if (rdwr & RRD_READWRITE)
181         {
182            if (setvbuf((rrd_file->fd),NULL,_IONBF,2)) {
183                   rrd_set_error("failed to disable the stream buffer\n");
184                   return (-1);
185            }
186         }
187 */
188 #ifdef HAVE_MMAP
189     data = mmap(0, rrd_file->file_len, mm_prot, mm_flags,
190                 rrd_file->fd, offset);
191
192     /* lets see if the first read worked */
193     if (data == MAP_FAILED) {
194         rrd_set_error("error mmaping file '%s': %s", file_name,
195                       rrd_strerror(errno));
196         goto out_close;
197     }
198     rrd_file->file_start = data;
199 #else
200 #endif
201 #ifdef USE_MADVISE
202     if (rdwr & RRD_COPY) {  /*XXX: currently not used! */
203         /* We will read everything in a moment (copying) */
204         madvise(data, rrd_file->file_len, MADV_WILLNEED | MADV_SEQUENTIAL);
205         goto out_done;
206     }
207     /* We do not need to read anything in for the moment */
208     madvise(data, rrd_file->file_len, MADV_DONTNEED);
209 #endif
210
211 #ifdef USE_MADVISE
212     /* the stat_head will be needed soonish, so hint accordingly */
213     madvise(data + offset, sizeof(stat_head_t), MADV_WILLNEED);
214 #endif
215
216     __rrd_read(rrd->stat_head, stat_head_t,
217                1);
218
219     /* lets do some test if we are on track ... */
220     if (memcmp(rrd->stat_head->cookie, RRD_COOKIE, sizeof(RRD_COOKIE)) != 0) {
221         rrd_set_error("'%s' is not an RRD file", file_name);
222         goto out_nullify_head;
223     }
224
225     if (rrd->stat_head->float_cookie != FLOAT_COOKIE) {
226         rrd_set_error("This RRD was created on other architecture");
227         goto out_nullify_head;
228     }
229
230     version = atoi(rrd->stat_head->version);
231
232     if (version > atoi(RRD_VERSION)) {
233         rrd_set_error("can't handle RRD file version %s",
234                       rrd->stat_head->version);
235         goto out_nullify_head;
236     }
237 #ifdef USE_MADVISE
238     /* the ds_def will be needed soonish, so hint accordingly */
239     madvise(data + offset, sizeof(ds_def_t) * rrd->stat_head->ds_cnt,
240             MADV_WILLNEED);
241 #endif
242     __rrd_read(rrd->ds_def, ds_def_t,
243                rrd->stat_head->ds_cnt);
244
245 #ifdef USE_MADVISE
246     /* the rra_def will be needed soonish, so hint accordingly */
247     madvise(data + offset, sizeof(rra_def_t) * rrd->stat_head->rra_cnt,
248             MADV_WILLNEED);
249 #endif
250     __rrd_read(rrd->rra_def, rra_def_t,
251                rrd->stat_head->rra_cnt);
252
253     /* handle different format for the live_head */
254     if (version < 3) {
255         rrd->live_head = (live_head_t *) malloc(sizeof(live_head_t));
256         if (rrd->live_head == NULL) {
257             rrd_set_error("live_head_t malloc");
258             goto out_close;
259         }
260 #ifdef HAVE_MMAP
261         memmove(&rrd->live_head->last_up, data + offset, sizeof(long));
262         offset += sizeof(long);
263 #else
264         offset += read(rrd_file->fd, &rrd->live_head->last_up, sizeof(long));
265 #endif
266         rrd->live_head->last_up_usec = 0;
267     } else {
268 #ifdef USE_MADVISE
269         /* the live_head will be needed soonish, so hint accordingly */
270         madvise(data + offset, sizeof(live_head_t), MADV_WILLNEED);
271 #endif
272         __rrd_read(rrd->live_head, live_head_t,
273                    1);
274     }
275 //XXX: This doesn't look like it needs madvise
276     __rrd_read(rrd->pdp_prep, pdp_prep_t,
277                rrd->stat_head->ds_cnt);
278
279 //XXX: This could benefit from madvise()ing
280     __rrd_read(rrd->cdp_prep, cdp_prep_t,
281                rrd->stat_head->rra_cnt * rrd->stat_head->ds_cnt);
282
283 //XXX: This could benefit from madvise()ing
284     __rrd_read(rrd->rra_ptr, rra_ptr_t,
285                rrd->stat_head->rra_cnt);
286
287 #ifdef USE_MADVISE
288   out_done:
289 #endif
290     rrd_file->header_len = offset;
291     rrd_file->pos = offset;
292 /* we could close(rrd_file->fd); here, the mapping is still valid anyway */
293     return (rrd_file);
294   out_nullify_head:
295     rrd->stat_head = NULL;
296   out_close:
297     close(rrd_file->fd);
298     return NULL;
299 }
300
301 /* Close a reference to an rrd_file.  */
302 int rrd_close(
303     rrd_file_t *rrd_file)
304 {
305     int       ret = 0;
306
307 #ifdef HAVE_MMAP
308     ret = munmap(rrd_file->file_start, rrd_file->file_len);
309 //  if (ret != 0)
310 //      rrd_set_error("munmap rrd_file");
311 #endif
312     free(rrd_file);
313     rrd_file = NULL;
314     return ret;
315 }
316
317 /* Set position of rrd_file.  */
318 off_t rrd_seek(
319     rrd_file_t *rrd_file,
320     off_t off,
321     int whence)
322 {
323     off_t     ret = 0;
324
325 #ifdef HAVE_MMAP
326     if (whence == SEEK_SET)
327         rrd_file->pos = off;
328     else if (whence == SEEK_CUR)
329         rrd_file->pos += off;
330     else if (whence == SEEK_END)
331         rrd_file->pos = rrd_file->file_len + off;
332 #else
333     ret = lseek(rrd_file->fd, off, whence);
334     if (ret < 0)
335         rrd_set_error("lseek: %s", rrd_strerror(errno));
336     rrd_file->pos = ret;
337 #endif
338 //XXX: mimic fseek, which returns 0 upon success
339     return ret == -1;   //XXX: or just ret to mimic lseek
340 }
341
342 /* Get current position in rrd_file.  */
343 inline off_t rrd_tell(
344     rrd_file_t *rrd_file)
345 {
346     return rrd_file->pos;
347 }
348
349 /* read count bytes into buffer buf, starting at rrd_file->pos.
350  * Returns the number of bytes read.  */
351 ssize_t rrd_read(
352     rrd_file_t *rrd_file,
353     void *buf,
354     size_t count)
355 {
356 #ifdef HAVE_MMAP
357     char     *pos = rrd_file->file_start + rrd_file->pos;
358
359     buf = memmove(buf, pos, count);
360     rrd_file->pos += count; /* mimmic read() semantics */
361     return count;
362 #else
363     ssize_t   ret;
364
365     ret = read(rrd_file->fd, buf, count);
366     //XXX: eventually add generic rrd_set_error(""); here
367     rrd_file->pos += count; /* mimmic read() semantics */
368     return ret;
369 #endif
370 }
371
372 /* write count bytes from buffer buf to the current position
373  * rrd_file->pos of rrd_file->fd.
374  * Returns the number of bytes written.  */
375 ssize_t rrd_write(
376     rrd_file_t *rrd_file,
377     const void *buf,
378     size_t count)
379 {
380     ssize_t   ret = count;
381
382 #ifdef HAVE_MMAP
383     char     *off, *new_pos;
384
385     off = rrd_file->file_start + rrd_file->pos;
386     new_pos = memmove(rrd_file->file_start + rrd_file->pos, buf, count);
387     ret = new_pos - off;
388 #else
389     ret = write(rrd_file->fd, buf, count);
390 #endif
391     return ret;
392 }
393
394 /* flush all data pending to be written to FD.  */
395 inline void rrd_flush(
396     rrd_file_t *rrd_file)
397 {
398     if (fdatasync(rrd_file->fd) != 0) {
399         rrd_set_error("flushing fd %d: %s", rrd_file->fd,
400                       rrd_strerror(errno));
401     }
402 }
403
404 void rrd_init(
405     rrd_t *rrd)
406 {
407     rrd->stat_head = NULL;
408     rrd->ds_def = NULL;
409     rrd->rra_def = NULL;
410     rrd->live_head = NULL;
411     rrd->rra_ptr = NULL;
412     rrd->pdp_prep = NULL;
413     rrd->cdp_prep = NULL;
414     rrd->rrd_value = NULL;
415 }
416
417 void rrd_free(
418     rrd_t UNUSED(*rrd))
419 {
420 #ifndef HAVE_MMAP
421     if (atoi(rrd->stat_head->version) < 3)
422         free(rrd->live_head);
423     free(rrd->stat_head);
424     free(rrd->ds_def);
425     free(rrd->rra_def);
426     free(rrd->rra_ptr);
427     free(rrd->pdp_prep);
428     free(rrd->cdp_prep);
429     free(rrd->rrd_value);
430 #endif
431 }
432
433 /* routine used by external libraries to free memory allocated by
434  * rrd library */
435 void rrd_freemem(
436     void *mem)
437 {
438     free(mem);
439 }
440
441 int readfile(
442     const char *file_name,
443     char **buffer,
444     int skipfirst)
445 {
446     long      writecnt = 0, totalcnt = MEMBLK;
447     long      offset = 0;
448     FILE     *input = NULL;
449     char      c;
450
451     if ((strcmp("-", file_name) == 0)) {
452         input = stdin;
453     } else {
454         if ((input = fopen(file_name, "rb")) == NULL) {
455             rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
456             return (-1);
457         }
458     }
459     if (skipfirst) {
460         do {
461             c = getc(input);
462             offset++;
463         } while (c != '\n' && !feof(input));
464     }
465     if (strcmp("-", file_name)) {
466         fseek(input, 0, SEEK_END);
467         /* have extra space for detecting EOF without realloc */
468         totalcnt = (ftell(input) + 1) / sizeof(char) - offset;
469         if (totalcnt < MEMBLK)
470             totalcnt = MEMBLK;  /* sanitize */
471         fseek(input, offset * sizeof(char), SEEK_SET);
472     }
473     if (((*buffer) = (char *) malloc((totalcnt + 4) * sizeof(char))) == NULL) {
474         perror("Allocate Buffer:");
475         exit(1);
476     };
477     do {
478         writecnt +=
479             fread((*buffer) + writecnt, 1,
480                   (totalcnt - writecnt) * sizeof(char), input);
481         if (writecnt >= totalcnt) {
482             totalcnt += MEMBLK;
483             if (((*buffer) =
484                  rrd_realloc((*buffer),
485                              (totalcnt + 4) * sizeof(char))) == NULL) {
486                 perror("Realloc Buffer:");
487                 exit(1);
488             };
489         }
490     } while (!feof(input));
491     (*buffer)[writecnt] = '\0';
492     if (strcmp("-", file_name) != 0) {
493         fclose(input);
494     };
495     return writecnt;
496 }