# # Copyright (C) 2010 Alexandros Stergiakis <alsterg@gmail.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # 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. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # #//# # Execute an external system command on its own PTY. # # This is apropriate for executiong external commands that require a controling # terminal, such as vi, more, less, and telnet from busybox. 'ptyexec' does not # return until command has finished, but while it is executing the event loop # is served. The external application can be forced to terminate prematurely. #//# namespace eval pty { namespace export ptyexec ptykill ptyresize variable term 0 variable busy 0 # Execute an external program like "exec" does, but in its own PTY. # Some interactive programs like 'vi', 'more', 'less', 'telnet', 'sh' assume that they are # connected on a pty. If this is not the case (like when using "exec"), they malfunction. # # The external command takes control of Standard I/O of the session. # # Execution can be interrupted by the user with the break key sequence: Ctrl ^ + x/ # This is enforced in session.tcl by calling ptykill (see below). # # It supports the syntax of 'bgexec' and also: # -root : Execute without root priviledges. # # @param args Optional parameters, the program to execute, and arguments to the executable # @return Nothing if not error occurs, otherwise the error (and options). # @error proc ptyexec {args} { variable term 0 variable busy # XXX -root & -- if {[lindex $args 0] eq {-root}} { set args [lrange $args 1 end] } if {$busy} { error "there is an on-going ptyexec session for this thread; only one is allowed per session at any time" } set stdin_e [fileevent stdin readable] fileevent stdin readable {} set stdin_s [fconfigure stdin] set stdout_s [fconfigure stdout] set stderr_s [fconfigure stderr] fconfigure stdin -buffering none -blocking 1 -translation auto fconfigure stdout -buffering line -blocking 1 -translation auto fconfigure stderr -buffering line -blocking 1 -translation auto set busy 1 catch { bgexec ::pty::term pty {*}$args >@ stdout <@ stdin 2>@ stderr } result options set busy 0 fconfigure stdin {*}$stdin_s fconfigure stdout {*}$stdout_s fconfigure stderr {*}$stderr_s fileevent stdin readable $stdin_e # Ignore error if we forced termination. # The error in this case is always: Child process terminated unexpectedly if {[lindex $term 0] eq {KILLED}} return return -options $options $result } # Kill an ongoing ptyexec execution. proc ptykill {} { variable term 1 } # Resize the window size of the PTY of an ongoing ptyexec session. proc ptyresize {} { # XXX } } ;# Namespace ends