e7eed20a0806493bd11c988692ff9d3d0267bdc0
[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 /* open a database file, return its header and a open filehandle */
70 /* positioned to the first cdp in the first rra */
71
72 rrd_file_t *rrd_open(
73     const char *const file_name,
74     rrd_t *rrd,
75     unsigned rdwr)
76 {
77     int       flags = 0;
78     mode_t    mode = S_IRUSR;
79     int       version, prot = PROT_READ;
80     off_t     offset = 0;
81     char     *data;
82     struct stat statb;
83     rrd_file_t *rrd_file = malloc(sizeof(rrd_file_t));
84
85     if (rrd_file == NULL) {
86         rrd_set_error("allocating rrd_file descriptor for '%s'", file_name);
87         return NULL;
88     }
89     memset(rrd_file, 0, sizeof(rrd_file_t));
90     rrd_init(rrd);
91     if (rdwr == RRD_READWRITE) {
92         mode |= S_IWUSR;
93         prot |= PROT_WRITE;
94     } else if (rdwr == RRD_CREAT) {
95         mode |= S_IWUSR;
96         prot |= PROT_WRITE;
97         flags |= (O_CREAT | O_TRUNC);
98     }
99 #ifdef O_NONBLOCK
100     flags |= O_NONBLOCK;
101 #endif
102
103     if ((rrd_file->fd = open(file_name, flags, mode)) < 0) {
104         rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
105         return NULL;
106     }
107
108     /* ???: length = lseek(rrd_file->fd, 0, SEEK_END); */
109     /* ??? locking the whole area of the file may overdo it a bit, does it? */
110     if ((fstat(rrd_file->fd, &statb)) < 0) {
111         rrd_set_error("fstat '%s': %s", file_name, rrd_strerror(errno));
112         goto out_close;
113     }
114     rrd_file->file_len = statb.st_size;
115
116 #ifdef HAVE_POSIX_FADVISE
117     /* In general we need no read-ahead when dealing with rrd_files.
118        When we stop reading, it is highly unlikely that we start up again.
119        In this manner we actually save time and diskaccess (and buffer cache).
120        Thanks to Dave Plonka for the Idea of using POSIX_FADV_RANDOM here. */
121     if (0 != posix_fadvise(rrd_file->fd, 0, 0, POSIX_FADV_RANDOM)) {
122         rrd_set_error("setting POSIX_FADV_RANDOM on '%s': %s", file_name,
123                       rrd_strerror(errno));
124         goto out_close;
125     }
126 #endif
127
128 /*
129         if (rdwr == RRD_READWRITE)
130         {
131            if (setvbuf((rrd_file->fd),NULL,_IONBF,2)) {
132                   rrd_set_error("failed to disable the stream buffer\n");
133                   return (-1);
134            }
135         }
136 */
137     data = mmap(0, rrd_file->file_len, prot, MAP_SHARED,
138                 rrd_file->fd, offset);
139
140     /* lets see if the first read worked */
141     if (data == MAP_FAILED) {
142         rrd_set_error("error mmaping file '%s'", file_name);
143         goto out_close;
144     }
145     rrd_file->file_start = data;
146 #ifdef USE_MADVISE
147     if (rrd == NULL) {  /*XXX: currently not used! */
148         /* We will read everything in a moment (copying) */
149         madvise(data, rrd_file->file_len, MADV_WILLNEED | MADV_SEQUENTIAL);
150         goto out_done;
151     }
152     /* We do not need to read anything in for the moment */
153     madvise(data, rrd_file->file_len, MADV_DONTNEED);
154 #endif
155
156 #ifdef USE_MADVISE
157     /* the stat_head will be needed soonish, so hint accordingly */
158     madvise(data + offset, sizeof(stat_head_t), MADV_WILLNEED);
159 #endif
160
161     rrd->stat_head = (stat_head_t *) (data + offset);
162     offset += sizeof(stat_head_t);
163
164     /* lets do some test if we are on track ... */
165     if (memcmp(rrd->stat_head->cookie, RRD_COOKIE, sizeof(RRD_COOKIE)) != 0) {
166         rrd_set_error("'%s' is not an RRD file", file_name);
167         goto out_nullify_head;
168     }
169
170     if (rrd->stat_head->float_cookie != FLOAT_COOKIE) {
171         rrd_set_error("This RRD was created on other architecture");
172         goto out_nullify_head;
173     }
174
175     version = atoi(rrd->stat_head->version);
176
177     if (version > atoi(RRD_VERSION)) {
178         rrd_set_error("can't handle RRD file version %s",
179                       rrd->stat_head->version);
180         goto out_nullify_head;
181     }
182 #ifdef USE_MADVISE
183     /* the ds_def will be needed soonish, so hint accordingly */
184     madvise(data + offset, sizeof(ds_def_t) * rrd->stat_head->ds_cnt,
185             MADV_WILLNEED);
186 #endif
187     rrd->ds_def = (ds_def_t *) (data + offset);
188     offset += sizeof(ds_def_t) * rrd->stat_head->ds_cnt;
189
190 #ifdef USE_MADVISE
191     /* the rra_def will be needed soonish, so hint accordingly */
192     madvise(data + offset, sizeof(rra_def_t) * rrd->stat_head->rra_cnt,
193             MADV_WILLNEED);
194 #endif
195     rrd->rra_def = (rra_def_t *) (data + offset);
196     offset += sizeof(rra_def_t) * rrd->stat_head->rra_cnt;
197
198     /* handle different format for the live_head */
199     if (version < 3) {
200         rrd->live_head = (live_head_t *) malloc(sizeof(live_head_t));
201         if (rrd->live_head == NULL) {
202             rrd_set_error("live_head_t malloc");
203             goto out_close;
204         }
205         memmove(&rrd->live_head->last_up, data + offset, sizeof(long));
206         rrd->live_head->last_up_usec = 0;
207     } else {
208 #ifdef USE_MADVISE
209         /* the live_head will be needed soonish, so hint accordingly */
210         madvise(data + offset, sizeof(live_head_t), MADV_WILLNEED);
211 #endif
212         rrd->live_head = (live_head_t *) (data + offset);
213         offset += sizeof(live_head_t);
214     }
215 // This doesn't look like it needs madvise
216     rrd->pdp_prep = (pdp_prep_t *) (data + offset);
217     offset += sizeof(pdp_prep_t) * rrd->stat_head->ds_cnt;
218
219 // This could benefit from madvise()ing
220     rrd->cdp_prep = (cdp_prep_t *) (data + offset);
221     offset += sizeof(cdp_prep_t) *
222         (rrd->stat_head->rra_cnt * rrd->stat_head->ds_cnt);
223
224 // This could benefit from madvise()ing
225     rrd->rra_ptr = (rra_ptr_t *) (data + offset);
226     offset += sizeof(rra_ptr_t) * rrd->stat_head->rra_cnt;
227 #ifdef USE_MADVISE
228   out_done:
229 #endif
230     rrd_file->header_len = offset;
231     rrd_file->pos = offset;
232 /* we could close(rrd_file->fd); here, the mapping is still valid anyway */
233     return (rrd_file);
234   out_nullify_head:
235     rrd->stat_head = NULL;
236   out_close:
237     close(rrd_file->fd);
238     return NULL;
239 }
240
241 /* Close a reference to an rrd_file.  */
242 int rrd_close(
243     rrd_file_t * rrd_file)
244 {
245     int       ret = 0;
246
247 #ifdef HAVE_MMAP
248     ret = munmap(rrd_file->file_start, rrd_file->file_len);
249 //  if (ret != 0)
250 //      rrd_set_error("munmap rrd_file");
251 #endif
252     free(rrd_file);
253     rrd_file = NULL;
254     return ret;
255 }
256
257 /* Set position of rrd_file.  */
258 off_t rrd_seek(
259     rrd_file_t * rrd_file,
260     off_t off,
261     int whence)
262 {
263     off_t     ret = 0;
264
265 #ifdef HAVE_MMAP
266     if (whence == SEEK_SET)
267         rrd_file->pos = off;
268     else if (whence == SEEK_CUR)
269         rrd_file->pos += off;
270     else if (whence == SEEK_END)
271         rrd_file->pos = rrd_file->file_len + off;
272 #else
273     ret = lseek(rrd_file->fd, off, whence);
274     if (ret < 0)
275         rrd_set_error("lseek: %s", rrd_strerror(errno));
276     rrd_file->pos = ret;
277 #endif
278 //XXX: mimic fseek, which returns 0 upon success
279     return ret == -1;   //XXX: or just ret to mimic lseek
280 }
281
282 /* Get current position in rrd_file.  */
283 off_t rrd_tell(
284     rrd_file_t * rrd_file)
285 {
286     return rrd_file->pos;
287 }
288
289 /* read count bytes into buffer buf, starting at rrd_file->pos.
290  * Returns the number of bytes read.  */
291 ssize_t rrd_read(
292     rrd_file_t * rrd_file,
293     void *buf,
294     size_t count)
295 {
296 #ifdef HAVE_MMAP
297     char     *pos = rrd_file->file_start + rrd_file->pos;
298
299     buf = memmove(buf, pos, count);
300     return count;
301 #else
302     ssize_t   ret;
303
304     ret = read(rrd_file->fd, buf, count);
305     //XXX: eventually add generic rrd_set_error(""); here
306     return ret;
307 #endif
308 }
309
310 /* write count bytes from buffer buf to the current position
311  * rrd_file->pos of rrd_file->fd.  */
312 ssize_t rrd_write(
313     rrd_file_t * rrd_file,
314     const void *buf,
315     size_t count)
316 {
317     ssize_t   ret = count;
318
319 #ifdef HAVE_MMAP
320     char     *off, *new_pos;
321
322     off = rrd_file->file_start + rrd_file->pos;
323     new_pos = memmove(rrd_file->file_start + rrd_file->pos, buf, count);
324     ret = new_pos - off;
325 #else
326     ret = write(rrd_file->fd, buf, count)
327 #endif
328         return ret;
329 }
330
331 /* flush all data pending to be written to FD.  */
332 void rrd_flush(
333     rrd_file_t * rrd_file)
334 {
335     if (fdatasync(rrd_file->fd) != 0) {
336         rrd_set_error("flushing fd %d: %s", rrd_file->fd,
337                       rrd_strerror(errno));
338     }
339 }
340
341 void rrd_init(
342     rrd_t *rrd)
343 {
344     rrd->stat_head = NULL;
345     rrd->ds_def = NULL;
346     rrd->rra_def = NULL;
347     rrd->live_head = NULL;
348     rrd->rra_ptr = NULL;
349     rrd->pdp_prep = NULL;
350     rrd->cdp_prep = NULL;
351     rrd->rrd_value = NULL;
352 }
353
354 void rrd_free(
355     rrd_t UNUSED(*rrd))
356 {
357 #ifndef HAVE_MMAP
358     if (atoi(rrd->stat_head->version) < 3)
359         free(rrd->live_head);
360     free(rrd->stat_head);
361     free(rrd->ds_def);
362     free(rrd->rra_def);
363     free(rrd->rra_ptr);
364     free(rrd->pdp_prep);
365     free(rrd->cdp_prep);
366     free(rrd->rrd_value);
367 #endif
368 }
369
370 /* routine used by external libraries to free memory allocated by
371  * rrd library */
372 void rrd_freemem(
373     void *mem)
374 {
375     free(mem);
376 }
377
378 int readfile(
379     const char *file_name,
380     char **buffer,
381     int skipfirst)
382 {
383     long      writecnt = 0, totalcnt = MEMBLK;
384     long      offset = 0;
385     FILE     *input = NULL;
386     char      c;
387
388     if ((strcmp("-", file_name) == 0)) {
389         input = stdin;
390     } else {
391         if ((input = fopen(file_name, "rb")) == NULL) {
392             rrd_set_error("opening '%s': %s", file_name, rrd_strerror(errno));
393             return (-1);
394         }
395     }
396     if (skipfirst) {
397         do {
398             c = getc(input);
399             offset++;
400         } while (c != '\n' && !feof(input));
401     }
402     if (strcmp("-", file_name)) {
403         fseek(input, 0, SEEK_END);
404         /* have extra space for detecting EOF without realloc */
405         totalcnt = (ftell(input) + 1) / sizeof(char) - offset;
406         if (totalcnt < MEMBLK)
407             totalcnt = MEMBLK;  /* sanitize */
408         fseek(input, offset * sizeof(char), SEEK_SET);
409     }
410     if (((*buffer) = (char *) malloc((totalcnt + 4) * sizeof(char))) == NULL) {
411         perror("Allocate Buffer:");
412         exit(1);
413     };
414     do {
415         writecnt +=
416             fread((*buffer) + writecnt, 1,
417                   (totalcnt - writecnt) * sizeof(char), input);
418         if (writecnt >= totalcnt) {
419             totalcnt += MEMBLK;
420             if (((*buffer) =
421                  rrd_realloc((*buffer),
422                              (totalcnt + 4) * sizeof(char))) == NULL) {
423                 perror("Realloc Buffer:");
424                 exit(1);
425             };
426         }
427     } while (!feof(input));
428     (*buffer)[writecnt] = '\0';
429     if (strcmp("-", file_name) != 0) {
430         fclose(input);
431     };
432     return writecnt;
433 }