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