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 */
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"
25 /* support pre-8.4 tcl */
27 #ifndef CONST84
28 # define CONST84
29 #endif
31 extern int Tclrrd_Init(
32 Tcl_Interp *interp);
33 extern int Tclrrd_SafeInit(
34 Tcl_Interp *interp);
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;
50 argv2 = calloc(argc, sizeof(char *));
51 for (i = 0; i < argc; i++) {
52 argv2[i] = strdup(argv[i]);
53 }
54 return argv2;
55 }
57 static void getopt_cleanup(
58 int argc,
59 char **argv2)
60 {
61 int i;
63 for (i = 0; i < argc; i++) {
64 if (argv2[i] != NULL) {
65 free(argv2[i]);
66 }
67 }
68 free(argv2);
69 }
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 }
81 static void getopt_squieeze(
82 int *argc,
83 char *argv2[])
84 {
85 int i, null_i = 0, argc_tmp = *argc;
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 }
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;
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",
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 }
180 getopt_squieeze(&argc, argv2);
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 }
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);
197 rrd_clear_error();
198 return TCL_ERROR;
199 }
201 return TCL_OK;
202 }
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 }
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);
226 rrd_clear_error();
227 return TCL_ERROR;
228 }
230 return TCL_OK;
231 }
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;
244 if (argc < 2) {
245 Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
246 (char *) NULL);
247 return TCL_ERROR;
248 }
250 t = rrd_last_r(argv[1]);
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 }
259 Tcl_SetIntObj(Tcl_GetObjResult(interp), t);
261 return TCL_OK;
262 }
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;
276 argv2 = getopt_init(argc, argv);
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 }
311 getopt_squieeze(&argc, argv2);
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 }
323 rrd_update_r(argv2[1], template, argc - 2, (const char **)argv2 + 2);
325 if (template != NULL) {
326 free(template);
327 }
328 getopt_cleanup(argc, argv2);
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 }
337 return TCL_OK;
338 }
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;
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 }
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 }
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;
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);
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 }
429 return TCL_OK;
430 }
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;
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 }
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 }
509 rc = rrd_graph(argc, argv2, &calcpr, &xsize, &ysize, stream, &ymin,
510 &ymax);
511 getopt_cleanup(argc, argv2);
513 if (stream != NULL)
514 fclose(stream); /* plug potential malloc & file descriptor leak */
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;
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 }
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 }
539 return TCL_OK;
540 }
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;
552 argv2 = getopt_init(argc, argv);
553 rrd_tune(argc, argv2);
554 getopt_cleanup(argc, argv2);
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 }
563 return TCL_OK;
564 }
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;
576 argv2 = getopt_init(argc, argv);
577 rrd_resize(argc, argv2);
578 getopt_cleanup(argc, argv2);
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 }
587 return TCL_OK;
588 }
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;
600 argv2 = getopt_init(argc, argv);
601 rrd_restore(argc, argv2);
602 getopt_cleanup(argc, argv2);
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 }
611 return TCL_OK;
612 }
616 /*
617 * The following structure defines the commands in the Rrd extension.
618 */
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;
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 };
646 static int init(
647 Tcl_Interp *interp,
648 int safe)
649 {
650 CmdInfo *cmdInfoPtr;
651 Tcl_CmdInfo info;
653 if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL)
654 return TCL_ERROR;
656 if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 1) == NULL) {
657 return TCL_ERROR;
658 }
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);
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 }
715 if (Tcl_PkgProvide(interp, "Rrd", VERSION) != TCL_OK) {
716 return TCL_ERROR;
717 }
719 return TCL_OK;
720 }
722 int Tclrrd_Init(
723 Tcl_Interp *interp)
724 {
725 return init(interp, 0);
726 }
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 }