/* * tclrrd.c -- A TCL interpreter extension to access the RRD library. * * 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. * * $Id: tclrrd.c 1044 2007-05-02 05:08:21Z oetiker $ */ #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 Tclrrd_Init(Tcl_Interp *interp); extern int Tclrrd_SafeInit(Tcl_Interp *interp); /* * 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(int argc, CONST84 char *argv[]) { char **argv2; int i; argv2 = calloc(argc, sizeof(char *)); for (i = 0; i < argc; i++) { argv2[i] = strdup(argv[i]); } return argv2; } static void getopt_cleanup(int argc, char **argv2) { int i; for (i = 0; i < argc; 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 clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]) { 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); 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()) { Tcl_AppendResult(interp, "RRD Error: ", rrd_get_error(), (char *) NULL); rrd_clear_error(); return TCL_ERROR; } return TCL_OK; } /* Thread-safe version */ static int Rrd_Dump(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]) { 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. */ if (rrd_test_error()) { Tcl_AppendResult(interp, "RRD Error: ", rrd_get_error(), (char *) NULL); rrd_clear_error(); return TCL_ERROR; } return TCL_OK; } /* Thread-safe version */ static int Rrd_Last(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]) { time_t t; 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: ", rrd_get_error(), (char *) NULL); rrd_clear_error(); return TCL_ERROR; } Tcl_SetIntObj(Tcl_GetObjResult(interp), t); return TCL_OK; } /* Thread-safe version */ static int Rrd_Update(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]) { int argv_i; char **argv2, *template = NULL; argv2 = getopt_init(argc, argv); 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: ", rrd_get_error(), (char *) NULL); rrd_clear_error(); return TCL_ERROR; } 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; } 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; for(i = 0; calcpr[i]; i++){ printf("%s\n", calcpr[i]); free(calcpr[i]); } #endif free(calcpr); } } if (rrd_test_error()) { Tcl_AppendResult(interp, "RRD Error: ", rrd_get_error(), (char *) NULL); rrd_clear_error(); return TCL_ERROR; } return TCL_OK; } static int 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(argc, argv2); if (rrd_test_error()) { Tcl_AppendResult(interp, "RRD Error: ", rrd_get_error(), (char *) NULL); rrd_clear_error(); return TCL_ERROR; } return TCL_OK; } static int 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(argc, argv2); if (rrd_test_error()) { Tcl_AppendResult(interp, "RRD Error: ", rrd_get_error(), (char *) NULL); rrd_clear_error(); return TCL_ERROR; } return TCL_OK; } static int 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(argc, argv2); if (rrd_test_error()) { Tcl_AppendResult(interp, "RRD Error: ", rrd_get_error(), (char *) NULL); rrd_clear_error(); return TCL_ERROR; } return TCL_OK; } /* * The following structure defines the commands in the Rrd extension. */ 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, 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 } }; 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++) { /* * Check if the command already exists and return an error * to ensure we detect name clashes while loading the Rrd * extension. */ if (Tcl_GetCommandInfo(interp, cmdInfoPtr->name, &info)) { Tcl_AppendResult(interp, "command \"", cmdInfoPtr->name, "\" already exists", (char *) NULL); return TCL_ERROR; } 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); } if (Tcl_PkgProvide(interp, "Rrd", VERSION) != TCL_OK) { return TCL_ERROR; } 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); }