2 * tclrrd.c -- A TCL interpreter extension to access the RRD library.
4 * Copyright (c) 1999,2000 Frank Strauss, Technical University of Braunschweig.
6 * Thread-safe code copyright (c) 2005 Oleg Derevenetz, CenterTelecom Voronezh ISP.
8 * See the file "COPYING" for information on usage and redistribution
9 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
22 #include "../../src/rrd_tool.h"
23 #include "../../src/rrd_format.h"
25 /* support pre-8.4 tcl */
31 extern int Tclrrd_Init(
33 extern int Tclrrd_SafeInit(
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.
43 static char **getopt_init(
50 argv2 = calloc(argc, sizeof(char *));
51 for (i = 0; i < argc; i++) {
52 argv2[i] = strdup(argv[i]);
57 static void getopt_cleanup(
63 for (i = 0; i < argc; i++) {
64 if (argv2[i] != NULL) {
71 static void getopt_free_element(
75 if (argv2[argn] != NULL) {
81 static void getopt_squieeze(
85 int i, null_i = 0, argc_tmp = *argc;
87 for (i = 0; i < argc_tmp; i++) {
88 if (argv2[i] == NULL) {
91 argv2[null_i++] = argv2[i];
98 /* Thread-safe version */
99 static int Rrd_Create(
100 ClientData __attribute__((unused)) clientData,
103 CONST84 char *argv[])
107 char *parsetime_error = NULL;
108 time_t last_up = time(NULL) - 10;
110 unsigned long int pdp_step = 300;
111 rrd_time_value_t last_up_tv;
113 argv2 = getopt_init(argc, argv);
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",
121 getopt_cleanup(argc, argv2);
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);
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",
136 getopt_cleanup(argc, argv2);
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",
144 getopt_cleanup(argc, argv2);
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",
155 getopt_cleanup(argc, argv2);
158 long_tmp = atol(argv2[argv_i]);
160 Tcl_AppendResult(interp,
161 "RRD Error: step size should be no less than one second",
163 getopt_cleanup(argc, argv2);
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);
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);
180 getopt_squieeze(&argc, argv2);
183 Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
185 getopt_cleanup(argc, argv2);
189 rrd_create_r(argv2[1], pdp_step, last_up, argc - 2,
190 (const char **)argv2 + 2);
192 getopt_cleanup(argc, argv2);
194 if (rrd_test_error()) {
195 Tcl_AppendResult(interp, "RRD Error: ",
196 rrd_get_error(), (char *) NULL);
206 /* Thread-safe version */
208 ClientData __attribute__((unused)) clientData,
211 CONST84 char *argv[])
214 Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
219 rrd_dump_r(argv[1], NULL);
221 /* NOTE: rrd_dump() writes to stdout. No interaction with TCL. */
223 if (rrd_test_error()) {
224 Tcl_AppendResult(interp, "RRD Error: ",
225 rrd_get_error(), (char *) NULL);
233 /* Thread-safe version */
234 static int Rrd_Flush(
235 ClientData __attribute__((unused)) clientData,
238 CONST84 char *argv[])
241 Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
246 rrd_cmd_flush(argc, (char**)argv);
248 if (rrd_test_error()) {
249 Tcl_AppendResult(interp, "RRD Error: ",
250 rrd_get_error(), (char *) NULL);
259 /* Thread-safe version */
261 ClientData __attribute__((unused)) clientData,
264 CONST84 char *argv[])
269 Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
274 t = rrd_last_r(argv[1]);
276 if (rrd_test_error()) {
277 Tcl_AppendResult(interp, "RRD Error: ",
278 rrd_get_error(), (char *) NULL);
283 Tcl_SetIntObj(Tcl_GetObjResult(interp), t);
290 /* Thread-safe version */
291 static int Rrd_Update(
292 ClientData __attribute__((unused)) clientData,
295 CONST84 char *argv[])
298 char **argv2, *template = NULL;
300 argv2 = getopt_init(argc, argv);
302 for (argv_i = 1; argv_i < argc; argv_i++) {
303 if (!strcmp(argv2[argv_i], "--template")
304 || !strcmp(argv2[argv_i], "-t")) {
305 if (argv_i++ >= argc) {
306 Tcl_AppendResult(interp, "RRD Error: option '",
307 argv2[argv_i - 1], "' needs an argument",
309 if (template != NULL) {
312 getopt_cleanup(argc, argv2);
315 if (template != NULL) {
318 template = strdup(argv2[argv_i]);
319 getopt_free_element(argv2, argv_i - 1);
320 getopt_free_element(argv2, argv_i);
321 } else if (!strcmp(argv2[argv_i], "--")) {
322 getopt_free_element(argv2, argv_i);
324 } else if (argv2[argv_i][0] == '-') {
325 Tcl_AppendResult(interp, "RRD Error: unknown option '",
326 argv2[argv_i], "'", (char *) NULL);
327 if (template != NULL) {
330 getopt_cleanup(argc, argv2);
335 getopt_squieeze(&argc, argv2);
338 Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
340 if (template != NULL) {
343 getopt_cleanup(argc, argv2);
347 rrd_update_r(argv2[1], template, argc - 2, (const char **)argv2 + 2);
349 if (template != NULL) {
352 getopt_cleanup(argc, argv2);
354 if (rrd_test_error()) {
355 Tcl_AppendResult(interp, "RRD Error: ",
356 rrd_get_error(), (char *) NULL);
364 static int Rrd_Lastupdate(
365 ClientData __attribute__((unused)) clientData,
368 CONST84 char *argv[])
376 unsigned long ds_cnt, i;
378 /* TODO: support for rrdcached */
380 Tcl_AppendResult(interp, "RRD Error: needs a single rrd filename",
385 argv2 = getopt_init(argc, argv);
386 if (rrd_lastupdate_r(argv2[1], &last_update,
387 &ds_cnt, &ds_namv, &last_ds) == 0) {
388 listPtr = Tcl_GetObjResult(interp);
389 for (i = 0; i < ds_cnt; i++) {
390 sprintf(s, " %28s", ds_namv[i]);
391 Tcl_ListObjAppendElement(interp, listPtr,
392 Tcl_NewStringObj(s, -1));
393 sprintf(s, "\n\n%10lu:", last_update);
394 Tcl_ListObjAppendElement(interp, listPtr,
395 Tcl_NewStringObj(s, -1));
396 for (i = 0; i < ds_cnt; i++) {
397 sprintf(s, " %s", last_ds[i]);
398 Tcl_ListObjAppendElement(interp, listPtr,
399 Tcl_NewStringObj(s, -1));
404 Tcl_ListObjAppendElement(interp, listPtr,
405 Tcl_NewStringObj(s, -1));
413 static int Rrd_Fetch(
414 ClientData __attribute__((unused)) clientData,
417 CONST84 char *argv[])
419 time_t start, end, j;
420 unsigned long step, ds_cnt, i, ii;
421 rrd_value_t *data, *datai;
427 argv2 = getopt_init(argc, argv);
428 if (rrd_fetch(argc, argv2, &start, &end, &step,
429 &ds_cnt, &ds_namv, &data) != -1) {
431 listPtr = Tcl_GetObjResult(interp);
432 for (j = start; j <= end; j += step) {
433 for (ii = 0; ii < ds_cnt; ii++) {
434 sprintf(s, "%.2f", *(datai++));
435 Tcl_ListObjAppendElement(interp, listPtr,
436 Tcl_NewStringObj(s, -1));
439 for (i = 0; i < ds_cnt; i++)
444 getopt_cleanup(argc, argv2);
446 if (rrd_test_error()) {
447 Tcl_AppendResult(interp, "RRD Error: ",
448 rrd_get_error(), (char *) NULL);
458 static int Rrd_Graph(
459 ClientData __attribute__((unused)) clientData,
462 CONST84 char *argv[])
468 char **calcpr = NULL;
469 int rc, xsize, ysize;
476 * If the "filename" is a Tcl fileID, then arrange for rrd_graph() to write to
477 * that file descriptor. Will this work with windoze? I have no idea.
479 if ((channel = Tcl_GetChannel(interp, argv[1], &mode)) != NULL) {
481 * It >is< a Tcl fileID
483 if (!(mode & TCL_WRITABLE)) {
484 Tcl_AppendResult(interp, "channel \"", argv[1],
485 "\" wasn't opened for writing", (char *) NULL);
489 * Must flush channel to make sure any buffered data is written before
490 * rrd_graph() writes to the stream
492 if (Tcl_Flush(channel) != TCL_OK) {
493 Tcl_AppendResult(interp, "flush failed for \"", argv[1], "\": ",
494 strerror(Tcl_GetErrno()), (char *) NULL);
497 if (Tcl_GetChannelHandle(channel, TCL_WRITABLE, &fd1) != TCL_OK) {
498 Tcl_AppendResult(interp,
499 "cannot get file descriptor associated with \"",
500 argv[1], "\"", (char *) NULL);
504 * Must dup() file descriptor so we can fclose(stream), otherwise the fclose()
505 * would close Tcl's file descriptor
507 if ((fd2 = dup((int) fd1)) == -1) {
508 Tcl_AppendResult(interp,
509 "dup() failed for file descriptor associated with \"",
510 argv[1], "\": ", strerror(errno), (char *) NULL);
514 * rrd_graph() wants a FILE*
516 if ((stream = fdopen(fd2, "wb")) == NULL) {
517 Tcl_AppendResult(interp,
518 "fdopen() failed for file descriptor associated with \"",
519 argv[1], "\": ", strerror(errno), (char *) NULL);
520 close(fd2); /* plug potential file descriptor leak */
526 argv2 = getopt_init(argc, argv);
529 Tcl_ResetResult(interp); /* clear error from Tcl_GetChannel() */
530 argv2 = getopt_init(argc, argv);
533 rc = rrd_graph(argc, argv2, &calcpr, &xsize, &ysize, stream, &ymin,
535 getopt_cleanup(argc, argv2);
538 fclose(stream); /* plug potential malloc & file descriptor leak */
541 sprintf(dimensions, "%d %d", xsize, ysize);
542 Tcl_AppendResult(interp, dimensions, (char *) NULL);
547 for (i = 0; calcpr[i]; i++) {
548 printf("%s\n", calcpr[i]);
556 if (rrd_test_error()) {
557 Tcl_AppendResult(interp, "RRD Error: ",
558 rrd_get_error(), (char *) NULL);
569 ClientData __attribute__((unused)) clientData,
572 CONST84 char *argv[])
576 argv2 = getopt_init(argc, argv);
577 rrd_tune(argc, argv2);
578 getopt_cleanup(argc, argv2);
580 if (rrd_test_error()) {
581 Tcl_AppendResult(interp, "RRD Error: ",
582 rrd_get_error(), (char *) NULL);
592 static int Rrd_Resize(
593 ClientData __attribute__((unused)) clientData,
596 CONST84 char *argv[])
600 argv2 = getopt_init(argc, argv);
601 rrd_resize(argc, argv2);
602 getopt_cleanup(argc, argv2);
604 if (rrd_test_error()) {
605 Tcl_AppendResult(interp, "RRD Error: ",
606 rrd_get_error(), (char *) NULL);
616 static int Rrd_Restore(
617 ClientData __attribute__((unused)) clientData,
620 CONST84 char *argv[])
624 argv2 = getopt_init(argc, argv);
625 rrd_restore(argc, argv2);
626 getopt_cleanup(argc, argv2);
628 if (rrd_test_error()) {
629 Tcl_AppendResult(interp, "RRD Error: ",
630 rrd_get_error(), (char *) NULL);
641 * The following structure defines the commands in the Rrd extension.
645 char *name; /* Name of the command. */
646 Tcl_CmdProc *proc; /* Procedure for command. */
647 int hide; /* Hide if safe interpreter */
650 static CmdInfo rrdCmds[] = {
651 {"Rrd::create", Rrd_Create, 1}, /* Thread-safe version */
652 {"Rrd::dump", Rrd_Dump, 0}, /* Thread-safe version */
653 {"Rrd::flush", Rrd_Flush, 0},
654 {"Rrd::last", Rrd_Last, 0}, /* Thread-safe version */
655 {"Rrd::lastupdate", Rrd_Lastupdate, 0}, /* Thread-safe version */
656 {"Rrd::update", Rrd_Update, 1}, /* Thread-safe version */
657 {"Rrd::fetch", Rrd_Fetch, 0},
658 {"Rrd::graph", Rrd_Graph, 1}, /* Due to RRD's API, a safe
659 interpreter cannot create
660 a graph since it writes to
661 a filename supplied by the
663 {"Rrd::tune", Rrd_Tune, 1},
664 {"Rrd::resize", Rrd_Resize, 1},
665 {"Rrd::restore", Rrd_Restore, 1},
666 {(char *) NULL, (Tcl_CmdProc *) NULL, 0}
678 if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL)
681 if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 1) == NULL) {
686 * Why a global array? In keeping with the Rrd:: namespace, why
687 * not simply create a normal variable Rrd::version and set it?
689 Tcl_SetVar2(interp, "rrd", "version", VERSION, TCL_GLOBAL_ONLY);
691 for (cmdInfoPtr = rrdCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) {
693 * Check if the command already exists and return an error
694 * to ensure we detect name clashes while loading the Rrd
697 if (Tcl_GetCommandInfo(interp, cmdInfoPtr->name, &info)) {
698 Tcl_AppendResult(interp, "command \"", cmdInfoPtr->name,
699 "\" already exists", (char *) NULL);
702 if (safe && cmdInfoPtr->hide) {
705 * Turns out the one cannot hide a command in a namespace
706 * due to a limitation of Tcl, one can only hide global
707 * commands. Thus, if we created the commands without
708 * the Rrd:: namespace in a safe interpreter, then the
709 * "unsafe" commands could be hidden -- which would allow
710 * an owning interpreter either un-hiding them or doing
711 * an "interp invokehidden". If the Rrd:: namespace is
712 * used, then it's still possible for the owning interpreter
713 * to fake out the missing commands:
715 * # Make all Rrd::* commands available in master interperter
716 * package require Rrd
717 * set safe [interp create -safe]
718 * # Make safe Rrd::* commands available in safe interperter
719 * interp invokehidden $safe -global load ./tclrrd1.2.11.so
720 * # Provide the safe interpreter with the missing commands
721 * $safe alias Rrd::update do_update $safe
722 * proc do_update {which_interp $args} {
723 * # Do some checking maybe...
725 * return [eval Rrd::update $args]
728 * Our solution for now is to just not create the "unsafe"
729 * commands in a safe interpreter.
731 if (Tcl_HideCommand(interp, cmdInfoPtr->name, cmdInfoPtr->name) !=
736 Tcl_CreateCommand(interp, cmdInfoPtr->name, cmdInfoPtr->proc,
737 (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
740 if (Tcl_PkgProvide(interp, "Rrd", VERSION) != TCL_OK) {
750 return init(interp, 0);
754 * See the comments above and note how few commands are considered "safe"...
755 * Using rrdtool in a safe interpreter has very limited functionality. It's
756 * tempting to just return TCL_ERROR and forget about it.
761 return init(interp, 1);