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 Guillaume Chazarain <guichaz@gmail.com>
31 if sys
.hexversion
< 0x02040000:
32 print >> sys
.stderr
, 'Your python version is too old (%s)' % \
33 (sys
.version
.split()[0])
34 print >> sys
.stderr
, 'You need at least Python 2.4'
37 from gsh
import remote_dispatcher
38 from gsh
import dispatchers
39 from gsh
.console
import console_output
40 from gsh
.stdin
import the_stdin_thread
41 from gsh
.host_syntax
import expand_syntax
42 from gsh
.version
import VERSION
43 from gsh
import control_commands
46 """When gsh quits, we kill all the remote shells we started"""
47 for i
in dispatchers
.all_instances():
49 os
.kill(-i
.pid
, signal
.SIGKILL
)
51 # The process was already dead, no problem
55 usage
= '%s [OPTIONS] HOSTS...\n' % (sys
.argv
[0]) + \
56 'Control commands are prefixed by ":". Use :help for the list'
57 parser
= optparse
.OptionParser(usage
, version
='gsh ' + VERSION
)
58 parser
.add_option('--hosts-file', type='str', action
='append',
59 dest
='hosts_filenames', metavar
='FILE', default
=[],
60 help='read hostnames from given file, one per line')
61 parser
.add_option('--command', type='str', dest
='command', default
=None,
62 help='command to execute on the remote shells',
64 def_ssh
= 'exec ssh -oLogLevel=Quiet -t %(host)s exec bash --noprofile'
65 parser
.add_option('--ssh', type='str', dest
='ssh', default
=def_ssh
,
66 metavar
='SSH', help='ssh command to use [%s]' % def_ssh
)
67 parser
.add_option('--user', type='str', dest
='user', default
=None,
68 help='remote user to log in as', metavar
='USER')
69 parser
.add_option('--password-file', type='str', dest
='password_file',
70 default
=None, metavar
='FILE',
71 help='read a password from the specified file. - is ' +
73 parser
.add_option('--log-file', type='str', dest
='log_file',
74 help='file to log each machine conversation [none]')
75 parser
.add_option('--abort-errors', action
='store_true', dest
='abort_error',
76 help='abort if some shell fails to initialize [ignore]')
77 parser
.add_option('--debug', action
='store_true', dest
='debug',
78 help='print debugging information.')
79 parser
.add_option('--profile', action
='store_true', dest
='profile',
80 default
=False, help=optparse
.SUPPRESS_HELP
)
82 options
, args
= parser
.parse_args()
83 for filename
in options
.hosts_filenames
:
85 hosts_file
= open(filename
, 'r')
86 for line
in hosts_file
.readlines():
88 line
= line
[:line
.index('#')]
98 options
.log_file
= file(options
.log_file
, 'a')
104 parser
.error('no hosts given')
106 if options
.password_file
== '-':
107 options
.password
= getpass
.getpass()
108 elif options
.password_file
is not None:
109 password_file
= file(options
.password_file
, 'r')
110 options
.password
= password_file
.readline().rstrip('\n')
112 options
.password
= None
116 def find_non_interactive_command(command
):
117 if sys
.stdin
.isatty():
120 stdin
= sys
.stdin
.read()
121 if stdin
and command
:
122 print >> sys
.stderr
, '--command and reading from stdin are incompatible'
124 if stdin
and not stdin
.endswith('\n'):
126 return command
or stdin
134 current_signal
= next_signal
136 sig2chr
= {signal
.SIGINT
: 'c', signal
.SIGTSTP
: 'z'}
137 ctrl
= sig2chr
[current_signal
]
138 remote_dispatcher
.log('> ^%c\n' % ctrl
.upper())
139 control_commands
.do_send_ctrl(ctrl
)
141 the_stdin_thread
.prepend_text
= None
142 while dispatchers
.count_awaited_processes()[0] and \
143 remote_dispatcher
.main_loop_iteration(timeout
=0.2):
146 for r
in dispatchers
.all_instances():
147 r
.print_unfinished_line()
148 current_status
= dispatchers
.count_awaited_processes()
149 if current_status
!= last_status
:
151 if remote_dispatcher
.options
.interactive
:
152 the_stdin_thread
.want_raw_input()
153 last_status
= current_status
154 if dispatchers
.all_terminated():
157 raise asyncore
.ExitNow(remote_dispatcher
.options
.exit_code
)
159 # possible race here with the signal handler
160 remote_dispatcher
.main_loop_iteration()
161 except asyncore
.ExitNow
, e
:
165 def setprocname(name
):
166 # From comments on http://davyd.livejournal.com/166352.html
170 libc
= ctypes
.CDLL(None)
171 # Linux 2.6 PR_SET_NAME
172 if libc
.prctl(15, name
, 0, 0, 0):
174 libc
.setproctitle(name
)
181 # Linux 2.6 PR_SET_NAME
182 if libc
.call('prctl', 15, name
, 0, 0, 0):
184 libc
.call('setproctitle', name
)
188 def _profile(continuation
):
189 prof_file
= 'gsh.prof'
193 print 'Profiling using cProfile'
194 cProfile
.runctx('continuation()', globals(), locals(), prof_file
)
195 stats
= pstats
.Stats(prof_file
)
199 prof
= hotshot
.Profile(prof_file
, lineevents
=1)
200 print 'Profiling using hotshot'
201 prof
.runcall(continuation
)
203 stats
= hotshot
.stats
.load(prof_file
)
205 stats
.sort_stats('time', 'calls')
206 stats
.print_stats(50)
207 stats
.print_callees(50)
210 def restore_tty_on_exit():
211 fd
= sys
.stdin
.fileno()
212 old
= termios
.tcgetattr(fd
)
213 atexit
.register(lambda: termios
.tcsetattr(fd
, termios
.TCSADRAIN
, old
))
215 # We handle signals in the main loop, this way we can be signaled while
221 locale
.setlocale(locale
.LC_ALL
, '')
223 options
, args
= parse_cmdline()
225 atexit
.register(kill_all
)
226 signal
.signal(signal
.SIGPIPE
, signal
.SIG_DFL
)
227 options
.command
= find_non_interactive_command(options
.command
)
228 options
.exit_code
= 0
229 options
.interactive
= not options
.command
and sys
.stdin
.isatty() and \
231 if options
.interactive
:
232 def handler(sig
, frame
):
235 signal
.signal(signal
.SIGINT
, handler
)
236 signal
.signal(signal
.SIGTSTP
, handler
)
237 restore_tty_on_exit()
239 def handler(sig
, frame
):
240 signal
.signal(sig
, signal
.SIG_DFL
)
243 signal
.signal(signal
.SIGINT
, handler
)
245 remote_dispatcher
.options
= options
249 hosts
.extend(expand_syntax(arg
))
251 dispatchers
.create_remote_dispatchers(hosts
)
253 signal
.signal(signal
.SIGWINCH
, lambda signum
, frame
:
254 dispatchers
.update_terminal_size())
256 the_stdin_thread
.activate(options
.interactive
)
259 def safe_main_loop():
264 _profile(safe_main_loop
)