1 # This program is free software; you can redistribute it and/or modify
2 # it under the terms of the GNU General Public License as published by
3 # the Free Software Foundation; either version 2 of the License, or
4 # (at your option) any later version.
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # GNU Library General Public License for more details.
11 # You should have received a copy of the GNU General Public License
12 # along with this program; if not, write to the Free Software
13 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15 # See the COPYING file for license information.
17 # Copyright (c) 2006, 2007, 2008 Guillaume Chazarain <guichaz@gmail.com>
30 if sys
.hexversion
< 0x02040000:
31 print >> sys
.stderr
, 'Your python version is too old (%s)' % \
32 (sys
.version
.split()[0])
33 print >> sys
.stderr
, 'You need at least Python 2.4'
36 from gsh
import remote_dispatcher
37 from gsh
import dispatchers
38 from gsh
.console
import console_output
39 from gsh
.stdin
import the_stdin_thread
, ignore_sigchld
40 from gsh
.host_syntax
import expand_syntax
41 from gsh
.version
import VERSION
42 from gsh
import control_commands
45 """When gsh quits, we kill all the remote shells we started"""
46 for i
in dispatchers
.all_instances():
48 os
.kill(i
.pid
, signal
.SIGKILL
)
50 # The process was already dead, no problem
54 usage
= '%s [OPTIONS] HOSTS...\n' % (sys
.argv
[0]) + \
55 'Control commands are prefixed by ":". Use :help for the list'
56 parser
= optparse
.OptionParser(usage
, version
='gsh ' + VERSION
)
57 parser
.add_option('--hosts-file', type='str', action
='append',
58 dest
='hosts_filenames', metavar
='FILE', default
=[],
59 help='read hostnames from given file, one per line')
60 parser
.add_option('--command', type='str', dest
='command', default
=None,
61 help='command to execute on the remote shells',
63 def_ssh
= 'ssh -t %(host)s bash --noprofile'
64 parser
.add_option('--ssh', type='str', dest
='ssh', default
=def_ssh
,
65 metavar
='SSH', help='ssh command to use [%s]' % def_ssh
)
66 parser
.add_option('--log-file', type='str', dest
='log_file',
67 help='file to log each machine conversation [none]')
68 parser
.add_option('--abort-errors', action
='store_true', dest
='abort_error',
69 help='abort if some shell fails to initialize [ignore]')
70 parser
.add_option('--debug', action
='store_true', dest
='debug',
71 help='print debugging information.')
72 parser
.add_option('--profile', action
='store_true', dest
='profile',
73 default
=False, help=optparse
.SUPPRESS_HELP
)
75 options
, args
= parser
.parse_args()
76 for filename
in options
.hosts_filenames
:
78 hosts_file
= open(filename
, 'r')
79 for line
in hosts_file
.readlines():
81 line
= line
[:line
.index('#')]
91 options
.log_file
= file(options
.log_file
, 'a')
97 parser
.error('no hosts given')
101 def find_non_interactive_command(command
):
102 if sys
.stdin
.isatty():
105 stdin
= sys
.stdin
.read()
106 if stdin
and command
:
107 print >> sys
.stderr
, '--command and reading from stdin are incompatible'
109 if stdin
and not stdin
.endswith('\n'):
111 return command
or stdin
119 current_signal
= next_signal
121 sig2chr
= {signal
.SIGINT
: 'c', signal
.SIGTSTP
: 'z'}
122 ctrl
= sig2chr
[current_signal
]
123 remote_dispatcher
.log('> ^%c\n' % ctrl
.upper())
124 control_commands
.do_send_ctrl(ctrl
)
126 the_stdin_thread
.prepend_text
= None
127 while dispatchers
.count_awaited_processes()[0] and \
128 remote_dispatcher
.main_loop_iteration(timeout
=0.2):
131 for r
in dispatchers
.all_instances():
132 r
.print_unfinished_line()
133 current_status
= dispatchers
.count_awaited_processes()
134 if current_status
!= last_status
:
136 if remote_dispatcher
.options
.interactive
:
137 the_stdin_thread
.want_raw_input()
138 last_status
= current_status
139 if dispatchers
.all_terminated():
142 raise asyncore
.ExitNow(0)
144 # possible race here with the signal handler
145 remote_dispatcher
.main_loop_iteration()
146 except asyncore
.ExitNow
, e
:
150 def setprocname(name
):
151 # From comments on http://davyd.livejournal.com/166352.html
155 libc
= ctypes
.CDLL(None)
156 # Linux 2.6 PR_SET_NAME
157 if libc
.prctl(15, name
, 0, 0, 0):
159 libc
.setproctitle(name
)
166 # Linux 2.6 PR_SET_NAME
167 if libc
.call('prctl', 15, name
, 0, 0, 0):
169 libc
.call('setproctitle', name
)
173 def _profile(continuation
):
174 prof_file
= 'gsh.prof'
178 print 'Profiling using cProfile'
179 cProfile
.runctx('continuation()', globals(), locals(), prof_file
)
180 stats
= pstats
.Stats(prof_file
)
184 prof
= hotshot
.Profile(prof_file
, lineevents
=1)
185 print 'Profiling using hotshot'
186 prof
.runcall(continuation
)
188 stats
= hotshot
.stats
.load(prof_file
)
190 stats
.sort_stats('time', 'calls')
191 stats
.print_stats(50)
192 stats
.print_callees(50)
195 def restore_tty_on_exit():
196 fd
= sys
.stdin
.fileno()
197 old
= termios
.tcgetattr(fd
)
198 atexit
.register(lambda: termios
.tcsetattr(fd
, termios
.TCSADRAIN
, old
))
200 # We handle signals in the main loop, this way we can be signaled while
206 locale
.setlocale(locale
.LC_ALL
, '')
208 options
, args
= parse_cmdline()
210 atexit
.register(kill_all
)
211 ignore_sigchld(True) # Don't create zombies
212 signal
.signal(signal
.SIGPIPE
, signal
.SIG_DFL
)
213 options
.command
= find_non_interactive_command(options
.command
)
214 options
.interactive
= not options
.command
and sys
.stdin
.isatty() and \
216 if options
.interactive
:
217 def handler(sig
, frame
):
220 signal
.signal(signal
.SIGINT
, handler
)
221 signal
.signal(signal
.SIGTSTP
, handler
)
222 restore_tty_on_exit()
224 def handler(sig
, frame
):
225 signal
.signal(sig
, signal
.SIG_DFL
)
228 signal
.signal(signal
.SIGINT
, handler
)
230 remote_dispatcher
.options
= options
233 for host
in expand_syntax(arg
):
234 remote_dispatcher
.remote_dispatcher(host
)
236 signal
.signal(signal
.SIGWINCH
, lambda signum
, frame
:
237 dispatchers
.update_terminal_size())
239 the_stdin_thread
.activate(options
.interactive
)
242 def safe_main_loop():
247 _profile(safe_main_loop
)