X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=bindings%2Ftcl%2Ftclrrd.c;h=11d25dfae86d276fe680725aa4863d83a9d10f15;hb=5fc7ff89bdbced9c593c566fea9840a269935dcd;hp=92616de259cf85036f4d04906bcc59e71f93ba51;hpb=8cb9661bb4befb4bcd4fc8a23ed0e2c3821126e7;p=rrdtool.git diff --git a/bindings/tcl/tclrrd.c b/bindings/tcl/tclrrd.c index 92616de..11d25df 100644 --- a/bindings/tcl/tclrrd.c +++ b/bindings/tcl/tclrrd.c @@ -3,6 +3,8 @@ * * Copyright (c) 1999,2000 Frank Strauss, Technical University of Braunschweig. * + * Thread-safe code copyright (c) 2005 Oleg Derevenetz, CenterTelecom Voronezh ISP. + * * See the file "COPYING" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * @@ -11,24 +13,31 @@ +#include +#include #include +#include #include -#include -#include +#include "../../src/rrd_tool.h" +#include "../../src/rrd_format.h" +/* support pre-8.4 tcl */ +#ifndef CONST84 +# define CONST84 +#endif -extern int __getopt_initialized; +extern int Tclrrd_Init(Tcl_Interp *interp); +extern int Tclrrd_SafeInit(Tcl_Interp *interp); /* - * some rrd_XXX() functions might modify the argv strings passed to it. + * some rrd_XXX() and new thread-safe versions of Rrd_XXX() + * functions might modify the argv strings passed to it. * Hence, we need to do some preparation before * calling the rrd library functions. */ -static char ** getopt_init(argc, argv) - int argc; - char *argv[]; +static char ** getopt_init(int argc, CONST84 char *argv[]) { char **argv2; int i; @@ -40,31 +49,128 @@ static char ** getopt_init(argc, argv) return argv2; } -static void getopt_cleanup(argc, argv2) - int argc; - char *argv2[]; +static void getopt_cleanup(int argc, char **argv2) { int i; for (i = 0; i < argc; i++) { - free(argv2[i]); + if (argv2[i] != NULL) { + free(argv2[i]); + } } free(argv2); } +static void getopt_free_element(argv2, argn) + char *argv2[]; + int argn; +{ + if (argv2[argn] != NULL) { + free(argv2[argn]); + argv2[argn] = NULL; + } +} + +static void getopt_squieeze(argc, argv2) + int *argc; + char *argv2[]; +{ + int i, null_i = 0, argc_tmp = *argc; + + for (i = 0; i < argc_tmp; i++) { + if (argv2[i] == NULL) { + (*argc)--; + } else { + argv2[null_i++] = argv2[i]; + } + } +} + +/* Thread-safe version */ static int -Rrd_Create(clientData, interp, argc, argv) - ClientData clientData; - Tcl_Interp *interp; - int argc; - char *argv[]; +Rrd_Create(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]) { - char **argv2; + int argv_i; + char **argv2; + char *parsetime_error = NULL; + time_t last_up = time(NULL) - 10; + long int long_tmp; + unsigned long int pdp_step = 300; + struct rrd_time_value last_up_tv; argv2 = getopt_init(argc, argv); - rrd_create(argc, argv2); + + for (argv_i = 1; argv_i < argc; argv_i++) { + if (!strcmp(argv2[argv_i], "--start") || !strcmp(argv2[argv_i], "-b")) { + if (argv_i++>=argc) { + Tcl_AppendResult(interp, "RRD Error: option '", + argv2[argv_i - 1], "' needs an argument", (char *) NULL); + getopt_cleanup(argc, argv2); + return TCL_ERROR; + } + if ((parsetime_error = parsetime(argv2[argv_i], &last_up_tv))) { + Tcl_AppendResult(interp, "RRD Error: invalid time format: '", + argv2[argv_i], "'", (char *) NULL); + getopt_cleanup(argc, argv2); + return TCL_ERROR; + } + if (last_up_tv.type == RELATIVE_TO_END_TIME || + last_up_tv.type == RELATIVE_TO_START_TIME) { + Tcl_AppendResult(interp, "RRD Error: specifying time relative to the 'start' ", + "or 'end' makes no sense here", (char *) NULL); + getopt_cleanup(argc, argv2); + return TCL_ERROR; + } + last_up = mktime(&last_up_tv.tm) + last_up_tv.offset; + if (last_up < 3600*24*365*10) { + Tcl_AppendResult(interp, "RRD Error: the first entry to the RRD should be after 1980", + (char *) NULL); + getopt_cleanup(argc, argv2); + return TCL_ERROR; + } + getopt_free_element(argv2, argv_i - 1); + getopt_free_element(argv2, argv_i); + } else if (!strcmp(argv2[argv_i], "--step") || !strcmp(argv2[argv_i], "-s")) { + if (argv_i++>=argc) { + Tcl_AppendResult(interp, "RRD Error: option '", + argv2[argv_i - 1], "' needs an argument", (char *) NULL); + getopt_cleanup(argc, argv2); + return TCL_ERROR; + } + long_tmp = atol(argv2[argv_i]); + if (long_tmp < 1) { + Tcl_AppendResult(interp, "RRD Error: step size should be no less than one second", + (char *) NULL); + getopt_cleanup(argc, argv2); + return TCL_ERROR; + } + pdp_step = long_tmp; + getopt_free_element(argv2, argv_i - 1); + getopt_free_element(argv2, argv_i); + } else if (!strcmp(argv2[argv_i], "--")) { + getopt_free_element(argv2, argv_i); + break; + } else if (argv2[argv_i][0]=='-') { + Tcl_AppendResult(interp, "RRD Error: unknown option '", + argv2[argv_i], "'", (char *) NULL); + getopt_cleanup(argc, argv2); + return TCL_ERROR; + } + } + + getopt_squieeze(&argc, argv2); + + if (argc < 2) { + Tcl_AppendResult(interp, "RRD Error: needs rrd filename", + (char *) NULL); + getopt_cleanup(argc, argv2); + return TCL_ERROR; + } + + rrd_create_r(argv2[1], pdp_step, last_up, argc - 2, argv2 + 2); + getopt_cleanup(argc, argv2); if (rrd_test_error()) { @@ -79,18 +185,17 @@ Rrd_Create(clientData, interp, argc, argv) +/* Thread-safe version */ static int -Rrd_Dump(clientData, interp, argc, argv) - ClientData clientData; - Tcl_Interp *interp; - int argc; - char *argv[]; +Rrd_Dump(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]) { - char **argv2; - - argv2 = getopt_init(argc, argv); - rrd_dump(argc, argv2); - getopt_cleanup(argv, argv2); + if (argc < 2) { + Tcl_AppendResult(interp, "RRD Error: needs rrd filename", + (char *) NULL); + return TCL_ERROR; + } + + rrd_dump_r(argv[1], NULL); /* NOTE: rrd_dump() writes to stdout. No interaction with TCL. */ @@ -106,20 +211,19 @@ Rrd_Dump(clientData, interp, argc, argv) +/* Thread-safe version */ static int -Rrd_Last(clientData, interp, argc, argv) - ClientData clientData; - Tcl_Interp *interp; - int argc; - char *argv[]; +Rrd_Last(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]) { time_t t; - char **argv2; - argv2 = getopt_init(argc, argv); - t = rrd_last(argc, argv2); - getopt_cleanup(argv, argv2); + if (argc < 2) { + Tcl_AppendResult(interp, "RRD Error: needs rrd filename", + (char *) NULL); + return TCL_ERROR; + } + t = rrd_last_r(argv[1]); if (rrd_test_error()) { Tcl_AppendResult(interp, "RRD Error: ", @@ -135,18 +239,64 @@ Rrd_Last(clientData, interp, argc, argv) +/* Thread-safe version */ static int -Rrd_Update(clientData, interp, argc, argv) - ClientData clientData; - Tcl_Interp *interp; - int argc; - char *argv[]; +Rrd_Update(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]) { - char **argv2; + int argv_i; + char **argv2, *template = NULL; argv2 = getopt_init(argc, argv); - rrd_update(argc, argv2); - getopt_cleanup(argv, argv2); + + for (argv_i = 1; argv_i < argc; argv_i++) { + if (!strcmp(argv2[argv_i], "--template") || !strcmp(argv2[argv_i], "-t")) { + if (argv_i++>=argc) { + Tcl_AppendResult(interp, "RRD Error: option '", + argv2[argv_i - 1], "' needs an argument", (char *) NULL); + if (template != NULL) { + free(template); + } + getopt_cleanup(argc, argv2); + return TCL_ERROR; + } + if (template != NULL) { + free(template); + } + template = strdup(argv2[argv_i]); + getopt_free_element(argv2, argv_i - 1); + getopt_free_element(argv2, argv_i); + } else if (!strcmp(argv2[argv_i], "--")) { + getopt_free_element(argv2, argv_i); + break; + } else if (argv2[argv_i][0]=='-') { + Tcl_AppendResult(interp, "RRD Error: unknown option '", + argv2[argv_i], "'", (char *) NULL); + if (template != NULL) { + free(template); + } + getopt_cleanup(argc, argv2); + return TCL_ERROR; + } + } + + getopt_squieeze(&argc, argv2); + + if (argc < 2) { + Tcl_AppendResult(interp, "RRD Error: needs rrd filename", + (char *) NULL); + if (template != NULL) { + free(template); + } + getopt_cleanup(argc, argv2); + return TCL_ERROR; + } + + rrd_update_r(argv2[1], template, argc - 2, argv2 + 2); + + if (template != NULL) { + free(template); + } + getopt_cleanup(argc, argv2); if (rrd_test_error()) { Tcl_AppendResult(interp, "RRD Error: ", @@ -158,16 +308,49 @@ Rrd_Update(clientData, interp, argc, argv) return TCL_OK; } - +static int +Rrd_Lastupdate(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]) +{ + time_t last_update; + char **argv2; + char **ds_namv; + char **last_ds; + char s[30]; + Tcl_Obj *listPtr; + unsigned long ds_cnt, i; + + argv2 = getopt_init(argc, argv); + if (rrd_lastupdate(argc-1, argv2, &last_update, + &ds_cnt, &ds_namv, &last_ds) == 0) { + listPtr = Tcl_GetObjResult(interp); + for (i=0; iis< a Tcl fileID + */ + if (!(mode & TCL_WRITABLE)) { + Tcl_AppendResult(interp, "channel \"", argv[1], + "\" wasn't opened for writing", (char *) NULL); + return TCL_ERROR; + } + /* + * Must flush channel to make sure any buffered data is written before + * rrd_graph() writes to the stream + */ + if (Tcl_Flush(channel) != TCL_OK) { + Tcl_AppendResult(interp, "flush failed for \"", argv[1], "\": ", + strerror(Tcl_GetErrno()), (char *) NULL); + return TCL_ERROR; + } + if (Tcl_GetChannelHandle(channel, TCL_WRITABLE, &fd1) != TCL_OK) { + Tcl_AppendResult(interp, "cannot get file descriptor associated with \"", + argv[1], "\"", (char *) NULL); + return TCL_ERROR; + } + /* + * Must dup() file descriptor so we can fclose(stream), otherwise the fclose() + * would close Tcl's file descriptor + */ + if ((fd2 = dup((int)fd1)) == -1) { + Tcl_AppendResult(interp, "dup() failed for file descriptor associated with \"", + argv[1], "\": ", strerror(errno), (char *) NULL); + return TCL_ERROR; + } + /* + * rrd_graph() wants a FILE* + */ + if ((stream = fdopen(fd2, "wb")) == NULL) { + Tcl_AppendResult(interp, "fdopen() failed for file descriptor associated with \"", + argv[1], "\": ", strerror(errno), (char *) NULL); + close(fd2); /* plug potential file descriptor leak */ + return TCL_ERROR; + } - argv2 = getopt_init(argc, argv); - if (rrd_graph(argc, argv2, &calcpr, &xsize, &ysize, NULL, &ymin, &ymax) != -1 ) { - listPtr = Tcl_GetObjResult(interp); - Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewIntObj(xsize)); - Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewIntObj(ysize)); + save = argv[1]; + argv[1] = "-"; + argv2 = getopt_init(argc, argv); + argv[1] = save; + } else { + Tcl_ResetResult(interp); /* clear error from Tcl_GetChannel() */ + argv2 = getopt_init(argc, argv); + } + + rc = rrd_graph(argc, argv2, &calcpr, &xsize, &ysize, stream, &ymin, &ymax); + getopt_cleanup(argc, argv2); + + if (stream != NULL) + fclose(stream); /* plug potential malloc & file descriptor leak */ + + if (rc != -1) { + sprintf(dimensions, "%d %d", xsize, ysize); + Tcl_AppendResult(interp, dimensions, (char *) NULL); if (calcpr) { #if 0 int i; @@ -237,7 +478,6 @@ Rrd_Graph(clientData, interp, argc, argv) free(calcpr); } } - getopt_cleanup(argv, argv2); if (rrd_test_error()) { Tcl_AppendResult(interp, "RRD Error: ", @@ -252,17 +492,13 @@ Rrd_Graph(clientData, interp, argc, argv) static int -Rrd_Tune(clientData, interp, argc, argv) - ClientData clientData; - Tcl_Interp *interp; - int argc; - char *argv[]; +Rrd_Tune(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]) { char **argv2; argv2 = getopt_init(argc, argv); rrd_tune(argc, argv2); - getopt_cleanup(argv, argv2); + getopt_cleanup(argc, argv2); if (rrd_test_error()) { Tcl_AppendResult(interp, "RRD Error: ", @@ -277,17 +513,13 @@ Rrd_Tune(clientData, interp, argc, argv) static int -Rrd_Resize(clientData, interp, argc, argv) - ClientData clientData; - Tcl_Interp *interp; - int argc; - char *argv[]; +Rrd_Resize(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]) { char **argv2; argv2 = getopt_init(argc, argv); rrd_resize(argc, argv2); - getopt_cleanup(argv, argv2); + getopt_cleanup(argc, argv2); if (rrd_test_error()) { Tcl_AppendResult(interp, "RRD Error: ", @@ -302,17 +534,13 @@ Rrd_Resize(clientData, interp, argc, argv) static int -Rrd_Restore(clientData, interp, argc, argv) - ClientData clientData; - Tcl_Interp *interp; - int argc; - char *argv[]; +Rrd_Restore(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]) { char **argv2; argv2 = getopt_init(argc, argv); rrd_restore(argc, argv2); - getopt_cleanup(argv, argv2); + getopt_cleanup(argc, argv2); if (rrd_test_error()) { Tcl_AppendResult(interp, "RRD Error: ", @@ -333,35 +561,46 @@ Rrd_Restore(clientData, interp, argc, argv) typedef struct { char *name; /* Name of the command. */ Tcl_CmdProc *proc; /* Procedure for command. */ + int hide; /* Hide if safe interpreter */ } CmdInfo; static CmdInfo rrdCmds[] = { - { "Rrd::create", Rrd_Create }, - { "Rrd::dump", Rrd_Dump }, - { "Rrd::last", Rrd_Last }, - { "Rrd::update", Rrd_Update }, - { "Rrd::fetch", Rrd_Fetch }, - { "Rrd::graph", Rrd_Graph }, - { "Rrd::tune", Rrd_Tune }, - { "Rrd::resize", Rrd_Resize }, - { "Rrd::restore", Rrd_Restore }, - { (char *) NULL, (Tcl_CmdProc *) NULL } + { "Rrd::create", Rrd_Create, 1 }, /* Thread-safe version */ + { "Rrd::dump", Rrd_Dump, 0 }, /* Thread-safe version */ + { "Rrd::last", Rrd_Last, 0 }, /* Thread-safe version */ + { "Rrd::lastupdate", Rrd_Lastupdate, 0 }, /* Thread-safe version */ + { "Rrd::update", Rrd_Update, 1 }, /* Thread-safe version */ + { "Rrd::fetch", Rrd_Fetch, 0 }, + { "Rrd::graph", Rrd_Graph, 1 }, /* Due to RRD's API, a safe + interpreter cannot create + a graph since it writes to + a filename supplied by the + caller */ + { "Rrd::tune", Rrd_Tune, 1 }, + { "Rrd::resize", Rrd_Resize, 1 }, + { "Rrd::restore", Rrd_Restore, 1 }, + { (char *) NULL, (Tcl_CmdProc *) NULL, 0 } }; -int -Tclrrd_Init(interp, safe) - Tcl_Interp *interp; - int safe; +static int +init(Tcl_Interp *interp, int safe) { CmdInfo *cmdInfoPtr; Tcl_CmdInfo info; + if ( Tcl_InitStubs(interp,TCL_VERSION,0) == NULL ) + return TCL_ERROR; + if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 1) == NULL) { return TCL_ERROR; } + /* + * Why a global array? In keeping with the Rrd:: namespace, why + * not simply create a normal variable Rrd::version and set it? + */ Tcl_SetVar2(interp, "rrd", "version", VERSION, TCL_GLOBAL_ONLY); for (cmdInfoPtr = rrdCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) { @@ -375,7 +614,41 @@ Tclrrd_Init(interp, safe) "\" already exists", (char *) NULL); return TCL_ERROR; } - Tcl_CreateCommand(interp, cmdInfoPtr->name, cmdInfoPtr->proc, + if (safe && cmdInfoPtr->hide) { +#if 0 + /* + * Turns out the one cannot hide a command in a namespace + * due to a limitation of Tcl, one can only hide global + * commands. Thus, if we created the commands without + * the Rrd:: namespace in a safe interpreter, then the + * "unsafe" commands could be hidden -- which would allow + * an owning interpreter either un-hiding them or doing + * an "interp invokehidden". If the Rrd:: namespace is + * used, then it's still possible for the owning interpreter + * to fake out the missing commands: + * + * # Make all Rrd::* commands available in master interperter + * package require Rrd + * set safe [interp create -safe] + * # Make safe Rrd::* commands available in safe interperter + * interp invokehidden $safe -global load ./tclrrd1.2.11.so + * # Provide the safe interpreter with the missing commands + * $safe alias Rrd::update do_update $safe + * proc do_update {which_interp $args} { + * # Do some checking maybe... + * : + * return [eval Rrd::update $args] + * } + * + * Our solution for now is to just not create the "unsafe" + * commands in a safe interpreter. + */ + if (Tcl_HideCommand(interp, cmdInfoPtr->name, cmdInfoPtr->name) != TCL_OK) + return TCL_ERROR; +#endif + } + else + Tcl_CreateCommand(interp, cmdInfoPtr->name, cmdInfoPtr->proc, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); } @@ -385,3 +658,20 @@ Tclrrd_Init(interp, safe) return TCL_OK; } + +int +Tclrrd_Init(Tcl_Interp *interp) +{ + return init(interp, 0); +} + +/* + * See the comments above and note how few commands are considered "safe"... + * Using rrdtool in a safe interpreter has very limited functionality. It's + * tempting to just return TCL_ERROR and forget about it. + */ +int +Tclrrd_SafeInit(Tcl_Interp *interp) +{ + return init(interp, 1); +}