/* * InterpSlaveCmd.java -- * * Implements the built-in "interp" Tcl command. * * Copyright (c) 2000 Christian Krone. * * See the file "license.terms" for information on usage and * redistribution of this file, and for a DISCLAIMER OF ALL * WARRANTIES. * * Included in SQLite3 port to C# for use in testharness only; 2008 Noah B Hart * * RCS @(#) $Id: InterpSlaveCmd.java,v 1.1 2000/08/20 06:08:43 mo Exp $ * */ using System; using System.Collections; namespace tcl.lang { /// This class implements the slave interpreter commands, which are created /// in response to the built-in "interp create" command in Tcl. /// /// It is also used by the "interp" command to record and find information /// about slave interpreters. Maps from a command name in the master to /// information about a slave interpreter, e.g. what aliases are defined /// in it. /// class InterpSlaveCmd : CommandWithDispose, AssocData { private static readonly string[] options = new string[] { "alias", "aliases", "eval", "expose", "hide", "hidden", "issafe", "invokehidden", "marktrusted" }; private const int OPT_ALIAS = 0; private const int OPT_ALIASES = 1; private const int OPT_EVAL = 2; private const int OPT_EXPOSE = 3; private const int OPT_HIDE = 4; private const int OPT_HIDDEN = 5; private const int OPT_ISSAFE = 6; private const int OPT_INVOKEHIDDEN = 7; private const int OPT_MARKTRUSTED = 8; private static readonly string[] hiddenOptions = new string[] { "-global", "--" }; private const int OPT_HIDDEN_GLOBAL = 0; private const int OPT_HIDDEN_LAST = 1; // Master interpreter for this slave. internal Interp masterInterp; // Hash entry in masters slave table for this slave interpreter. // Used to find this record, and used when deleting the slave interpreter // to delete it from the master's table. internal string path; // The slave interpreter. internal Interp slaveInterp; // Interpreter object command. internal WrappedCommand interpCmd; public TCL.CompletionCode cmdProc( Interp interp, TclObject[] objv ) { if ( objv.Length < 2 ) { throw new TclNumArgsException( interp, 1, objv, "cmd ?arg ...?" ); } int cmd = TclIndex.get( interp, objv[1], options, "option", 0 ); switch ( cmd ) { case OPT_ALIAS: if ( objv.Length == 3 ) { InterpAliasCmd.describe( interp, slaveInterp, objv[2] ); return TCL.CompletionCode.RETURN; } if ( "".Equals( objv[3].ToString() ) ) { if ( objv.Length == 4 ) { InterpAliasCmd.delete( interp, slaveInterp, objv[2] ); return TCL.CompletionCode.RETURN; } } else { InterpAliasCmd.create( interp, slaveInterp, interp, objv[2], objv[3], 4, objv ); return TCL.CompletionCode.RETURN; } throw new TclNumArgsException( interp, 2, objv, "aliasName ?targetName? ?args..?" ); case OPT_ALIASES: InterpAliasCmd.list( interp, slaveInterp ); break; case OPT_EVAL: if ( objv.Length < 3 ) { throw new TclNumArgsException( interp, 2, objv, "arg ?arg ...?" ); } eval( interp, slaveInterp, 2, objv ); break; case OPT_EXPOSE: if ( objv.Length < 3 || objv.Length > 4 ) { throw new TclNumArgsException( interp, 2, objv, "hiddenCmdName ?cmdName?" ); } expose( interp, slaveInterp, 2, objv ); break; case OPT_HIDE: if ( objv.Length < 3 || objv.Length > 4 ) { throw new TclNumArgsException( interp, 2, objv, "cmdName ?hiddenCmdName?" ); } hide( interp, slaveInterp, 2, objv ); break; case OPT_HIDDEN: if ( objv.Length != 2 ) { throw new TclNumArgsException( interp, 2, objv, null ); } InterpSlaveCmd.hidden( interp, slaveInterp ); break; case OPT_ISSAFE: interp.setResult( slaveInterp.isSafe ); break; case OPT_INVOKEHIDDEN: bool global = false; int i; for ( i = 2; i < objv.Length; i++ ) { if ( objv[i].ToString()[0] != '-' ) { break; } int index = TclIndex.get( interp, objv[i], hiddenOptions, "option", 0 ); if ( index == OPT_HIDDEN_GLOBAL ) { global = true; } else { i++; break; } } if ( objv.Length - i < 1 ) { throw new TclNumArgsException( interp, 2, objv, "?-global? ?--? cmd ?arg ..?" ); } InterpSlaveCmd.invokeHidden( interp, slaveInterp, global, i, objv ); break; case OPT_MARKTRUSTED: if ( objv.Length != 2 ) { throw new TclNumArgsException( interp, 2, objv, null ); } markTrusted( interp, slaveInterp ); break; } return TCL.CompletionCode.RETURN; } /// ---------------------------------------------------------------------- /// /// disposeCmd -- /// /// Invoked when an object command for a slave interpreter is deleted; /// cleans up all state associated with the slave interpreter and destroys /// the slave interpreter. /// /// Results: /// None. /// /// Side effects: /// Cleans up all state associated with the slave interpreter and /// destroys the slave interpreter. /// /// ---------------------------------------------------------------------- /// public void disposeCmd() { // Unlink the slave from its master interpreter. SupportClass.HashtableRemove( masterInterp.slaveTable, path ); // Set to null so that when the InterpInfo is cleaned up in the slave // it does not try to delete the command causing all sorts of grief. // See SlaveRecordDeleteProc(). interpCmd = null; if ( slaveInterp != null ) { slaveInterp.dispose(); } } public void disposeAssocData( Interp interp ) // Current interpreter. { // There shouldn't be any commands left. if ( !( interp.slaveTable.Count == 0 ) ) { System.Console.Error.WriteLine( "InterpInfoDeleteProc: still exist commands" ); } interp.slaveTable = null; // Tell any interps that have aliases to this interp that they should // delete those aliases. If the other interp was already dead, it // would have removed the target record already. // TODO ATK foreach ( WrappedCommand slaveCmd in new ArrayList( interp.targetTable.Keys ) ) { Interp slaveInterp = (Interp)interp.targetTable[slaveCmd]; slaveInterp.deleteCommandFromToken( slaveCmd ); } interp.targetTable = null; if ( interp.interpChanTable != null ) { foreach ( Channel channel in new ArrayList( interp.interpChanTable.Values ) ) { TclIO.unregisterChannel( interp, channel ); } } if ( interp.slave.interpCmd != null ) { // Tcl_DeleteInterp() was called on this interpreter, rather // "interp delete" or the equivalent deletion of the command in the // master. First ensure that the cleanup callback doesn't try to // delete the interp again. interp.slave.slaveInterp = null; interp.slave.masterInterp.deleteCommandFromToken( interp.slave.interpCmd ); } // There shouldn't be any aliases left. if ( !( interp.aliasTable.Count == 0 ) ) { System.Console.Error.WriteLine( "InterpInfoDeleteProc: still exist aliases" ); } interp.aliasTable = null; } internal static Interp create( Interp interp, TclObject path, bool safe ) { Interp masterInterp; string pathString; TclObject[] objv = TclList.getElements( interp, path ); if ( objv.Length < 2 ) { masterInterp = interp; pathString = path.ToString(); } else { TclObject obj = TclList.newInstance(); TclList.insert( interp, obj, 0, objv, 0, objv.Length - 2 ); masterInterp = InterpCmd.getInterp( interp, obj ); pathString = objv[objv.Length - 1].ToString(); } if ( !safe ) { safe = masterInterp.isSafe; } if ( masterInterp.slaveTable.ContainsKey( pathString ) ) { throw new TclException( interp, "interpreter named \"" + pathString + "\" already exists, cannot create" ); } Interp slaveInterp = new Interp(); InterpSlaveCmd slave = new InterpSlaveCmd(); slaveInterp.slave = slave; slaveInterp.setAssocData( "InterpSlaveCmd", slave ); slave.masterInterp = masterInterp; slave.path = pathString; slave.slaveInterp = slaveInterp; masterInterp.createCommand( pathString, slaveInterp.slave ); slaveInterp.slave.interpCmd = NamespaceCmd.findCommand( masterInterp, pathString, null, 0 ); SupportClass.PutElement( masterInterp.slaveTable, pathString, slaveInterp.slave ); slaveInterp.setVar( "tcl_interactive", "0", TCL.VarFlag.GLOBAL_ONLY ); // Inherit the recursion limit. slaveInterp.maxNestingDepth = masterInterp.maxNestingDepth; if ( safe ) { try { makeSafe( slaveInterp ); } catch ( TclException e ) { SupportClass.WriteStackTrace( e, Console.Error ); } } else { //Tcl_Init(slaveInterp); } return slaveInterp; } internal static void eval( Interp interp, Interp slaveInterp, int objIx, TclObject[] objv ) { TCL.CompletionCode result; slaveInterp.preserve(); slaveInterp.allowExceptions(); try { if ( objIx + 1 == objv.Length ) { slaveInterp.eval( objv[objIx], 0 ); } else { TclObject obj = TclList.newInstance(); for ( int ix = objIx; ix < objv.Length; ix++ ) { TclList.append( interp, obj, objv[ix] ); } obj.preserve(); slaveInterp.eval( obj, 0 ); obj.release(); } result = slaveInterp.returnCode; } catch ( TclException e ) { result = e.getCompletionCode(); } slaveInterp.release(); interp.transferResult( slaveInterp, result ); } internal static void expose( Interp interp, Interp slaveInterp, int objIx, TclObject[] objv ) { if ( interp.isSafe ) { throw new TclException( interp, "permission denied: " + "safe interpreter cannot expose commands" ); } int nameIdx = objv.Length - objIx == 1 ? objIx : objIx + 1; try { slaveInterp.exposeCommand( objv[objIx].ToString(), objv[nameIdx].ToString() ); } catch ( TclException e ) { interp.transferResult( slaveInterp, e.getCompletionCode() ); throw; } } internal static void hide( Interp interp, Interp slaveInterp, int objIx, TclObject[] objv ) { if ( interp.isSafe ) { throw new TclException( interp, "permission denied: " + "safe interpreter cannot hide commands" ); } int nameIdx = objv.Length - objIx == 1 ? objIx : objIx + 1; try { slaveInterp.hideCommand( objv[objIx].ToString(), objv[nameIdx].ToString() ); } catch ( TclException e ) { interp.transferResult( slaveInterp, e.getCompletionCode() ); throw; } } internal static void hidden( Interp interp, Interp slaveInterp ) { if ( slaveInterp.hiddenCmdTable == null ) { return; } TclObject result = TclList.newInstance(); interp.setResult( result ); IEnumerator hiddenCmds = slaveInterp.hiddenCmdTable.Keys.GetEnumerator(); while ( hiddenCmds.MoveNext() ) { string cmdName = (string)hiddenCmds.Current; TclList.append( interp, result, TclString.newInstance( cmdName ) ); } } internal static void invokeHidden( Interp interp, Interp slaveInterp, bool global, int objIx, TclObject[] objv ) { TCL.CompletionCode result; if ( interp.isSafe ) { throw new TclException( interp, "not allowed to " + "invoke hidden commands from safe interpreter" ); } slaveInterp.preserve(); slaveInterp.allowExceptions(); TclObject[] localObjv = new TclObject[objv.Length - objIx]; for ( int i = 0; i < objv.Length - objIx; i++ ) { localObjv[i] = objv[i + objIx]; } try { if ( global ) { slaveInterp.invokeGlobal( localObjv, Interp.INVOKE_HIDDEN ); } else { slaveInterp.invoke( localObjv, Interp.INVOKE_HIDDEN ); } result = slaveInterp.returnCode; } catch ( TclException e ) { result = e.getCompletionCode(); } slaveInterp.release(); interp.transferResult( slaveInterp, result ); } internal static void markTrusted( Interp interp, Interp slaveInterp ) { if ( interp.isSafe ) { throw new TclException( interp, "permission denied: " + "safe interpreter cannot mark trusted" ); } slaveInterp.isSafe = false; } private static void makeSafe( Interp interp ) { Channel chan; // Channel to remove from safe interpreter. interp.hideUnsafeCommands(); interp.isSafe = true; // Unsetting variables : (which should not have been set // in the first place, but...) // No env array in a safe slave. try { interp.unsetVar( "env", TCL.VarFlag.GLOBAL_ONLY ); } catch ( TclException e ) { } // Remove unsafe parts of tcl_platform try { interp.unsetVar( "tcl_platform", "os", TCL.VarFlag.GLOBAL_ONLY ); } catch ( TclException e ) { } try { interp.unsetVar( "tcl_platform", "osVersion", TCL.VarFlag.GLOBAL_ONLY ); } catch ( TclException e ) { } try { interp.unsetVar( "tcl_platform", "machine", TCL.VarFlag.GLOBAL_ONLY ); } catch ( TclException e ) { } try { interp.unsetVar( "tcl_platform", "user", TCL.VarFlag.GLOBAL_ONLY ); } catch ( TclException e ) { } // Unset path informations variables // (the only one remaining is [info nameofexecutable]) try { interp.unsetVar( "tclDefaultLibrary", TCL.VarFlag.GLOBAL_ONLY ); } catch ( TclException e ) { } try { interp.unsetVar( "tcl_library", TCL.VarFlag.GLOBAL_ONLY ); } catch ( TclException e ) { } try { interp.unsetVar( "tcl_pkgPath", TCL.VarFlag.GLOBAL_ONLY ); } catch ( TclException e ) { } // Remove the standard channels from the interpreter; safe interpreters // do not ordinarily have access to stdin, stdout and stderr. // // NOTE: These channels are not added to the interpreter by the // Tcl_CreateInterp call, but may be added later, by another I/O // operation. We want to ensure that the interpreter does not have // these channels even if it is being made safe after being used for // some time.. chan = TclIO.getStdChannel( StdChannel.STDIN ); if ( chan != null ) { TclIO.unregisterChannel( interp, chan ); } chan = TclIO.getStdChannel( StdChannel.STDOUT ); if ( chan != null ) { TclIO.unregisterChannel( interp, chan ); } chan = TclIO.getStdChannel( StdChannel.STDERR ); if ( chan != null ) { TclIO.unregisterChannel( interp, chan ); } } } // end InterpSlaveCmd }