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 <rrd_tool.h>
22 #include <rrd_format.h>
24 extern int Tclrrd_Init(Tcl_Interp *interp);
25 extern int Tclrrd_SafeInit(Tcl_Interp *interp);
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;
39 argv2 = calloc(argc, sizeof(char *));
40 for (i = 0; i < argc; i++) {
41 argv2[i] = strdup(argv[i]);
42 }
43 return argv2;
44 }
46 static void getopt_cleanup(int argc, char **argv2)
47 {
48 int i;
50 for (i = 0; i < argc; i++) {
51 if (argv2[i] != NULL) {
52 free(argv2[i]);
53 }
54 }
55 free(argv2);
56 }
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 }
68 static void getopt_squieeze(argc, argv2)
69 int *argc;
70 char *argv2[];
71 {
72 int i, null_i = 0, argc_tmp = *argc;
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 }
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;
97 argv2 = getopt_init(argc, argv);
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 }
157 getopt_squieeze(&argc, argv2);
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 }
166 rrd_create_r(argv2[1], pdp_step, last_up, argc - 2, argv2 + 2);
168 getopt_cleanup(argc, argv2);
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 }
177 return TCL_OK;
178 }
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 }
192 rrd_dump_r(argv[1]);
194 /* NOTE: rrd_dump() writes to stdout. No interaction with TCL. */
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 }
203 return TCL_OK;
204 }
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;
214 if (argc < 2) {
215 Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
216 (char *) NULL);
217 return TCL_ERROR;
218 }
220 t = rrd_last_r(argv[1]);
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 }
229 Tcl_SetIntObj(Tcl_GetObjResult(interp), t);
231 return TCL_OK;
232 }
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;
243 argv2 = getopt_init(argc, argv);
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 }
276 getopt_squieeze(&argc, argv2);
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 }
288 rrd_update_r(argv2[1], template, argc - 2, argv2 + 2);
290 if (template != NULL) {
291 free(template);
292 }
293 getopt_cleanup(argc, argv2);
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 }
302 return TCL_OK;
303 }
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;
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);
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 }
343 return TCL_OK;
344 }
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;
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 }
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 }
417 rc = rrd_graph(argc, argv2, &calcpr, &xsize, &ysize, stream, &ymin, &ymax);
418 getopt_cleanup(argc, argv2);
420 if (stream != NULL)
421 fclose(stream); /* plug potential malloc & file descriptor leak */
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;
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 }
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 }
446 return TCL_OK;
447 }
451 static int
452 Rrd_Tune(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
453 {
454 char **argv2;
456 argv2 = getopt_init(argc, argv);
457 rrd_tune(argc, argv2);
458 getopt_cleanup(argc, argv2);
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 }
467 return TCL_OK;
468 }
472 static int
473 Rrd_Resize(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
474 {
475 char **argv2;
477 argv2 = getopt_init(argc, argv);
478 rrd_resize(argc, argv2);
479 getopt_cleanup(argc, argv2);
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 }
488 return TCL_OK;
489 }
493 static int
494 Rrd_Restore(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
495 {
496 char **argv2;
498 argv2 = getopt_init(argc, argv);
499 rrd_restore(argc, argv2);
500 getopt_cleanup(argc, argv2);
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 }
509 return TCL_OK;
510 }
514 /*
515 * The following structure defines the commands in the Rrd extension.
516 */
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;
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 };
543 static int
544 init(Tcl_Interp *interp, int safe)
545 {
546 CmdInfo *cmdInfoPtr;
547 Tcl_CmdInfo info;
549 if ( Tcl_InitStubs(interp,TCL_VERSION,0) == NULL )
550 return TCL_ERROR;
552 if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 1) == NULL) {
553 return TCL_ERROR;
554 }
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);
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 }
611 if (Tcl_PkgProvide(interp, "Rrd", VERSION) != TCL_OK) {
612 return TCL_ERROR;
613 }
615 return TCL_OK;
616 }
618 int
619 Tclrrd_Init(Tcl_Interp *interp)
620 {
621 return init(interp, 0);
622 }
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 }