aniel Pocock reported that the argument may be NULL in low-diskspace
[rrdtool.git] / bindings / tcl / tclrrd.c
1 /*
2  * tclrrd.c -- A TCL interpreter extension to access the RRD library.
3  *
4  * Copyright (c) 1999,2000 Frank Strauss, Technical University of Braunschweig.
5  *
6  * Thread-safe code copyright (c) 2005 Oleg Derevenetz, CenterTelecom Voronezh ISP.
7  *
8  * See the file "COPYING" for information on usage and redistribution
9  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10  *
11  * $Id$
12  */
13
14
15
16 #include <errno.h>
17 #include <string.h>
18 #include <time.h>
19 #include <unistd.h>
20 #include <tcl.h>
21 #include <stdlib.h>
22 #include "../../src/rrd_tool.h"
23 #include "../../src/rrd_format.h"
24
25 /* support pre-8.4 tcl */
26
27 #ifndef CONST84
28 #   define CONST84
29 #endif
30
31 extern int Tclrrd_Init(
32     Tcl_Interp *interp);
33 extern int Tclrrd_SafeInit(
34     Tcl_Interp *interp);
35
36
37 /*
38  * some rrd_XXX() and new thread-safe versions of Rrd_XXX()
39  * functions might modify the argv strings passed to it.
40  * Hence, we need to do some preparation before
41  * calling the rrd library functions.
42  */
43 static char **getopt_init(
44     int argc,
45     CONST84 char *argv[])
46 {
47     char    **argv2;
48     int       i;
49
50     argv2 = calloc(argc, sizeof(char *));
51     for (i = 0; i < argc; i++) {
52         argv2[i] = strdup(argv[i]);
53     }
54     return argv2;
55 }
56
57 static void getopt_cleanup(
58     int argc,
59     char **argv2)
60 {
61     int       i;
62
63     for (i = 0; i < argc; i++) {
64         if (argv2[i] != NULL) {
65             free(argv2[i]);
66         }
67     }
68     free(argv2);
69 }
70
71 static void getopt_free_element(
72     char *argv2[],
73     int argn)
74 {
75     if (argv2[argn] != NULL) {
76         free(argv2[argn]);
77         argv2[argn] = NULL;
78     }
79 }
80
81 static void getopt_squieeze(
82     int *argc,
83     char *argv2[])
84 {
85     int       i, null_i = 0, argc_tmp = *argc;
86
87     for (i = 0; i < argc_tmp; i++) {
88         if (argv2[i] == NULL) {
89             (*argc)--;
90         } else {
91             argv2[null_i++] = argv2[i];
92         }
93     }
94 }
95
96
97
98 /* Thread-safe version */
99 static int Rrd_Create(
100     ClientData __attribute__((unused)) clientData,
101     Tcl_Interp *interp,
102     int argc,
103     CONST84 char *argv[])
104 {
105     int       argv_i;
106     char    **argv2;
107     char     *parsetime_error = NULL;
108     time_t    last_up = time(NULL) - 10;
109     long int  long_tmp;
110     unsigned long int pdp_step = 300;
111     rrd_time_value_t last_up_tv;
112
113     argv2 = getopt_init(argc, argv);
114
115     for (argv_i = 1; argv_i < argc; argv_i++) {
116         if (!strcmp(argv2[argv_i], "--start") || !strcmp(argv2[argv_i], "-b")) {
117             if (argv_i++ >= argc) {
118                 Tcl_AppendResult(interp, "RRD Error: option '",
119                                  argv2[argv_i - 1], "' needs an argument",
120                                  (char *) NULL);
121                 getopt_cleanup(argc, argv2);
122                 return TCL_ERROR;
123             }
124             if ((parsetime_error = rrd_parsetime(argv2[argv_i], &last_up_tv))) {
125                 Tcl_AppendResult(interp, "RRD Error: invalid time format: '",
126                                  argv2[argv_i], "'", (char *) NULL);
127                 getopt_cleanup(argc, argv2);
128                 return TCL_ERROR;
129             }
130             if (last_up_tv.type == RELATIVE_TO_END_TIME ||
131                 last_up_tv.type == RELATIVE_TO_START_TIME) {
132                 Tcl_AppendResult(interp,
133                                  "RRD Error: specifying time relative to the 'start' ",
134                                  "or 'end' makes no sense here",
135                                  (char *) NULL);
136                 getopt_cleanup(argc, argv2);
137                 return TCL_ERROR;
138             }
139             last_up = mktime(&last_up_tv.tm) +last_up_tv.offset;
140             if (last_up < 3600 * 24 * 365 * 10) {
141                 Tcl_AppendResult(interp,
142                                  "RRD Error: the first entry to the RRD should be after 1980",
143                                  (char *) NULL);
144                 getopt_cleanup(argc, argv2);
145                 return TCL_ERROR;
146             }
147             getopt_free_element(argv2, argv_i - 1);
148             getopt_free_element(argv2, argv_i);
149         } else if (!strcmp(argv2[argv_i], "--step")
150                    || !strcmp(argv2[argv_i], "-s")) {
151             if (argv_i++ >= argc) {
152                 Tcl_AppendResult(interp, "RRD Error: option '",
153                                  argv2[argv_i - 1], "' needs an argument",
154                                  (char *) NULL);
155                 getopt_cleanup(argc, argv2);
156                 return TCL_ERROR;
157             }
158             long_tmp = atol(argv2[argv_i]);
159             if (long_tmp < 1) {
160                 Tcl_AppendResult(interp,
161                                  "RRD Error: step size should be no less than one second",
162                                  (char *) NULL);
163                 getopt_cleanup(argc, argv2);
164                 return TCL_ERROR;
165             }
166             pdp_step = long_tmp;
167             getopt_free_element(argv2, argv_i - 1);
168             getopt_free_element(argv2, argv_i);
169         } else if (!strcmp(argv2[argv_i], "--")) {
170             getopt_free_element(argv2, argv_i);
171             break;
172         } else if (argv2[argv_i][0] == '-') {
173             Tcl_AppendResult(interp, "RRD Error: unknown option '",
174                              argv2[argv_i], "'", (char *) NULL);
175             getopt_cleanup(argc, argv2);
176             return TCL_ERROR;
177         }
178     }
179
180     getopt_squieeze(&argc, argv2);
181
182     if (argc < 2) {
183         Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
184                          (char *) NULL);
185         getopt_cleanup(argc, argv2);
186         return TCL_ERROR;
187     }
188
189     rrd_create_r(argv2[1], pdp_step, last_up, argc - 2,
190                  (const char **)argv2 + 2);
191
192     getopt_cleanup(argc, argv2);
193
194     if (rrd_test_error()) {
195         Tcl_AppendResult(interp, "RRD Error: ",
196                          rrd_get_error(), (char *) NULL);
197         rrd_clear_error();
198         return TCL_ERROR;
199     }
200
201     return TCL_OK;
202 }
203
204
205
206 /* Thread-safe version */
207 static int Rrd_Dump(
208     ClientData __attribute__((unused)) clientData,
209     Tcl_Interp *interp,
210     int argc,
211     CONST84 char *argv[])
212 {
213     if (argc < 2) {
214         Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
215                          (char *) NULL);
216         return TCL_ERROR;
217     }
218
219     rrd_dump_r(argv[1], NULL);
220
221     /* NOTE: rrd_dump() writes to stdout. No interaction with TCL. */
222
223     if (rrd_test_error()) {
224         Tcl_AppendResult(interp, "RRD Error: ",
225                          rrd_get_error(), (char *) NULL);
226         rrd_clear_error();
227         return TCL_ERROR;
228     }
229
230     return TCL_OK;
231 }
232
233
234
235 /* Thread-safe version */
236 static int Rrd_Last(
237     ClientData __attribute__((unused)) clientData,
238     Tcl_Interp *interp,
239     int argc,
240     CONST84 char *argv[])
241 {
242     time_t    t;
243
244     if (argc < 2) {
245         Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
246                          (char *) NULL);
247         return TCL_ERROR;
248     }
249
250     t = rrd_last_r(argv[1]);
251
252     if (rrd_test_error()) {
253         Tcl_AppendResult(interp, "RRD Error: ",
254                          rrd_get_error(), (char *) NULL);
255         rrd_clear_error();
256         return TCL_ERROR;
257     }
258
259     Tcl_SetIntObj(Tcl_GetObjResult(interp), t);
260
261     return TCL_OK;
262 }
263
264
265
266 /* Thread-safe version */
267 static int Rrd_Update(
268     ClientData __attribute__((unused)) clientData,
269     Tcl_Interp *interp,
270     int argc,
271     CONST84 char *argv[])
272 {
273     int       argv_i;
274     char    **argv2, *template = NULL;
275
276     argv2 = getopt_init(argc, argv);
277
278     for (argv_i = 1; argv_i < argc; argv_i++) {
279         if (!strcmp(argv2[argv_i], "--template")
280             || !strcmp(argv2[argv_i], "-t")) {
281             if (argv_i++ >= argc) {
282                 Tcl_AppendResult(interp, "RRD Error: option '",
283                                  argv2[argv_i - 1], "' needs an argument",
284                                  (char *) NULL);
285                 if (template != NULL) {
286                     free(template);
287                 }
288                 getopt_cleanup(argc, argv2);
289                 return TCL_ERROR;
290             }
291             if (template != NULL) {
292                 free(template);
293             }
294             template = strdup(argv2[argv_i]);
295             getopt_free_element(argv2, argv_i - 1);
296             getopt_free_element(argv2, argv_i);
297         } else if (!strcmp(argv2[argv_i], "--")) {
298             getopt_free_element(argv2, argv_i);
299             break;
300         } else if (argv2[argv_i][0] == '-') {
301             Tcl_AppendResult(interp, "RRD Error: unknown option '",
302                              argv2[argv_i], "'", (char *) NULL);
303             if (template != NULL) {
304                 free(template);
305             }
306             getopt_cleanup(argc, argv2);
307             return TCL_ERROR;
308         }
309     }
310
311     getopt_squieeze(&argc, argv2);
312
313     if (argc < 2) {
314         Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
315                          (char *) NULL);
316         if (template != NULL) {
317             free(template);
318         }
319         getopt_cleanup(argc, argv2);
320         return TCL_ERROR;
321     }
322
323     rrd_update_r(argv2[1], template, argc - 2, (const char **)argv2 + 2);
324
325     if (template != NULL) {
326         free(template);
327     }
328     getopt_cleanup(argc, argv2);
329
330     if (rrd_test_error()) {
331         Tcl_AppendResult(interp, "RRD Error: ",
332                          rrd_get_error(), (char *) NULL);
333         rrd_clear_error();
334         return TCL_ERROR;
335     }
336
337     return TCL_OK;
338 }
339
340 static int Rrd_Lastupdate(
341     ClientData __attribute__((unused)) clientData,
342     Tcl_Interp *interp,
343     int argc,
344     CONST84 char *argv[])
345 {
346     time_t    last_update;
347     char    **argv2;
348     char    **ds_namv;
349     char    **last_ds;
350     char      s[30];
351     Tcl_Obj  *listPtr;
352     unsigned long ds_cnt, i;
353
354     /* TODO: support for rrdcached */
355     if (argc != 2) {
356         Tcl_AppendResult(interp, "RRD Error: needs a single rrd filename",
357                          (char *) NULL);
358         return TCL_ERROR;
359     }
360
361     argv2 = getopt_init(argc, argv);
362     if (rrd_lastupdate_r(argv2[1], &last_update,
363                        &ds_cnt, &ds_namv, &last_ds) == 0) {
364         listPtr = Tcl_GetObjResult(interp);
365         for (i = 0; i < ds_cnt; i++) {
366             sprintf(s, " %28s", ds_namv[i]);
367             Tcl_ListObjAppendElement(interp, listPtr,
368                                      Tcl_NewStringObj(s, -1));
369             sprintf(s, "\n\n%10lu:", last_update);
370             Tcl_ListObjAppendElement(interp, listPtr,
371                                      Tcl_NewStringObj(s, -1));
372             for (i = 0; i < ds_cnt; i++) {
373                 sprintf(s, " %s", last_ds[i]);
374                 Tcl_ListObjAppendElement(interp, listPtr,
375                                          Tcl_NewStringObj(s, -1));
376                 free(last_ds[i]);
377                 free(ds_namv[i]);
378             }
379             sprintf(s, "\n");
380             Tcl_ListObjAppendElement(interp, listPtr,
381                                      Tcl_NewStringObj(s, -1));
382             free(last_ds);
383             free(ds_namv);
384         }
385     }
386     return TCL_OK;
387 }
388
389 static int Rrd_Fetch(
390     ClientData __attribute__((unused)) clientData,
391     Tcl_Interp *interp,
392     int argc,
393     CONST84 char *argv[])
394 {
395     time_t    start, end, j;
396     unsigned long step, ds_cnt, i, ii;
397     rrd_value_t *data, *datai;
398     char    **ds_namv;
399     Tcl_Obj  *listPtr;
400     char      s[30];
401     char    **argv2;
402
403     argv2 = getopt_init(argc, argv);
404     if (rrd_fetch(argc, argv2, &start, &end, &step,
405                   &ds_cnt, &ds_namv, &data) != -1) {
406         datai = data;
407         listPtr = Tcl_GetObjResult(interp);
408         for (j = start; j <= end; j += step) {
409             for (ii = 0; ii < ds_cnt; ii++) {
410                 sprintf(s, "%.2f", *(datai++));
411                 Tcl_ListObjAppendElement(interp, listPtr,
412                                          Tcl_NewStringObj(s, -1));
413             }
414         }
415         for (i = 0; i < ds_cnt; i++)
416             free(ds_namv[i]);
417         free(ds_namv);
418         free(data);
419     }
420     getopt_cleanup(argc, argv2);
421
422     if (rrd_test_error()) {
423         Tcl_AppendResult(interp, "RRD Error: ",
424                          rrd_get_error(), (char *) NULL);
425         rrd_clear_error();
426         return TCL_ERROR;
427     }
428
429     return TCL_OK;
430 }
431
432
433
434 static int Rrd_Graph(
435     ClientData __attribute__((unused)) clientData,
436     Tcl_Interp *interp,
437     int argc,
438     CONST84 char *argv[])
439 {
440     Tcl_Channel channel;
441     int       mode, fd2;
442     ClientData fd1;
443     FILE     *stream = NULL;
444     char    **calcpr = NULL;
445     int       rc, xsize, ysize;
446     double    ymin, ymax;
447     char      dimensions[50];
448     char    **argv2;
449     CONST84 char *save;
450
451     /*
452      * If the "filename" is a Tcl fileID, then arrange for rrd_graph() to write to
453      * that file descriptor.  Will this work with windoze?  I have no idea.
454      */
455     if ((channel = Tcl_GetChannel(interp, argv[1], &mode)) != NULL) {
456         /*
457          * It >is< a Tcl fileID
458          */
459         if (!(mode & TCL_WRITABLE)) {
460             Tcl_AppendResult(interp, "channel \"", argv[1],
461                              "\" wasn't opened for writing", (char *) NULL);
462             return TCL_ERROR;
463         }
464         /*
465          * Must flush channel to make sure any buffered data is written before
466          * rrd_graph() writes to the stream
467          */
468         if (Tcl_Flush(channel) != TCL_OK) {
469             Tcl_AppendResult(interp, "flush failed for \"", argv[1], "\": ",
470                              strerror(Tcl_GetErrno()), (char *) NULL);
471             return TCL_ERROR;
472         }
473         if (Tcl_GetChannelHandle(channel, TCL_WRITABLE, &fd1) != TCL_OK) {
474             Tcl_AppendResult(interp,
475                              "cannot get file descriptor associated with \"",
476                              argv[1], "\"", (char *) NULL);
477             return TCL_ERROR;
478         }
479         /*
480          * Must dup() file descriptor so we can fclose(stream), otherwise the fclose()
481          * would close Tcl's file descriptor
482          */
483         if ((fd2 = dup((int) fd1)) == -1) {
484             Tcl_AppendResult(interp,
485                              "dup() failed for file descriptor associated with \"",
486                              argv[1], "\": ", strerror(errno), (char *) NULL);
487             return TCL_ERROR;
488         }
489         /*
490          * rrd_graph() wants a FILE*
491          */
492         if ((stream = fdopen(fd2, "wb")) == NULL) {
493             Tcl_AppendResult(interp,
494                              "fdopen() failed for file descriptor associated with \"",
495                              argv[1], "\": ", strerror(errno), (char *) NULL);
496             close(fd2); /* plug potential file descriptor leak */
497             return TCL_ERROR;
498         }
499
500         save = argv[1];
501         argv[1] = "-";
502         argv2 = getopt_init(argc, argv);
503         argv[1] = save;
504     } else {
505         Tcl_ResetResult(interp);    /* clear error from Tcl_GetChannel() */
506         argv2 = getopt_init(argc, argv);
507     }
508
509     rc = rrd_graph(argc, argv2, &calcpr, &xsize, &ysize, stream, &ymin,
510                    &ymax);
511     getopt_cleanup(argc, argv2);
512
513     if (stream != NULL)
514         fclose(stream); /* plug potential malloc & file descriptor leak */
515
516     if (rc != -1) {
517         sprintf(dimensions, "%d %d", xsize, ysize);
518         Tcl_AppendResult(interp, dimensions, (char *) NULL);
519         if (calcpr) {
520 #if 0
521             int       i;
522
523             for (i = 0; calcpr[i]; i++) {
524                 printf("%s\n", calcpr[i]);
525                 free(calcpr[i]);
526             }
527 #endif
528             free(calcpr);
529         }
530     }
531
532     if (rrd_test_error()) {
533         Tcl_AppendResult(interp, "RRD Error: ",
534                          rrd_get_error(), (char *) NULL);
535         rrd_clear_error();
536         return TCL_ERROR;
537     }
538
539     return TCL_OK;
540 }
541
542
543
544 static int Rrd_Tune(
545     ClientData __attribute__((unused)) clientData,
546     Tcl_Interp *interp,
547     int argc,
548     CONST84 char *argv[])
549 {
550     char    **argv2;
551
552     argv2 = getopt_init(argc, argv);
553     rrd_tune(argc, argv2);
554     getopt_cleanup(argc, argv2);
555
556     if (rrd_test_error()) {
557         Tcl_AppendResult(interp, "RRD Error: ",
558                          rrd_get_error(), (char *) NULL);
559         rrd_clear_error();
560         return TCL_ERROR;
561     }
562
563     return TCL_OK;
564 }
565
566
567
568 static int Rrd_Resize(
569     ClientData __attribute__((unused)) clientData,
570     Tcl_Interp *interp,
571     int argc,
572     CONST84 char *argv[])
573 {
574     char    **argv2;
575
576     argv2 = getopt_init(argc, argv);
577     rrd_resize(argc, argv2);
578     getopt_cleanup(argc, argv2);
579
580     if (rrd_test_error()) {
581         Tcl_AppendResult(interp, "RRD Error: ",
582                          rrd_get_error(), (char *) NULL);
583         rrd_clear_error();
584         return TCL_ERROR;
585     }
586
587     return TCL_OK;
588 }
589
590
591
592 static int Rrd_Restore(
593     ClientData __attribute__((unused)) clientData,
594     Tcl_Interp *interp,
595     int argc,
596     CONST84 char *argv[])
597 {
598     char    **argv2;
599
600     argv2 = getopt_init(argc, argv);
601     rrd_restore(argc, argv2);
602     getopt_cleanup(argc, argv2);
603
604     if (rrd_test_error()) {
605         Tcl_AppendResult(interp, "RRD Error: ",
606                          rrd_get_error(), (char *) NULL);
607         rrd_clear_error();
608         return TCL_ERROR;
609     }
610
611     return TCL_OK;
612 }
613
614
615
616 /*
617  * The following structure defines the commands in the Rrd extension.
618  */
619
620 typedef struct {
621     char     *name;     /* Name of the command. */
622     Tcl_CmdProc *proc;  /* Procedure for command. */
623     int       hide;     /* Hide if safe interpreter */
624 } CmdInfo;
625
626 static CmdInfo rrdCmds[] = {
627     {"Rrd::create", Rrd_Create, 1}, /* Thread-safe version */
628     {"Rrd::dump", Rrd_Dump, 0}, /* Thread-safe version */
629     {"Rrd::last", Rrd_Last, 0}, /* Thread-safe version */
630     {"Rrd::lastupdate", Rrd_Lastupdate, 0}, /* Thread-safe version */
631     {"Rrd::update", Rrd_Update, 1}, /* Thread-safe version */
632     {"Rrd::fetch", Rrd_Fetch, 0},
633     {"Rrd::graph", Rrd_Graph, 1},   /* Due to RRD's API, a safe
634                                        interpreter cannot create
635                                        a graph since it writes to
636                                        a filename supplied by the
637                                        caller */
638     {"Rrd::tune", Rrd_Tune, 1},
639     {"Rrd::resize", Rrd_Resize, 1},
640     {"Rrd::restore", Rrd_Restore, 1},
641     {(char *) NULL, (Tcl_CmdProc *) NULL, 0}
642 };
643
644
645
646 static int init(
647     Tcl_Interp *interp,
648     int safe)
649 {
650     CmdInfo  *cmdInfoPtr;
651     Tcl_CmdInfo info;
652
653     if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL)
654         return TCL_ERROR;
655
656     if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 1) == NULL) {
657         return TCL_ERROR;
658     }
659
660     /*
661      * Why a global array?  In keeping with the Rrd:: namespace, why
662      * not simply create a normal variable Rrd::version and set it?
663      */
664     Tcl_SetVar2(interp, "rrd", "version", VERSION, TCL_GLOBAL_ONLY);
665
666     for (cmdInfoPtr = rrdCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) {
667         /*
668          * Check if the command already exists and return an error
669          * to ensure we detect name clashes while loading the Rrd
670          * extension.
671          */
672         if (Tcl_GetCommandInfo(interp, cmdInfoPtr->name, &info)) {
673             Tcl_AppendResult(interp, "command \"", cmdInfoPtr->name,
674                              "\" already exists", (char *) NULL);
675             return TCL_ERROR;
676         }
677         if (safe && cmdInfoPtr->hide) {
678 #if 0
679             /*
680              * Turns out the one cannot hide a command in a namespace
681              * due to a limitation of Tcl, one can only hide global
682              * commands.  Thus, if we created the commands without
683              * the Rrd:: namespace in a safe interpreter, then the
684              * "unsafe" commands could be hidden -- which would allow
685              * an owning interpreter either un-hiding them or doing
686              * an "interp invokehidden".  If the Rrd:: namespace is
687              * used, then it's still possible for the owning interpreter
688              * to fake out the missing commands:
689              *
690              *   # Make all Rrd::* commands available in master interperter
691              *   package require Rrd
692              *   set safe [interp create -safe]
693              *   # Make safe Rrd::* commands available in safe interperter
694              *   interp invokehidden $safe -global load ./tclrrd1.2.11.so
695              *   # Provide the safe interpreter with the missing commands
696              *   $safe alias Rrd::update do_update $safe
697              *   proc do_update {which_interp $args} {
698              *     # Do some checking maybe...
699              *       :
700              *     return [eval Rrd::update $args]
701              *   }
702              *
703              * Our solution for now is to just not create the "unsafe"
704              * commands in a safe interpreter.
705              */
706             if (Tcl_HideCommand(interp, cmdInfoPtr->name, cmdInfoPtr->name) !=
707                 TCL_OK)
708                 return TCL_ERROR;
709 #endif
710         } else
711             Tcl_CreateCommand(interp, cmdInfoPtr->name, cmdInfoPtr->proc,
712                               (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
713     }
714
715     if (Tcl_PkgProvide(interp, "Rrd", VERSION) != TCL_OK) {
716         return TCL_ERROR;
717     }
718
719     return TCL_OK;
720 }
721
722 int Tclrrd_Init(
723     Tcl_Interp *interp)
724 {
725     return init(interp, 0);
726 }
727
728 /*
729  * See the comments above and note how few commands are considered "safe"...
730  * Using rrdtool in a safe interpreter has very limited functionality.  It's
731  * tempting to just return TCL_ERROR and forget about it.
732  */
733 int Tclrrd_SafeInit(
734     Tcl_Interp *interp)
735 {
736     return init(interp, 1);
737 }