001: /*
002: * InterpSlaveCmd.java --
003: *
004: * Implements the built-in "interp" Tcl command.
005: *
006: * Copyright (c) 2000 Christian Krone.
007: *
008: * See the file "license.terms" for information on usage and
009: * redistribution of this file, and for a DISCLAIMER OF ALL
010: * WARRANTIES.
011: *
012: * RCS: @(#) $Id: InterpSlaveCmd.java,v 1.7 2006/08/03 23:24:03 mdejong Exp $
013: *
014: */
015:
016: package tcl.lang;
017:
018: import java.util.*;
019:
020: /**
021: * This class implements the slave interpreter commands, which are created
022: * in response to the built-in "interp create" command in Tcl.
023: *
024: * It is also used by the "interp" command to record and find information
025: * about slave interpreters. Maps from a command name in the master to
026: * information about a slave interpreter, e.g. what aliases are defined
027: * in it.
028: */
029:
030: class InterpSlaveCmd implements CommandWithDispose, AssocData {
031:
032: static final private String options[] = { "alias", "aliases",
033: "eval", "expose", "hide", "hidden", "issafe",
034: "invokehidden", "marktrusted" };
035: static final private int OPT_ALIAS = 0;
036: static final private int OPT_ALIASES = 1;
037: static final private int OPT_EVAL = 2;
038: static final private int OPT_EXPOSE = 3;
039: static final private int OPT_HIDE = 4;
040: static final private int OPT_HIDDEN = 5;
041: static final private int OPT_ISSAFE = 6;
042: static final private int OPT_INVOKEHIDDEN = 7;
043: static final private int OPT_MARKTRUSTED = 8;
044:
045: static final private String hiddenOptions[] = { "-global", "--" };
046: static final private int OPT_HIDDEN_GLOBAL = 0;
047: static final private int OPT_HIDDEN_LAST = 1;
048:
049: // Master interpreter for this slave.
050:
051: Interp masterInterp;
052:
053: // Hash entry in masters slave table for this slave interpreter.
054: // Used to find this record, and used when deleting the slave interpreter
055: // to delete it from the master's table.
056:
057: String path;
058:
059: // The slave interpreter.
060:
061: Interp slaveInterp;
062:
063: // Interpreter object command.
064:
065: WrappedCommand interpCmd;
066:
067: // Debug child interp alloc/dispose calls
068:
069: static final boolean debug = false;
070:
071: /**
072: *----------------------------------------------------------------------
073: *
074: * SlaveObjCmd -> cmdProc
075: *
076: * Command to manipulate an interpreter, e.g. to send commands to it
077: * to be evaluated. One such command exists for each slave interpreter.
078: *
079: * Results:
080: * A standard Tcl result.
081: *
082: * Side effects:
083: * See user documentation for details.
084: *
085: *----------------------------------------------------------------------
086: */
087:
088: public void cmdProc(Interp interp, // Current interpreter.
089: TclObject[] objv) // Argument list.
090: throws TclException // A standard Tcl exception.
091: {
092: if (objv.length < 2) {
093: throw new TclNumArgsException(interp, 1, objv,
094: "cmd ?arg ...?");
095: }
096: int cmd = TclIndex.get(interp, objv[1], options, "option", 0);
097:
098: switch (cmd) {
099: case OPT_ALIAS:
100: if (objv.length == 3) {
101: InterpAliasCmd.describe(interp, slaveInterp, objv[2]);
102: return;
103: }
104: if ("".equals(objv[3].toString())) {
105: if (objv.length == 4) {
106: InterpAliasCmd.delete(interp, slaveInterp, objv[2]);
107: return;
108: }
109: } else {
110: InterpAliasCmd.create(interp, slaveInterp, interp,
111: objv[2], objv[3], 4, objv);
112: return;
113: }
114: throw new TclNumArgsException(interp, 2, objv,
115: "aliasName ?targetName? ?args..?");
116: case OPT_ALIASES:
117: InterpAliasCmd.list(interp, slaveInterp);
118: break;
119: case OPT_EVAL:
120: if (objv.length < 3) {
121: throw new TclNumArgsException(interp, 2, objv,
122: "arg ?arg ...?");
123: }
124: eval(interp, slaveInterp, 2, objv);
125: break;
126: case OPT_EXPOSE:
127: if (objv.length < 3 || objv.length > 4) {
128: throw new TclNumArgsException(interp, 2, objv,
129: "hiddenCmdName ?cmdName?");
130: }
131: expose(interp, slaveInterp, 2, objv);
132: break;
133: case OPT_HIDE:
134: if (objv.length < 3 || objv.length > 4) {
135: throw new TclNumArgsException(interp, 2, objv,
136: "cmdName ?hiddenCmdName?");
137: }
138: hide(interp, slaveInterp, 2, objv);
139: break;
140: case OPT_HIDDEN:
141: if (objv.length != 2) {
142: throw new TclNumArgsException(interp, 2, objv, null);
143: }
144: InterpSlaveCmd.hidden(interp, slaveInterp);
145: break;
146: case OPT_ISSAFE:
147: interp.setResult(slaveInterp.isSafe);
148: break;
149: case OPT_INVOKEHIDDEN:
150: boolean global = false;
151: int i;
152: for (i = 2; i < objv.length; i++) {
153: if (objv[i].toString().charAt(0) != '-') {
154: break;
155: }
156: int index = TclIndex.get(interp, objv[i],
157: hiddenOptions, "option", 0);
158: if (index == OPT_HIDDEN_GLOBAL) {
159: global = true;
160: } else {
161: i++;
162: break;
163: }
164: }
165: if (objv.length - i < 1) {
166: throw new TclNumArgsException(interp, 2, objv,
167: "?-global? ?--? cmd ?arg ..?");
168: }
169: InterpSlaveCmd.invokeHidden(interp, slaveInterp, global, i,
170: objv);
171: break;
172: case OPT_MARKTRUSTED:
173: if (objv.length != 2) {
174: throw new TclNumArgsException(interp, 2, objv, null);
175: }
176: markTrusted(interp, slaveInterp);
177: break;
178: }
179: }
180:
181: /**
182: *----------------------------------------------------------------------
183: *
184: * disposeCmd --
185: *
186: * Invoked when an object command for a slave interpreter is deleted;
187: * cleans up all state associated with the slave interpreter and destroys
188: * the slave interpreter.
189: *
190: * Results:
191: * None.
192: *
193: * Side effects:
194: * Cleans up all state associated with the slave interpreter and
195: * destroys the slave interpreter.
196: *
197: *----------------------------------------------------------------------
198: */
199:
200: public void disposeCmd() {
201: // Unlink the slave from its master interpreter.
202:
203: masterInterp.slaveTable.remove(path);
204:
205: // Set to null so that when the InterpInfo is cleaned up in the slave
206: // it does not try to delete the command causing all sorts of grief.
207: // See SlaveRecordDeleteProc().
208:
209: interpCmd = null;
210:
211: if (slaveInterp != null) {
212: if (debug) {
213: System.out.println("slaveInterp with path \"" + path
214: + "\" disposed of " + slaveInterp);
215: }
216: slaveInterp.dispose();
217: } else {
218: if (debug) {
219: System.out.println("slaveInterp with path \"" + path
220: + "\" is null");
221: }
222: }
223: }
224:
225: /*
226: *---------------------------------------------------------------------------
227: *
228: * disposeAssocData --
229: *
230: * Invoked when an interpreter is being deleted. It releases all
231: * storage used by the master/slave/safe interpreter facilities.
232: *
233: * Results:
234: * None.
235: *
236: * Side effects:
237: * Cleans up storage. Sets the interpInfoPtr field of the interp
238: * to NULL.
239: *
240: *---------------------------------------------------------------------------
241: */
242:
243: public void disposeAssocData(Interp interp) // Current interpreter.
244: {
245: // There shouldn't be any commands left.
246:
247: if (!interp.slaveTable.isEmpty()) {
248: throw new TclRuntimeError(
249: "disposeAssocData: commands still exist");
250: }
251: interp.slaveTable = null;
252:
253: // Tell any interps that have aliases to this interp that they should
254: // delete those aliases. If the other interp was already dead, it
255: // would have removed the target record already.
256:
257: for (Iterator iter = interp.targetTable.entrySet().iterator(); iter
258: .hasNext();) {
259: Map.Entry entry = (Map.Entry) iter.next();
260: WrappedCommand slaveCmd = (WrappedCommand) entry.getKey();
261: Interp slaveInterp = (Interp) entry.getValue();
262: slaveInterp.deleteCommandFromToken(slaveCmd);
263: }
264:
265: interp.targetTable = null;
266:
267: if (interp.interpChanTable != null) {
268: // Tear down channel table, be careful to pull the first element from
269: // the front of the table until empty since the table is modified
270: // by unregisterChannel().
271: Channel channel;
272: while ((channel = (Channel) Namespace
273: .FirstHashEntry(interp.interpChanTable)) != null) {
274: TclIO.unregisterChannel(interp, channel);
275: }
276: }
277:
278: if (interp.slave.interpCmd != null) {
279: // Tcl_DeleteInterp() was called on this interpreter, rather
280: // "interp delete" or the equivalent deletion of the command in the
281: // master. First ensure that the cleanup callback doesn't try to
282: // delete the interp again.
283:
284: interp.slave.slaveInterp = null;
285: interp.slave.masterInterp
286: .deleteCommandFromToken(interp.slave.interpCmd);
287: }
288:
289: // There shouldn't be any aliases left.
290:
291: if (!interp.aliasTable.isEmpty()) {
292: throw new TclRuntimeError(
293: "disposeAssocData: aliases still exist");
294: }
295: interp.aliasTable = null;
296: }
297:
298: /**
299: *----------------------------------------------------------------------
300: *
301: * slaveCreate -> create
302: *
303: * Helper function to do the actual work of creating a slave interp
304: * and new object command. Also optionally makes the new slave
305: * interpreter "safe".
306: *
307: * Results:
308: * Returns the new Tcl_Interp * if successful or NULL if not. If failed,
309: * the result of the invoking interpreter contains an error message.
310: *
311: * Side effects:
312: * Creates a new slave interpreter and a new object command.
313: *
314: *----------------------------------------------------------------------
315: */
316:
317: static Interp create(Interp interp, // Interp. to start search from.
318: TclObject path, // Path (name) of slave to create.
319: boolean safe) // Should we make it "safe"?
320: throws TclException // A standard Tcl exception.
321: {
322: Interp masterInterp;
323: String pathString;
324:
325: TclObject[] objv = TclList.getElements(interp, path);
326:
327: if (objv.length < 2) {
328: masterInterp = interp;
329: pathString = path.toString();
330: } else {
331: TclObject obj = TclList.newInstance();
332:
333: TclList.insert(interp, obj, 0, objv, 0, objv.length - 2);
334: masterInterp = InterpCmd.getInterp(interp, obj);
335: pathString = objv[objv.length - 1].toString();
336: }
337: if (!safe) {
338: safe = masterInterp.isSafe;
339: }
340:
341: if (masterInterp.slaveTable.containsKey(pathString)) {
342: throw new TclException(interp, "interpreter named \""
343: + pathString + "\" already exists, cannot create");
344: }
345:
346: Interp slaveInterp = new Interp();
347: InterpSlaveCmd slave = new InterpSlaveCmd();
348:
349: slaveInterp.slave = slave;
350: slaveInterp.setAssocData("InterpSlaveCmd", slave);
351:
352: slave.masterInterp = masterInterp;
353: slave.path = pathString;
354: slave.slaveInterp = slaveInterp;
355:
356: masterInterp.createCommand(pathString, slaveInterp.slave);
357: slaveInterp.slave.interpCmd = Namespace.findCommand(
358: masterInterp, pathString, null, 0);
359:
360: masterInterp.slaveTable.put(pathString, slaveInterp.slave);
361:
362: if (debug) {
363: System.out.println("slaveTable entry for \"" + pathString
364: + "\" created (" + slaveInterp + ")");
365: }
366:
367: slaveInterp.setVar("tcl_interactive", "0", TCL.GLOBAL_ONLY);
368:
369: // Inherit the recursion limit.
370:
371: //slaveInterp.maxNestingDepth = masterInterp.maxNestingDepth;
372:
373: if (safe) {
374: try {
375: makeSafe(slaveInterp);
376: } catch (TclException e) {
377: e.printStackTrace();
378: }
379: } else {
380: //Tcl_Init(slaveInterp);
381: }
382:
383: return slaveInterp;
384: }
385:
386: /**
387: *----------------------------------------------------------------------
388: *
389: * slaveEval -> eval
390: *
391: * Helper function to evaluate a command in a slave interpreter.
392: *
393: * Results:
394: * None.
395: *
396: * Side effects:
397: * Whatever the command does.
398: *
399: *----------------------------------------------------------------------
400: */
401:
402: static void eval(Interp interp, // Interp for error return.
403: Interp slaveInterp, // The slave interpreter in which command
404: // will be evaluated.
405: int objIx, // Number of arguments to ignored.
406: TclObject objv[]) // Argument objects.
407: throws TclException {
408: int result;
409:
410: slaveInterp.preserve();
411:
412: try {
413: slaveInterp.allowExceptions();
414:
415: try {
416: if (objIx + 1 == objv.length) {
417: slaveInterp.eval(objv[objIx], 0);
418: } else {
419: TclObject obj = TclList.newInstance();
420: for (int ix = objIx; ix < objv.length; ix++) {
421: TclList.append(interp, obj, objv[ix]);
422: }
423: obj.preserve();
424: slaveInterp.eval(obj, 0);
425: obj.release();
426: }
427: result = slaveInterp.returnCode;
428: } catch (TclException e) {
429: result = e.getCompletionCode();
430: }
431:
432: interp.transferResult(slaveInterp, result);
433: } finally {
434: slaveInterp.release();
435: }
436: }
437:
438: /*
439: *----------------------------------------------------------------------
440: *
441: * SlaveExpose -> expose
442: *
443: * Helper function to expose a command in a slave interpreter.
444: *
445: * Results:
446: * A standard Tcl result.
447: *
448: * Side effects:
449: * After this call scripts in the slave will be able to invoke
450: * the newly exposed command.
451: *
452: *----------------------------------------------------------------------
453: */
454:
455: static void expose(Interp interp, // Interp for error return.
456: Interp slaveInterp, // Interp in which command will be exposed.
457: int objIx, // Number of arguments to ignored.
458: TclObject objv[]) // Argument objects.
459: throws TclException {
460: if (interp.isSafe) {
461: throw new TclException(interp, "permission denied: "
462: + "safe interpreter cannot expose commands");
463: }
464:
465: int nameIdx = objv.length - objIx == 1 ? objIx : objIx + 1;
466:
467: try {
468: slaveInterp.exposeCommand(objv[objIx].toString(),
469: objv[nameIdx].toString());
470: } catch (TclException e) {
471: interp.transferResult(slaveInterp, e.getCompletionCode());
472: throw e;
473: }
474: }
475:
476: /*
477: *----------------------------------------------------------------------
478: *
479: * SlaveHide -> hide
480: *
481: * Helper function to hide a command in a slave interpreter.
482: *
483: * Results:
484: * A standard Tcl result.
485: *
486: * Side effects:
487: * After this call scripts in the slave will no longer be able
488: * to invoke the named command.
489: *
490: *----------------------------------------------------------------------
491: */
492:
493: static void hide(Interp interp, // Interp for error return.
494: Interp slaveInterp, // Interp in which command will be hidden.
495: int objIx, // Number of arguments to ignored.
496: TclObject objv[]) // Argument objects.
497: throws TclException {
498: if (interp.isSafe) {
499: throw new TclException(interp, "permission denied: "
500: + "safe interpreter cannot hide commands");
501: }
502:
503: int nameIdx = objv.length - objIx == 1 ? objIx : objIx + 1;
504:
505: try {
506: slaveInterp.hideCommand(objv[objIx].toString(),
507: objv[nameIdx].toString());
508: } catch (TclException e) {
509: interp.transferResult(slaveInterp, e.getCompletionCode());
510: throw e;
511: }
512: }
513:
514: /*
515: *----------------------------------------------------------------------
516: *
517: * SlaveHidden -> hidden
518: *
519: * Helper function to compute list of hidden commands in a slave
520: * interpreter.
521: *
522: * Results:
523: * A standard Tcl result.
524: *
525: * Side effects:
526: * None.
527: *
528: *----------------------------------------------------------------------
529: */
530:
531: static void hidden(Interp interp, // Interp for data return.
532: Interp slaveInterp) // Interp whose hidden commands to query.
533: throws TclException {
534: if (slaveInterp.hiddenCmdTable == null) {
535: return;
536: }
537:
538: TclObject result = TclList.newInstance();
539: for (Iterator iter = slaveInterp.hiddenCmdTable.entrySet()
540: .iterator(); iter.hasNext();) {
541: Map.Entry entry = (Map.Entry) iter.next();
542: String cmdName = (String) entry.getKey();
543: TclList.append(interp, result, TclString
544: .newInstance(cmdName));
545: }
546: interp.setResult(result);
547: }
548:
549: /*
550: *----------------------------------------------------------------------
551: *
552: * SlaveInvokeHidden -> invokeHidden
553: *
554: * Helper function to invoke a hidden command in a slave interpreter.
555: *
556: * Results:
557: * A standard Tcl result.
558: *
559: * Side effects:
560: * Whatever the hidden command does.
561: *
562: *----------------------------------------------------------------------
563: */
564:
565: static void invokeHidden(Interp interp, // Interp for error return.
566: Interp slaveInterp, // The slave interpreter in which command
567: // will be invoked.
568: boolean global, // True to invoke in global namespace.
569: int objIx, // Number of arguments to ignored.
570: TclObject[] objv) // Argument objects.
571: throws TclException {
572: int result;
573:
574: if (interp.isSafe) {
575: throw new TclException(interp, "not allowed to "
576: + "invoke hidden commands from safe interpreter");
577: }
578:
579: slaveInterp.preserve();
580:
581: try {
582: slaveInterp.allowExceptions();
583:
584: TclObject localObjv[] = new TclObject[objv.length - objIx];
585: for (int i = 0; i < objv.length - objIx; i++) {
586: localObjv[i] = objv[i + objIx];
587: }
588:
589: try {
590: if (global) {
591: slaveInterp.invokeGlobal(localObjv,
592: Interp.INVOKE_HIDDEN);
593: } else {
594: slaveInterp.invoke(localObjv, Interp.INVOKE_HIDDEN);
595: }
596: result = slaveInterp.returnCode;
597: } catch (TclException e) {
598: result = e.getCompletionCode();
599: }
600:
601: interp.transferResult(slaveInterp, result);
602: } finally {
603: slaveInterp.release();
604: }
605: }
606:
607: /*
608: *----------------------------------------------------------------------
609: *
610: * SlaveMarkTrusted -> markTrusted
611: *
612: * Helper function to mark a slave interpreter as trusted (unsafe).
613: *
614: * Results:
615: * A standard Tcl result.
616: *
617: * Side effects:
618: * After this call the hard-wired security checks in the core no
619: * longer prevent the slave from performing certain operations.
620: *
621: *----------------------------------------------------------------------
622: */
623:
624: static void markTrusted(Interp interp, // Interp for error return.
625: Interp slaveInterp) // The slave interpreter which will be
626: // marked trusted.
627: throws TclException {
628: if (interp.isSafe) {
629: throw new TclException(interp, "permission denied: "
630: + "safe interpreter cannot mark trusted");
631: }
632: slaveInterp.isSafe = false;
633: }
634:
635: /*
636: *----------------------------------------------------------------------
637: *
638: * makeSafe --
639: *
640: * Makes its argument interpreter contain only functionality that is
641: * defined to be part of Safe Tcl. Unsafe commands are hidden, the
642: * env array is unset, and the standard channels are removed.
643: *
644: * Results:
645: * None.
646: *
647: * Side effects:
648: * Hides commands in its argument interpreter, and removes settings
649: * and channels.
650: *
651: *----------------------------------------------------------------------
652: */
653:
654: private static void makeSafe(Interp interp) // Interpreter to be made safe.
655: throws TclException {
656: Channel chan; // Channel to remove from safe interpreter.
657:
658: interp.hideUnsafeCommands();
659:
660: interp.isSafe = true;
661:
662: // Unsetting variables : (which should not have been set
663: // in the first place, but...)
664:
665: // No env array in a safe slave.
666:
667: try {
668: interp.unsetVar("env", TCL.GLOBAL_ONLY);
669: } catch (TclException e) {
670: }
671: ;
672:
673: // Remove unsafe parts of tcl_platform
674:
675: try {
676: interp.unsetVar("tcl_platform", "os", TCL.GLOBAL_ONLY);
677: } catch (TclException e) {
678: }
679: ;
680: try {
681: interp.unsetVar("tcl_platform", "osVersion",
682: TCL.GLOBAL_ONLY);
683: } catch (TclException e) {
684: }
685: ;
686: try {
687: interp.unsetVar("tcl_platform", "machine", TCL.GLOBAL_ONLY);
688: } catch (TclException e) {
689: }
690: ;
691: try {
692: interp.unsetVar("tcl_platform", "user", TCL.GLOBAL_ONLY);
693: } catch (TclException e) {
694: }
695: ;
696:
697: // Unset path informations variables
698: // (the only one remaining is [info nameofexecutable])
699:
700: try {
701: interp.unsetVar("tclDefaultLibrary", TCL.GLOBAL_ONLY);
702: } catch (TclException e) {
703: }
704: ;
705: try {
706: interp.unsetVar("tcl_library", TCL.GLOBAL_ONLY);
707: } catch (TclException e) {
708: }
709: ;
710: try {
711: interp.unsetVar("tcl_pkgPath", TCL.GLOBAL_ONLY);
712: } catch (TclException e) {
713: }
714: ;
715:
716: // Remove the standard channels from the interpreter; safe interpreters
717: // do not ordinarily have access to stdin, stdout and stderr.
718: //
719: // NOTE: These channels are not added to the interpreter by the
720: // Tcl_CreateInterp call, but may be added later, by another I/O
721: // operation. We want to ensure that the interpreter does not have
722: // these channels even if it is being made safe after being used for
723: // some time..
724:
725: chan = TclIO.getStdChannel(StdChannel.STDIN);
726: if (chan != null) {
727: TclIO.unregisterChannel(interp, chan);
728: }
729: chan = TclIO.getStdChannel(StdChannel.STDOUT);
730: if (chan != null) {
731: TclIO.unregisterChannel(interp, chan);
732: }
733: chan = TclIO.getStdChannel(StdChannel.STDERR);
734: if (chan != null) {
735: TclIO.unregisterChannel(interp, chan);
736: }
737: }
738:
739: /*
740: *----------------------------------------------------------------------
741: *
742: * Tcl_GetSlave, GetInterp -> getSlave
743: *
744: * Finds a slave interpreter by its path name.
745: *
746: * Results:
747: * Returns a Interp for the named interpreter or
748: * raises a TclException if the interpreter is not found.
749: *
750: * Side effects:
751: * None.
752: *
753: *----------------------------------------------------------------------
754: */
755:
756: static Interp getSlave(Interp interp, // Interpreter to start search from.
757: TclObject slavePath) // Path of slave to find.
758: throws TclException {
759: Interp searchInterp;
760: final int len = TclList.getLength(interp, slavePath);
761:
762: searchInterp = interp;
763: for (int i = 0; i < len; i++) {
764: TclObject slavePathIndex = TclList.index(interp, slavePath,
765: i);
766: InterpSlaveCmd isc = (InterpSlaveCmd) searchInterp.slaveTable
767: .get(slavePathIndex.toString());
768: if (isc == null) {
769: searchInterp = null;
770: break;
771: }
772: searchInterp = isc.slaveInterp;
773: if (searchInterp == null) {
774: break;
775: }
776: }
777: if (searchInterp == null) {
778: throw new TclException(interp,
779: "could not find interpreter \"" + slavePath + "\"");
780: }
781: return searchInterp;
782: }
783:
784: } // end InterpSlaveCmd
|