diff options
| author | cashmere <cashmere@autistici.org> | 2026-09-04 09:05:24 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-09-04 09:05:24 +0200 |
| commit | 4f49b16079b27d7ce633619e4cddfdf2bffbc952 (patch) | |
| tree | c296c1e7f3c3e2f6859c6d3a1b1b208779b89679 /src/th_tcl.c | |
| download | relic-4f49b16079b27d7ce633619e4cddfdf2bffbc952.tar.gz | |
Diffstat (limited to 'src/th_tcl.c')
| -rw-r--r-- | src/th_tcl.c | 1287 |
1 files changed, 1287 insertions, 0 deletions
diff --git a/src/th_tcl.c b/src/th_tcl.c new file mode 100644 index 0000000..1999473 --- /dev/null +++ b/src/th_tcl.c @@ -0,0 +1,1287 @@ +/* +** Copyright (c) 2011 D. Richard Hipp +** Copyright (c) 2011 Joe Mistachkin +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code used to bridge the TH1 and Tcl scripting languages. +*/ +#include "config.h" + +#ifdef FOSSIL_ENABLE_TCL + +#include "sqlite3.h" +#include "th.h" +#include "tcl.h" + +#if TCL_MAJOR_VERSION<9 && !defined(Tcl_Size) +# define Tcl_Size int +#endif + +/* +** This macro is used to verify that the header version of Tcl meets some +** minimum requirement. +*/ +#define MINIMUM_TCL_VERSION(major, minor) \ + ((TCL_MAJOR_VERSION > (major)) || \ + ((TCL_MAJOR_VERSION == (major)) && (TCL_MINOR_VERSION >= (minor)))) + +/* +** These macros are designed to reduce the redundant code required to marshal +** arguments from TH1 to Tcl. +*/ +#define USE_ARGV_TO_OBJV() \ + int objc; \ + Tcl_Obj **objv; \ + int obji; + +#define COPY_ARGV_TO_OBJV() \ + objc = argc-1; \ + objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \ + for(obji=1; obji<argc; obji++){ \ + objv[obji-1] = Tcl_NewStringObj(argv[obji], TH1_LEN(argl[obji])); \ + Tcl_IncrRefCount(objv[obji-1]); \ + } + +#define FREE_ARGV_TO_OBJV() \ + for(obji=1; obji<argc; obji++){ \ + Tcl_DecrRefCount(objv[obji-1]); \ + objv[obji-1] = 0; \ + } \ + ckfree((char *)objv); \ + objv = 0; + +/* +** Fetch the Tcl interpreter from the specified void pointer, cast to a Tcl +** context. +*/ +#define GET_CTX_TCL_INTERP(ctx) \ + ((struct TclContext *)(ctx))->interp + +/* +** Fetch the (logically boolean) value from the specified void pointer that +** indicates whether or not we can/should use direct objProc calls. +*/ +#define GET_CTX_TCL_USEOBJPROC(ctx) \ + ((struct TclContext *)(ctx))->useObjProc + +/* +** This is the name of an environment variable that may refer to a Tcl library +** directory or file name. If this environment variable is set [to anything], +** its value will be used when searching for a Tcl library to load. +*/ +#ifndef TCL_PATH_ENV_VAR_NAME +# define TCL_PATH_ENV_VAR_NAME "FOSSIL_TCL_PATH" +#endif + +/* +** Define the Tcl shared library name, some exported function names, and some +** cross-platform macros for use with the Tcl stubs mechanism, when enabled. +*/ +#if defined(USE_TCL_STUBS) +# if defined(_WIN32) +# if !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +# endif +# if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0502) +# undef _WIN32_WINNT +# define _WIN32_WINNT 0x0502 /* SetDllDirectory, Windows XP SP2 */ +# endif +# include <windows.h> +# ifndef TCL_DIRECTORY_SEP +# define TCL_DIRECTORY_SEP '\\' +# endif +# ifndef TCL_LIBRARY_NAME +# define TCL_LIBRARY_NAME "tcl91.dll\0" +# endif +# ifndef TCL_MINOR_OFFSET +# define TCL_MINOR_OFFSET (4) +# endif +# ifndef dlopen +# define dlopen(a,b) (void *)LoadLibrary((a)) +# endif +# ifndef dlsym +# define dlsym(a,b) GetProcAddress((HANDLE)(a),(b)) +# endif +# ifndef dlclose +# define dlclose(a) FreeLibrary((HANDLE)(a)) +# endif +# else +# include <dlfcn.h> +# ifndef TCL_DIRECTORY_SEP +# define TCL_DIRECTORY_SEP '/' +# endif +# if defined(__CYGWIN__) && (TCL_MAJOR_VERSION > 8) +# ifndef TCL_LIBRARY_NAME +# define TCL_LIBRARY_NAME "cygtcl9.1.dll\0" +# endif +# ifndef TCL_MINOR_OFFSET +# define TCL_MINOR_OFFSET (8) +# endif +# elif defined(__APPLE__) +# ifndef TCL_LIBRARY_NAME +# define TCL_LIBRARY_NAME "libtcl9.1.dylib\0" +# endif +# ifndef TCL_MINOR_OFFSET +# define TCL_MINOR_OFFSET (8) +# endif +# elif defined(__FreeBSD__) +# ifndef TCL_LIBRARY_NAME +# define TCL_LIBRARY_NAME "libtcl91.so\0" +# endif +# ifndef TCL_MINOR_OFFSET +# define TCL_MINOR_OFFSET (7) +# endif +# else +# ifndef TCL_LIBRARY_NAME +# define TCL_LIBRARY_NAME "libtcl9.1.so\0" +# endif +# ifndef TCL_MINOR_OFFSET +# define TCL_MINOR_OFFSET (8) +# endif +# endif /* defined(__CYGWIN__) */ +# endif /* defined(_WIN32) */ +# ifndef TCL_FINDEXECUTABLE_NAME +# define TCL_FINDEXECUTABLE_NAME "_Tcl_FindExecutable\0" +# endif +# ifndef TCL_ZIPFSAPPHOOK_NAME +# define TCL_ZIPFSAPPHOOK_NAME "_TclZipfs_AppHook\0" +# endif +# ifndef TCL_CREATEINTERP_NAME +# define TCL_CREATEINTERP_NAME "_Tcl_CreateInterp\0" +# endif +# ifndef TCL_DELETEINTERP_NAME +# define TCL_DELETEINTERP_NAME "_Tcl_DeleteInterp\0" +# endif +# ifndef TCL_FINALIZE_NAME +# define TCL_FINALIZE_NAME "_Tcl_Finalize\0" +# endif +#endif /* defined(USE_TCL_STUBS) */ + +/* +** If this constant is defined to non-zero, the Win32 SetDllDirectory function +** will be used during the Tcl library loading process if the path environment +** variable for Tcl was set. +*/ +#ifndef TCL_USE_SET_DLL_DIRECTORY +# if defined(_WIN32) && defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0502) +# define TCL_USE_SET_DLL_DIRECTORY (1) +# else +# define TCL_USE_SET_DLL_DIRECTORY (0) +# endif +#endif /* TCL_USE_SET_DLL_DIRECTORY */ + +/* +** The function types for Tcl_FindExecutable and Tcl_CreateInterp are needed +** when the Tcl library is being loaded dynamically by a stubs-enabled +** application (i.e. the inverse of using a stubs-enabled package). These are +** the only Tcl API functions that MUST be called prior to being able to call +** Tcl_InitStubs (i.e. because it requires a Tcl interpreter). For complete +** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp +** and Tcl_Finalize function types are also required. +*/ +#if TCL_MAJOR_VERSION>=9 +typedef const char *(tcl_FindExecutableProc) (const char *); +typedef const char *(tcl_ZipfsAppHookProc) (int *, char ***); +#else +typedef void (tcl_FindExecutableProc) (const char *); +#endif +typedef Tcl_Interp *(tcl_CreateInterpProc) (void); +typedef void (tcl_DeleteInterpProc) (Tcl_Interp *); +typedef void (tcl_FinalizeProc) (void); + +/* +** The function types for the "hook" functions to be called before and after a +** TH1 command makes a call to evaluate a Tcl script. If the "pre" function +** returns anything but TH_OK, then evaluation of the Tcl script is skipped and +** that value is used as the return code. If the "post" function returns +** anything other than its rc argument, that will become the new return code +** for the command. +*/ +typedef int (tcl_NotifyProc) ( + void *pContext, /* The context for this notification. */ + Th_Interp *interp, /* The TH1 interpreter being used. */ + void *ctx, /* The original TH1 command context. */ + int argc, /* Number of arguments for the TH1 command. */ + const char **argv, /* Array of arguments for the TH1 command. */ + int *argl, /* Array of lengths for the TH1 command arguments. */ + int rc /* Recommended notification return value. */ +); + +/* +** Are we using our own private implementation of the Tcl stubs mechanism? If +** this is enabled, it prevents the user from having to link against the Tcl +** stubs library for the target platform, which may not be readily available. +*/ +#if defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS) +/* +** HACK: Using some preprocessor magic and a private static variable, redirect +** the Tcl API calls [found within this file] to the function pointers +** that will be contained in our private Tcl stubs table. This takes +** advantage of the fact that the Tcl headers always define the Tcl API +** functions in terms of the "tclStubsPtr" variable when the define +** USE_TCL_STUBS is present during compilation. +*/ +#define tclStubsPtr privateTclStubsPtr +static const TclStubs *tclStubsPtr = NULL; + +/* +** Create a Tcl interpreter structure that mirrors just enough fields to get +** it up and running successfully with our private implementation of the Tcl +** stubs mechanism. +*/ +struct PrivateTclInterp { + char *result; + Tcl_FreeProc *freeProc; + int errorLine; + const struct TclStubs *stubTable; +}; + +/* +** Fossil can now be compiled without linking to the actual Tcl stubs library. +** In that case, this function will be used to perform those steps that would +** normally be performed within the Tcl stubs library. +*/ +static int initTclStubs( + Th_Interp *interp, + Tcl_Interp *tclInterp +){ + tclStubsPtr = ((struct PrivateTclInterp *)tclInterp)->stubTable; + if( !tclStubsPtr || (tclStubsPtr->magic!=TCL_STUB_MAGIC) ){ + Th_ErrorMessage(interp, + "could not initialize Tcl stubs: incompatible mechanism", + (const char *)"", 0); + return TH_ERROR; + } + /* NOTE: At this point, the Tcl API functions should be available. */ + if( Tcl_PkgRequireEx(tclInterp, "Tcl", "8.5-", 0, (void *)&tclStubsPtr)==0 ){ + Th_ErrorMessage(interp, + "could not initialize Tcl stubs: incompatible version", + (const char *)"", 0); + return TH_ERROR; + } + return TH_OK; +} +#endif /* defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS) */ + +/* +** Is the loaded version of Tcl one where querying and/or calling the objProc +** for a command does not work for some reason? The following special cases +** are currently handled by this function: +** +** 1. All versions of Tcl 8.4 have a bug that causes a crash when calling into +** the Tcl_GetCommandFromObj function via stubs (i.e. the stubs table entry +** is NULL). +** +** 2. Various beta builds of Tcl 8.6, namely 1 and 2, have an NRE-specific bug +** in Tcl_EvalObjCmd (SF bug #3399564) that cause a panic when calling into +** the objProc directly. +** +** For both of the above cases, the Tcl_EvalObjv function must be used instead +** of the more direct route of querying and calling the objProc directly. +*/ +static int canUseObjProc(){ + int major = -1, minor = -1, patchLevel = -1, type = -1; + + Tcl_GetVersion(&major, &minor, &patchLevel, &type); + if( major<0 || minor<0 || patchLevel<0 || type<0 ){ + return 0; /* NOTE: Invalid version info, assume bad. */ + } + if( major==8 && minor==4 ){ + return 0; /* NOTE: Disabled on Tcl 8.4, missing public API. */ + } + if( major==8 && minor==6 && type==TCL_BETA_RELEASE && patchLevel<3 ){ + return 0; /* NOTE: Disabled on Tcl 8.6b1/b2, SF bug #3399564. */ + } + return 1; /* NOTE: For all other cases, assume good. */ +} + +/* +** Is the loaded version of Tcl one where TIP #285 (asynchronous script +** cancellation) is available? This should return non-zero only for Tcl +** 8.6 and higher. +*/ +static int canUseTip285(){ +#if MINIMUM_TCL_VERSION(8, 6) + int major = -1, minor = -1, patchLevel = -1, type = -1; + + Tcl_GetVersion(&major, &minor, &patchLevel, &type); + if( major<0 || minor<0 || patchLevel<0 || type<0 ){ + return 0; /* NOTE: Invalid version info, assume bad. */ + } + return (major>8 || (major==8 && minor>=6)); +#else + return 0; +#endif +} + +/* +** Creates and initializes a Tcl interpreter for use with the specified TH1 +** interpreter. Stores the created Tcl interpreter in the Tcl context supplied +** by the caller. This must be declared here because quite a few functions in +** this file need to use it before it can be defined. +*/ +static int createTclInterp(Th_Interp *interp, void *pContext); + +/* +** Returns the Tcl return code corresponding to the specified TH1 +** return code. +*/ +static int getTclReturnCode( + int rc /* The TH1 return code value to convert. */ +){ + switch( rc ){ + case /*0*/ TH_OK: return /*0*/ TCL_OK; + case /*1*/ TH_ERROR: return /*1*/ TCL_ERROR; + case /*2*/ TH_BREAK: return /*3*/ TCL_BREAK; + case /*3*/ TH_RETURN: return /*2*/ TCL_RETURN; + case /*4*/ TH_CONTINUE: return /*4*/ TCL_CONTINUE; + default /*?*/: return /*?*/ rc; + } +} + +/* +** Returns a name for a Tcl return code. +*/ +static const char *getTclReturnCodeName( + int rc, + int nullIfOk +){ + static char zRc[TCL_INTEGER_SPACE + 17]; /* "Tcl return code\0" */ + + switch( rc ){ + case TCL_OK: return nullIfOk ? 0 : "TCL_OK"; + case TCL_ERROR: return "TCL_ERROR"; + case TCL_RETURN: return "TCL_RETURN"; + case TCL_BREAK: return "TCL_BREAK"; + case TCL_CONTINUE: return "TCL_CONTINUE"; + default: { + sqlite3_snprintf(sizeof(zRc), zRc, "Tcl return code %d", rc); + } + } + return zRc; +} + +/* +** Returns the Tcl interpreter result as a string with the associated length. +** If the Tcl interpreter or the Tcl result are NULL, the length will be 0. +** If the length pointer is NULL, the length will not be stored. +*/ +static char *getTclResult( + Tcl_Interp *pInterp, + int *pN +){ + Tcl_Obj *resultPtr; + Tcl_Size n; + char *zRes; + + if( !pInterp ){ /* This should not happen. */ + if( pN ) *pN = 0; + return 0; + } + resultPtr = Tcl_GetObjResult(pInterp); + if( !resultPtr ){ /* This should not happen either? */ + if( pN ) *pN = 0; + return 0; + } + zRes = Tcl_GetStringFromObj(resultPtr, &n); + *pN = (int)n; + return zRes; +} + +/* +** Tcl context information used by TH1. This structure definition has been +** copied from and should be kept in sync with the one in "main.c". +*/ +struct TclContext { + int argc; /* Number of original arguments. */ + char **argv; /* Full copy of the original arguments. */ + void *hLibrary; /* The Tcl library module handle. */ + tcl_FindExecutableProc *xFindExecutable; /* Tcl_FindExecutable() pointer. */ +#if TCL_MAJOR_VERSION>=9 + tcl_ZipfsAppHookProc *xZipfsAppHook; /* TclZipfsAppHook() pointer. */ +#endif + tcl_CreateInterpProc *xCreateInterp; /* Tcl_CreateInterp() pointer. */ + tcl_DeleteInterpProc *xDeleteInterp; /* Tcl_DeleteInterp() pointer. */ + tcl_FinalizeProc *xFinalize; /* Tcl_Finalize() pointer. */ + Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */ + int useObjProc; /* Non-zero if an objProc can be called directly. */ + int useTip285; /* Non-zero if TIP #285 is available. */ + const char *setup; /* The optional Tcl setup script. */ +}; + +/* +** TH1 command: tclEval arg ?arg ...? +** +** Evaluates the Tcl script and returns its result verbatim. If a Tcl script +** error is generated, it will be transformed into a TH1 script error. The +** Tcl interpreter will be created automatically if it has not been already. +*/ +static int tclEval_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + Tcl_Interp *tclInterp; + Tcl_Obj *objPtr; + int rc = TH_OK; + int nResult; + const char *zResult; + + if( createTclInterp(interp, ctx)!=TH_OK ){ + return TH_ERROR; + } + if( argc<2 ){ + return Th_WrongNumArgs(interp, "tclEval arg ?arg ...?"); + } + tclInterp = GET_CTX_TCL_INTERP(ctx); + if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ + Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); + return TH_ERROR; + } + Tcl_Preserve((ClientData)tclInterp); + if( argc==2 ){ + objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); + Tcl_IncrRefCount(objPtr); + rc = Tcl_EvalObjEx(tclInterp, objPtr, 0); + Tcl_DecrRefCount(objPtr); objPtr = 0; + }else{ + USE_ARGV_TO_OBJV(); + COPY_ARGV_TO_OBJV(); + objPtr = Tcl_ConcatObj(objc, objv); + Tcl_IncrRefCount(objPtr); + rc = Tcl_EvalObjEx(tclInterp, objPtr, 0); + Tcl_DecrRefCount(objPtr); objPtr = 0; + FREE_ARGV_TO_OBJV(); + } + zResult = getTclResult(tclInterp, &nResult); + Th_SetResult(interp, zResult, nResult); + Tcl_Release((ClientData)tclInterp); + return rc; +} + +/* +** TH1 command: tclExpr arg ?arg ...? +** +** Evaluates the Tcl expression and returns its result verbatim. If a Tcl +** script error is generated, it will be transformed into a TH1 script error. +** The Tcl interpreter will be created automatically if it has not been +** already. +*/ +static int tclExpr_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + Tcl_Interp *tclInterp; + Tcl_Obj *objPtr; + Tcl_Obj *resultObjPtr; + int rc = TH_OK; + int nResult; + const char *zResult; + + if( createTclInterp(interp, ctx)!=TH_OK ){ + return TH_ERROR; + } + if( argc<2 ){ + return Th_WrongNumArgs(interp, "tclExpr arg ?arg ...?"); + } + tclInterp = GET_CTX_TCL_INTERP(ctx); + if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ + Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); + return TH_ERROR; + } + Tcl_Preserve((ClientData)tclInterp); + if( argc==2 ){ + objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); + Tcl_IncrRefCount(objPtr); + rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr); + Tcl_DecrRefCount(objPtr); objPtr = 0; + }else{ + USE_ARGV_TO_OBJV(); + COPY_ARGV_TO_OBJV(); + objPtr = Tcl_ConcatObj(objc, objv); + Tcl_IncrRefCount(objPtr); + rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr); + Tcl_DecrRefCount(objPtr); objPtr = 0; + FREE_ARGV_TO_OBJV(); + } + if( rc==TCL_OK ){ + Tcl_Size szResult = 0; + zResult = Tcl_GetStringFromObj(resultObjPtr, &szResult); + nResult = (int)szResult; + }else{ + zResult = getTclResult(tclInterp, &nResult); + } + Th_SetResult(interp, zResult, (int)nResult); + if( rc==TCL_OK ){ + Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0; + } + Tcl_Release((ClientData)tclInterp); + return rc; +} + +/* +** TH1 command: tclInvoke command ?arg ...? +** +** Invokes the Tcl command using the supplied arguments. No additional +** substitutions are performed on the arguments. The Tcl interpreter +** will be created automatically if it has not been already. +*/ +static int tclInvoke_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + Tcl_Interp *tclInterp; + int rc = TH_OK; + int nResult; + const char *zResult; + USE_ARGV_TO_OBJV(); + + if( createTclInterp(interp, ctx)!=TH_OK ){ + return TH_ERROR; + } + if( argc<2 ){ + return Th_WrongNumArgs(interp, "tclInvoke command ?arg ...?"); + } + tclInterp = GET_CTX_TCL_INTERP(ctx); + if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ + Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); + return TH_ERROR; + } + Tcl_Preserve((ClientData)tclInterp); +#if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV + if( GET_CTX_TCL_USEOBJPROC(ctx) ){ + Tcl_Command command; + Tcl_CmdInfo cmdInfo; + Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1])); + Tcl_IncrRefCount(objPtr); + command = Tcl_GetCommandFromObj(tclInterp, objPtr); + if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){ + Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]); + Tcl_DecrRefCount(objPtr); objPtr = 0; + Tcl_Release((ClientData)tclInterp); + return TH_ERROR; + } + if( !cmdInfo.objProc ){ + Th_ErrorMessage(interp, "cannot invoke Tcl command:", argv[1], argl[1]); + Tcl_DecrRefCount(objPtr); objPtr = 0; + Tcl_Release((ClientData)tclInterp); + return TH_ERROR; + } + Tcl_DecrRefCount(objPtr); objPtr = 0; + COPY_ARGV_TO_OBJV(); + Tcl_ResetResult(tclInterp); + rc = cmdInfo.objProc(cmdInfo.objClientData, tclInterp, objc, objv); + FREE_ARGV_TO_OBJV(); + }else +#endif /* !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV */ + { + COPY_ARGV_TO_OBJV(); + rc = Tcl_EvalObjv(tclInterp, objc, objv, 0); + FREE_ARGV_TO_OBJV(); + } + zResult = getTclResult(tclInterp, &nResult); + Th_SetResult(interp, zResult, nResult); + Tcl_Release((ClientData)tclInterp); + return rc; +} + +/* +** TH1 command: tclIsSafe +** +** Returns non-zero if the Tcl interpreter is "safe". The Tcl interpreter +** will be created automatically if it has not been already. +*/ +static int tclIsSafe_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + Tcl_Interp *tclInterp; + + if( createTclInterp(interp, ctx)!=TH_OK ){ + return TH_ERROR; + } + if( argc!=1 ){ + return Th_WrongNumArgs(interp, "tclIsSafe"); + } + tclInterp = GET_CTX_TCL_INTERP(ctx); + if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ + Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); + return TH_ERROR; + } + Th_SetResultInt(interp, Tcl_IsSafe(tclInterp)); + return TH_OK; +} + +/* +** TH1 command: tclMakeSafe +** +** Forces the Tcl interpreter into "safe" mode by removing all "unsafe" +** commands and variables. This operation cannot be undone. The Tcl +** interpreter will remain "safe" until the process terminates. +*/ +static int tclMakeSafe_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + static int registerChans = 1; + Tcl_Interp *tclInterp; + int rc = TH_OK; + + if( createTclInterp(interp, ctx)!=TH_OK ){ + return TH_ERROR; + } + if( argc!=1 ){ + return Th_WrongNumArgs(interp, "tclMakeSafe"); + } + tclInterp = GET_CTX_TCL_INTERP(ctx); + if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){ + Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0); + return TH_ERROR; + } + if( Tcl_IsSafe(tclInterp) ){ + Th_ErrorMessage(interp, + "Tcl interpreter is already 'safe'", (const char *)"", 0); + return TH_ERROR; + } + if( registerChans ){ + /* + ** HACK: Prevent the call to Tcl_MakeSafe() from actually closing the + ** standard channels instead of simply unregistering them from + ** the Tcl interpreter. This should only need to be done once + ** per thread (process?). + */ + registerChans = 0; + Tcl_RegisterChannel(NULL, Tcl_GetStdChannel(TCL_STDIN)); + Tcl_RegisterChannel(NULL, Tcl_GetStdChannel(TCL_STDOUT)); + Tcl_RegisterChannel(NULL, Tcl_GetStdChannel(TCL_STDERR)); + } + Tcl_Preserve((ClientData)tclInterp); +#if ((TCL_MAJOR_VERSION==8 && TCL_MINOR_VERSION>6) \ + || (TCL_MAJOR_VERSION>8)) + /* TCL 8.7+ removes Tcl_MakeSafe(): + ** https://core.tcl-lang.org/tcl/tktview?name=655300 + ** https://core.tcl-lang.org/tips/doc/trunk/tip/624.md + ** 8.7 has it in the headers but not in the libs. + */ +# define Tcl_MakeSafe(X) TCL_OK +#endif + if( Tcl_MakeSafe(tclInterp)!=TCL_OK ){ + int nResult; + const char *zResult = getTclResult(tclInterp, &nResult); + Th_ErrorMessage(interp, + "could not make Tcl interpreter 'safe':", zResult, nResult); + rc = TH_ERROR; + }else{ + Th_SetResult(interp, 0, 0); + } + Tcl_Release((ClientData)tclInterp); + return rc; +} + +/* +** Tcl command: th1Eval arg +** +** Evaluates the TH1 script and returns its result verbatim. If a TH1 script +** error is generated, it will be transformed into a Tcl script error. +*/ +static int Th1EvalObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[] +){ + Th_Interp *th1Interp; + int nArg; + Tcl_Size szArg; + const char *arg; + int rc; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "arg"); + return TCL_ERROR; + } + th1Interp = (Th_Interp *)clientData; + if( !th1Interp ){ + Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL); + return TCL_ERROR; + } + arg = Tcl_GetStringFromObj(objv[1], &szArg); + nArg = (int)szArg; + rc = Th_Eval(th1Interp, 0, arg, nArg); + arg = Th_GetResult(th1Interp, &nArg); + Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg))); + return getTclReturnCode(rc); +} + +/* +** Tcl command: th1Expr arg +** +** Evaluates the TH1 expression and returns its result verbatim. If a TH1 +** script error is generated, it will be transformed into a Tcl script error. +*/ +static int Th1ExprObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[] +){ + Th_Interp *th1Interp; + int nArg; + Tcl_Size szArg; + const char *arg; + int rc; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "arg"); + return TCL_ERROR; + } + th1Interp = (Th_Interp *)clientData; + if( !th1Interp ){ + Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL); + return TCL_ERROR; + } + arg = Tcl_GetStringFromObj(objv[1], &szArg); + rc = Th_Expr(th1Interp, arg, (int)szArg); + arg = Th_GetResult(th1Interp, &nArg); + Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg))); + return getTclReturnCode(rc); +} + +/* +** Array of Tcl integration commands. Used when adding or removing the Tcl +** integration commands from TH1. +*/ +static struct _Command { + const char *zName; + Th_CommandProc xProc; + void *pContext; +} aCommand[] = { + {"tclEval", tclEval_command, 0}, + {"tclExpr", tclExpr_command, 0}, + {"tclInvoke", tclInvoke_command, 0}, + {"tclIsSafe", tclIsSafe_command, 0}, + {"tclMakeSafe", tclMakeSafe_command, 0}, + {0, 0, 0} +}; + +/* +** Called if the Tcl interpreter is deleted. Removes the Tcl integration +** commands from the TH1 interpreter. +*/ +static void Th1DeleteProc( + ClientData clientData, + Tcl_Interp *interp +){ + int i; + Th_Interp *th1Interp = (Th_Interp *)clientData; + + if( !th1Interp ) return; + /* Remove the Tcl integration commands. */ + for(i=0; i<count(aCommand); i++){ + Th_RenameCommand(th1Interp, aCommand[i].zName, -1, NULL, 0); + } +} + +/* +** When Tcl stubs support is enabled, attempts to dynamically load the Tcl +** shared library and fetch the function pointers necessary to create an +** interpreter and initialize the stubs mechanism; otherwise, simply setup +** the function pointers provided by the caller with the statically linked +** functions. +*/ +char *fossil_getenv(const char *zName); /* file.h */ +int file_isdir(const char *zPath, int); /* file.h */ +#define ExtFILE 0 /* file.h */ +#define RepoFILE 1 /* file.h */ +#define SymFILE 2 /* file.h */ +char *file_dirname(const char *zPath); /* file.h */ +void fossil_free(void *p); /* util.h */ + +static int loadTcl( + Th_Interp *interp, + void **phLibrary, + tcl_FindExecutableProc **pxFindExecutable, +#if TCL_MAJOR_VERSION>=9 + tcl_ZipfsAppHookProc **pxZipfsAppHook, +#endif + tcl_CreateInterpProc **pxCreateInterp, + tcl_DeleteInterpProc **pxDeleteInterp, + tcl_FinalizeProc **pxFinalize +){ +#if defined(USE_TCL_STUBS) + const char *zEnvPath = fossil_getenv(TCL_PATH_ENV_VAR_NAME); + char aFileName[] = TCL_LIBRARY_NAME; +#endif /* defined(USE_TCL_STUBS) */ + + if( !phLibrary || !pxFindExecutable || !pxCreateInterp || + !pxDeleteInterp || !pxFinalize ){ + Th_ErrorMessage(interp, + "invalid Tcl loader argument(s)", (const char *)"", 0); + return TH_ERROR; + } +#if defined(USE_TCL_STUBS) +#if TCL_MAJOR_VERSION<9 +#if defined(_WIN32) || defined(__FreeBSD__) + aFileName[TCL_MINOR_OFFSET-1] = '0' + TCL_MAJOR_VERSION; +#else + aFileName[TCL_MINOR_OFFSET-2] = '0' + TCL_MAJOR_VERSION; +#endif + aFileName[TCL_MINOR_OFFSET] = '0' + TCL_MINOR_VERSION; +#endif + do { + char *zFileName; + void *hLibrary; + if( !zEnvPath ){ + zFileName = aFileName; /* NOTE: Assume present in PATH. */ + }else if( file_isdir(zEnvPath, ExtFILE)==1 ){ +#if TCL_USE_SET_DLL_DIRECTORY + SetDllDirectory(zEnvPath); /* NOTE: Maybe needed for "zlib1.dll". */ +#endif /* TCL_USE_SET_DLL_DIRECTORY */ + /* NOTE: The environment variable contains a directory name. */ + zFileName = sqlite3_mprintf("%s%c%s%c", zEnvPath, TCL_DIRECTORY_SEP, + aFileName, '\0'); + }else{ +#if TCL_USE_SET_DLL_DIRECTORY + char *zDirName = file_dirname(zEnvPath); + if( zDirName ){ + SetDllDirectory(zDirName); /* NOTE: Maybe needed for "zlib1.dll". */ + } +#endif /* TCL_USE_SET_DLL_DIRECTORY */ + /* NOTE: The environment variable might contain a file name. */ + zFileName = sqlite3_mprintf("%s%c", zEnvPath, '\0'); +#if TCL_USE_SET_DLL_DIRECTORY + if( zDirName ){ + fossil_free(zDirName); zDirName = 0; + } +#endif /* TCL_USE_SET_DLL_DIRECTORY */ + } + if( !zFileName ) break; + hLibrary = dlopen(zFileName, RTLD_NOW | RTLD_GLOBAL); + /* NOTE: If the file name was allocated, free it now. */ + if( zFileName!=aFileName ){ + sqlite3_free(zFileName); zFileName = 0; + } + if( hLibrary ){ + tcl_FindExecutableProc *xFindExecutable; +#if TCL_MAJOR_VERSION>=9 + tcl_ZipfsAppHookProc *xZipfsAppHook; +#endif + tcl_CreateInterpProc *xCreateInterp; + tcl_DeleteInterpProc *xDeleteInterp; + tcl_FinalizeProc *xFinalize; + const char *procName = TCL_FINDEXECUTABLE_NAME; + xFindExecutable = (tcl_FindExecutableProc *)dlsym(hLibrary, procName+1); + if( !xFindExecutable ){ + xFindExecutable = (tcl_FindExecutableProc *)dlsym(hLibrary, procName); + } + if( !xFindExecutable ){ + Th_ErrorMessage(interp, + "could not locate Tcl_FindExecutable", (const char *)"", 0); + dlclose(hLibrary); hLibrary = 0; + return TH_ERROR; + } +#if TCL_MAJOR_VERSION>=9 + procName = TCL_ZIPFSAPPHOOK_NAME; + xZipfsAppHook = (tcl_ZipfsAppHookProc *)dlsym(hLibrary, procName+1); + if( !xZipfsAppHook ){ + xZipfsAppHook = (tcl_ZipfsAppHookProc *)dlsym(hLibrary, procName); + } +#endif + procName = TCL_CREATEINTERP_NAME; + xCreateInterp = (tcl_CreateInterpProc *)dlsym(hLibrary, procName+1); + if( !xCreateInterp ){ + xCreateInterp = (tcl_CreateInterpProc *)dlsym(hLibrary, procName); + } + if( !xCreateInterp ){ + Th_ErrorMessage(interp, + "could not locate Tcl_CreateInterp", (const char *)"", 0); + dlclose(hLibrary); hLibrary = 0; + return TH_ERROR; + } + procName = TCL_DELETEINTERP_NAME; + xDeleteInterp = (tcl_DeleteInterpProc *)dlsym(hLibrary, procName+1); + if( !xDeleteInterp ){ + xDeleteInterp = (tcl_DeleteInterpProc *)dlsym(hLibrary, procName); + } + if( !xDeleteInterp ){ + Th_ErrorMessage(interp, + "could not locate Tcl_DeleteInterp", (const char *)"", 0); + dlclose(hLibrary); hLibrary = 0; + return TH_ERROR; + } + procName = TCL_FINALIZE_NAME; + xFinalize = (tcl_FinalizeProc *)dlsym(hLibrary, procName+1); + if( !xFinalize ){ + xFinalize = (tcl_FinalizeProc *)dlsym(hLibrary, procName); + } + if( !xFinalize ){ + Th_ErrorMessage(interp, + "could not locate Tcl_Finalize", (const char *)"", 0); + dlclose(hLibrary); hLibrary = 0; + return TH_ERROR; + } + *phLibrary = hLibrary; + *pxFindExecutable = xFindExecutable; +#if TCL_MAJOR_VERSION>=9 + *pxZipfsAppHook = xZipfsAppHook; +#endif + *pxCreateInterp = xCreateInterp; + *pxDeleteInterp = xDeleteInterp; + *pxFinalize = xFinalize; + return TH_OK; + } + } while( --aFileName[TCL_MINOR_OFFSET]!='6' && aFileName[TCL_MINOR_OFFSET]>='0'); /* Tcl 8.6+ */ + aFileName[TCL_MINOR_OFFSET] = 'x'; + Th_ErrorMessage(interp, + "could not load any supported Tcl shared library \"", + aFileName, -1); + return TH_ERROR; +#else + *phLibrary = 0; + *pxFindExecutable = Tcl_FindExecutable; +#if TCL_MAJOR_VERSION>=9 + *pxZipfsAppHook = (tcl_ZipfsAppHookProc *)(void *)TclZipfs_AppHook; +#endif + *pxCreateInterp = Tcl_CreateInterp; + *pxDeleteInterp = Tcl_DeleteInterp; + *pxFinalize = Tcl_Finalize; + return TH_OK; +#endif /* defined(USE_TCL_STUBS) */ +} + +/* +** Sets the "argv0", "argc", and "argv" script variables in the Tcl interpreter +** based on the supplied command line arguments. +*/ +static int setTclArguments( + Tcl_Interp *pInterp, + int argc, + char **argv +){ + Tcl_Obj *objPtr; + Tcl_Obj *resultObjPtr; + Tcl_Obj *listPtr; + int rc = TCL_OK; + + if( argc<=0 || !argv ){ + return TCL_OK; + } + objPtr = Tcl_NewStringObj(argv[0], -1); + Tcl_IncrRefCount(objPtr); + resultObjPtr = Tcl_SetVar2Ex(pInterp, "argv0", NULL, objPtr, + TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG); + Tcl_DecrRefCount(objPtr); objPtr = 0; + if( !resultObjPtr ){ + return TCL_ERROR; + } + objPtr = Tcl_NewWideIntObj(argc - 1); + Tcl_IncrRefCount(objPtr); + resultObjPtr = Tcl_SetVar2Ex(pInterp, "argc", NULL, objPtr, + TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG); + Tcl_DecrRefCount(objPtr); objPtr = 0; + if( !resultObjPtr ){ + return TCL_ERROR; + } + listPtr = Tcl_NewListObj(0, NULL); + Tcl_IncrRefCount(listPtr); + if( argc>1 ){ + while( --argc ){ + objPtr = Tcl_NewStringObj(*++argv, -1); + Tcl_IncrRefCount(objPtr); + rc = Tcl_ListObjAppendElement(pInterp, listPtr, objPtr); + Tcl_DecrRefCount(objPtr); objPtr = 0; + if( rc!=TCL_OK ){ + break; + } + } + } + if( rc==TCL_OK ){ + resultObjPtr = Tcl_SetVar2Ex(pInterp, "argv", NULL, listPtr, + TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG); + if( !resultObjPtr ){ + rc = TCL_ERROR; + } + } + Tcl_DecrRefCount(listPtr); listPtr = 0; + return rc; +} + +/* +** Evaluate a Tcl script, creating the Tcl interpreter if necessary. If the +** Tcl script succeeds, start a Tcl event loop until there are no more events +** remaining to process -OR- the script calls [exit]. If the bWait argument +** is zero, only process events that are already in the queue; otherwise, +** process events until the script terminates the Tcl event loop. +*/ +void fossil_print(const char *zFormat, ...); /* printf.h */ + +int evaluateTclWithEvents( + Th_Interp *interp, + void *pContext, + const char *zScript, + int nScript, + int bCancel, + int bWait, + int bVerbose +){ + struct TclContext *tclContext = (struct TclContext *)pContext; + Tcl_Interp *tclInterp; + int rc; + int flags = TCL_ALL_EVENTS; + int useTip285; + + if( createTclInterp(interp, pContext)!=TH_OK ){ + return TH_ERROR; + } + tclInterp = tclContext->interp; + useTip285 = bCancel ? tclContext->useTip285 : 0; + rc = Tcl_EvalEx(tclInterp, zScript, nScript, TCL_EVAL_GLOBAL); + if( rc!=TCL_OK ){ + if( bVerbose ){ + const char *zResult = getTclResult(tclInterp, 0); + fossil_print("%s: ", getTclReturnCodeName(rc, 0)); + fossil_print("%s\n", zResult); + } + return rc; + } + if( !bWait ) flags |= TCL_DONT_WAIT; + Tcl_Preserve((ClientData)tclInterp); + while( Tcl_DoOneEvent(flags) ){ + if( Tcl_InterpDeleted(tclInterp) ){ + break; + } +#if MINIMUM_TCL_VERSION(8, 6) + if( useTip285 && Tcl_Canceled(tclInterp, 0)!=TCL_OK ){ + break; + } +#endif + } + Tcl_Release((ClientData)tclInterp); + return rc; +} + +/* +** Creates and initializes a Tcl interpreter for use with the specified TH1 +** interpreter. Stores the created Tcl interpreter in the Tcl context supplied +** by the caller. +*/ +static int createTclInterp( + Th_Interp *interp, + void *pContext +){ + struct TclContext *tclContext = (struct TclContext *)pContext; + int argc; + char **argv; + char *argv0 = 0; + Tcl_Interp *tclInterp; + const char *setup; + + if( !tclContext ){ + Th_ErrorMessage(interp, + "invalid Tcl context", (const char *)"", 0); + return TH_ERROR; + } + if( tclContext->interp ){ + return TH_OK; + } + if( loadTcl(interp, &tclContext->hLibrary, &tclContext->xFindExecutable, + #if TCL_MAJOR_VERSION >= 9 + &tclContext->xZipfsAppHook, + #endif + &tclContext->xCreateInterp, &tclContext->xDeleteInterp, + &tclContext->xFinalize)!=TH_OK ){ + return TH_ERROR; + } + argc = tclContext->argc; + argv = tclContext->argv; + if( argc>0 && argv ){ + argv0 = argv[0]; + } +#if TCL_MAJOR_VERSION>=9 + if (tclContext->xZipfsAppHook) { + tclContext->xZipfsAppHook(&tclContext->argc, &tclContext->argv); + } +#endif + tclContext->xFindExecutable(argv0); + tclInterp = tclContext->xCreateInterp(); + if( !tclInterp ){ + Th_ErrorMessage(interp, + "could not create Tcl interpreter", (const char *)"", 0); + return TH_ERROR; + } +#if defined(USE_TCL_STUBS) +#if defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS) + if( initTclStubs(interp, tclInterp)!=TH_OK ){ + tclContext->xDeleteInterp(tclInterp); + tclInterp = 0; + return TH_ERROR; + } +#else + if( !Tcl_InitStubs(tclInterp, "8.5-", 0) ){ + Th_ErrorMessage(interp, + "could not initialize Tcl stubs", (const char *)"", 0); + tclContext->xDeleteInterp(tclInterp); + tclInterp = 0; + return TH_ERROR; + } +#endif /* defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS) */ +#endif /* defined(USE_TCL_STUBS) */ + if( Tcl_InterpDeleted(tclInterp) ){ + Th_ErrorMessage(interp, + "Tcl interpreter appears to be deleted", (const char *)"", 0); + Tcl_DeleteInterp(tclInterp); /* TODO: Redundant? */ + tclInterp = 0; + return TH_ERROR; + } + tclContext->interp = tclInterp; + if( Tcl_Init(tclInterp)!=TCL_OK ){ + Th_ErrorMessage(interp, + "Tcl initialization error:", + Tcl_GetString(Tcl_GetObjResult(tclInterp)), -1); + Tcl_DeleteInterp(tclInterp); + tclContext->interp = tclInterp = 0; + return TH_ERROR; + } + if( setTclArguments(tclInterp, argc, argv)!=TCL_OK ){ + Th_ErrorMessage(interp, + "Tcl error setting arguments:", + Tcl_GetString(Tcl_GetObjResult(tclInterp)), -1); + Tcl_DeleteInterp(tclInterp); + tclContext->interp = tclInterp = 0; + return TH_ERROR; + } + /* + ** Determine (and cache) if an objProc can be called directly for a Tcl + ** command invoked via the tclInvoke TH1 command. + */ + tclContext->useObjProc = canUseObjProc(); + /* + ** Determine (and cache) whether or not we can use TIP #285 (asynchronous + ** script cancellation). + */ + tclContext->useTip285 = canUseTip285(); + /* Add the TH1 integration commands to Tcl. */ + Tcl_CallWhenDeleted(tclInterp, Th1DeleteProc, interp); + Tcl_CreateObjCommand(tclInterp, "th1Eval", Th1EvalObjCmd, interp, NULL); + Tcl_CreateObjCommand(tclInterp, "th1Expr", Th1ExprObjCmd, interp, NULL); + /* If necessary, evaluate the custom Tcl setup script. */ + setup = tclContext->setup; + if( setup && Tcl_EvalEx(tclInterp, setup, -1, 0)!=TCL_OK ){ + Th_ErrorMessage(interp, + "Tcl setup script error:", + Tcl_GetString(Tcl_GetObjResult(tclInterp)), -1); + Tcl_DeleteInterp(tclInterp); + tclContext->interp = tclInterp = 0; + return TH_ERROR; + } + return TH_OK; +} + +/* +** Finalizes and unloads the previously loaded Tcl library, if applicable. +*/ +int unloadTcl( + Th_Interp *interp, + void *pContext +){ + struct TclContext *tclContext = (struct TclContext *)pContext; + Tcl_Interp *tclInterp; + tcl_FinalizeProc *xFinalize; +#if defined(USE_TCL_STUBS) + void *hLibrary; +#endif /* defined(USE_TCL_STUBS) */ + + if( !tclContext ){ + Th_ErrorMessage(interp, + "invalid Tcl context", (const char *)"", 0); + return TH_ERROR; + } + /* + ** Grab the Tcl_Finalize function pointer prior to deleting the Tcl + ** interpreter because the memory backing the Tcl stubs table will + ** be going away. + */ + xFinalize = tclContext->xFinalize; + /* + ** If the Tcl interpreter has been created, formally delete it now. + */ + tclInterp = tclContext->interp; + if( tclInterp ){ + Tcl_DeleteInterp(tclInterp); + tclContext->interp = tclInterp = 0; + } + /* + ** If the Tcl library is not finalized prior to unloading it, a deadlock + ** can occur in some circumstances (i.e. the [clock] thread is running). + */ + if( xFinalize ) xFinalize(); +#if defined(USE_TCL_STUBS) + /* + ** If Tcl is compiled on Windows using the latest MinGW, Fossil can crash + ** when exiting while a stubs-enabled Tcl is still loaded. This is due to + ** a bug in MinGW, see: + ** + ** http://comments.gmane.org/gmane.comp.gnu.mingw.user/41724 + ** + ** The workaround is to manually unload the loaded Tcl library prior to + ** exiting the process. + */ + hLibrary = tclContext->hLibrary; + if( hLibrary ){ + dlclose(hLibrary); + tclContext->hLibrary = hLibrary = 0; + } +#endif /* defined(USE_TCL_STUBS) */ + return TH_OK; +} + +/* +** Register the Tcl language commands with interpreter interp. +** Usually this is called soon after interpreter creation. +*/ +int th_register_tcl( + Th_Interp *interp, + void *pContext +){ + int i; + + /* Add the Tcl integration commands to TH1. */ + for(i=0; i<count(aCommand); i++){ + void *ctx; + if( !aCommand[i].zName || !aCommand[i].xProc ) continue; + ctx = aCommand[i].pContext; + /* Use Tcl interpreter for context? */ + if( !ctx ) ctx = pContext; + Th_CreateCommand(interp, aCommand[i].zName, aCommand[i].xProc, ctx, 0); + } + return TH_OK; +} + +#endif /* FOSSIL_ENABLE_TCL */ |
