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