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