def do_export_vars(command): """ Usage: :export_vars Export some environment variables on enabled remote shells. POLYSH_NR_SHELLS is the total number of enabled shells. POLYSH_RANK uniquely identifies each shell with a number between 0 and POLYSH_NR_SHELLS - 1. POLYSH_NAME is the hostname as specified on the command line and POLYSH_DISPLAY_NAME the hostname as displayed by :list (most of the time the same as POLYSH_NAME). """ rank = 0 for shell in dispatchers.all_instances(): if shell.enabled: environment_variables = { 'POLYSH_RANK': rank, 'POLYSH_NAME': shell.hostname, 'POLYSH_DISPLAY_NAME': shell.display_name, } for name, value in environment_variables.iteritems(): value = pipes.quote(str(value)) shell.dispatch_command('export %s=%s\n' % (name, value)) rank += 1 for shell in dispatchers.all_instances(): if shell.enabled: shell.dispatch_command('export POLYSH_NR_SHELLS=%d\n' % rank)
def upload(local_path): peers = [i for i in dispatchers.all_instances() if i.enabled] if not peers: console_output('No other remote shell to replicate files to\n') return if len(peers) == 1: # We wouldn't be able to show the progress indicator with only one # destination. We need one remote connection in blocking mode to send # the base64 data to. We also need one remote connection in non blocking # mode for polysh to display the progress indicator via the main select # loop. console_output('Uploading to only one remote shell is not supported, ' 'use scp instead\n') return def should_print_bw(node, already_chosen=[False]): if not node.children and not already_chosen[0]: already_chosen[0] = True return True return False tree = file_transfer_tree_node(None, peers[0], peers[1:], 0, should_print_bw, path=local_path, is_upload=True)
def complete_shells(line, text, predicate=lambda i: True): """Return the shell names to include in the completion""" res = [i.display_name + ' ' for i in dispatchers.all_instances() if \ i.display_name.startswith(text) and \ predicate(i) and \ ' ' + i.display_name + ' ' not in line] return res
def complete_shells(line, text, predicate=lambda i: True): """Return the shell names to include in the completion""" res = [ i.display_name + ' ' for i in dispatchers.all_instances() if i.display_name.startswith(text) and predicate(i) and ' ' + i.display_name + ' ' not in line ] return res
def kill_all(): """When polysh quits, we kill all the remote shells we started""" for i in dispatchers.all_instances(): try: os.kill(-i.pid, signal.SIGKILL) except OSError: # The process was already dead, no problem pass
def do_export_vars(command: str) -> None: rank = 0 for shell in dispatchers.all_instances(): if shell.enabled: environment_variables = { 'POLYSH_RANK': str(rank), 'POLYSH_NAME': shell.hostname, 'POLYSH_DISPLAY_NAME': shell.display_name, } for name, value in environment_variables.items(): shell.dispatch_command('export {}={}\n'.format( name, shlex.quote(value)).encode()) rank += 1 for shell in dispatchers.all_instances(): if shell.enabled: shell.dispatch_command( 'export POLYSH_NR_SHELLS={:d}\n'.format(rank).encode())
def do_rename(command): """ Usage: :rename [NEW_NAME] Rename all enabled remote shells with the argument. The argument will be shell expanded on the remote processes. With no argument, the original hostname will be restored as the displayed name. """ for i in dispatchers.all_instances(): if i.enabled: i.rename(command)
def selected_shells(command): """Iterator over the shells with names matching the patterns. An empty patterns matches all the shells""" if not command or command == '*': for i in dispatchers.all_instances(): yield i return selected = set() instance_found = False for pattern in command.split(): found = False for expanded_pattern in expand_syntax(pattern): for i in dispatchers.all_instances(): instance_found = True if fnmatch(i.display_name, expanded_pattern): found = True if i not in selected: selected.add(i) yield i if instance_found and not found: console_output('%s not found\n' % pattern)
def selected_shells(command): """Iterator over the shells with names matching the patterns. An empty patterns matches all the shells""" if not command or command == '*': for i in dispatchers.all_instances(): yield i return selected = set() instance_found = False for pattern in command.split(): found = False for expanded_pattern in expand_syntax(pattern): for i in dispatchers.all_instances(): instance_found = True if fnmatch(i.display_name, expanded_pattern): found = True if i not in selected: selected.add(i) yield i if instance_found and not found: console_output('{} not found\n'.format(pattern).encode())
def do_hide_password(command: str) -> None: warned = False for i in dispatchers.all_instances(): if i.enabled and i.debug: i.debug = False if not warned: console_output(b'Debugging disabled to avoid displaying ' b'passwords\n') warned = True stdin.set_echo(False) if remote_dispatcher.options.log_file: console_output(b'Logging disabled to avoid writing passwords\n') remote_dispatcher.options.log_file = None
def selected_shells( command: str) -> Iterator[remote_dispatcher.RemoteDispatcher]: """Iterator over the shells with names matching the patterns. An empty patterns matches all the shells""" if not command or command == '*': for i in dispatchers.all_instances(): yield i return selected = set() # type: Set[remote_dispatcher.RemoteDispatcher] instance_found = False for pattern in command.split(): found = False for expanded_pattern in expand_syntax(pattern): for i in dispatchers.all_instances(): instance_found = True if (fnmatch(i.display_name, expanded_pattern) or fnmatch( str(i.last_printed_line), expanded_pattern)): found = True if i not in selected: selected.add(i) yield i if instance_found and not found: console_output('{} not found\n'.format(pattern).encode())
def process_input_buffer() -> None: """Send the content of the input buffer to all remote processes, this must be called in the main thread""" from polysh.control_commands_helpers import handle_control_command data = the_stdin_thread.input_buffer.get() remote_dispatcher.log(b'> ' + data) if data.startswith(b':'): try: handle_control_command(data[1:-1].decode()) except UnicodeDecodeError as e: console_output(b'Could not decode command.') return if data.startswith(b'!'): try: retcode = subprocess.call(data[1:], shell=True) except OSError as e: if e.errno == errno.EINTR: console_output(b'Child was interrupted\n') retcode = 0 else: raise if retcode > 128 and retcode <= 192: retcode = 128 - retcode if retcode > 0: console_output('Child returned {:d}\n'.format(retcode).encode()) elif retcode < 0: console_output('Child was terminated by signal {:d}\n'.format( -retcode).encode()) return for r in dispatchers.all_instances(): try: r.dispatch_command(data) except asyncore.ExitNow as e: raise e except Exception as msg: raise msg console_output('{} for {}, disconnecting\n'.format( str(msg), r.display_name).encode()) r.disconnect() else: if r.enabled and r.state is remote_dispatcher.STATE_IDLE: r.change_state(remote_dispatcher.STATE_RUNNING)
def loop(interactive): histfile = os.path.expanduser("~/.polysh_history") init_history(histfile) next_signal = None last_status = None while True: try: if next_signal: current_signal = next_signal next_signal = None sig2chr = {signal.SIGINT: 'C', signal.SIGTSTP: 'Z'} ctrl = sig2chr[current_signal] remote_dispatcher.log('> ^{}\n'.format(ctrl).encode()) control_commands.do_send_ctrl(ctrl) console_output(b'') stdin.the_stdin_thread.prepend_text = None while dispatchers.count_awaited_processes()[0] and \ remote_dispatcher.main_loop_iteration(timeout=0.2): pass # Now it's quiet for r in dispatchers.all_instances(): r.print_unfinished_line() current_status = dispatchers.count_awaited_processes() if current_status != last_status: console_output(b'') if remote_dispatcher.options.interactive: stdin.the_stdin_thread.want_raw_input() last_status = current_status if dispatchers.all_terminated(): # Clear the prompt console_output(b'') raise asyncore.ExitNow(remote_dispatcher.options.exit_code) if not next_signal: # possible race here with the signal handler remote_dispatcher.main_loop_iteration() except KeyboardInterrupt: if interactive: next_signal = signal.SIGINT else: kill_all() os.kill(0, signal.SIGINT) except asyncore.ExitNow as e: console_output(b'') save_history(histfile) sys.exit(e.args[0])
def replicate(shell, path): peers = [i for i in dispatchers.all_instances() if i.enabled] if len(peers) <= 1: console_output('No other remote shell to replicate files to\n') return def should_print_bw(node, already_chosen=[False]): if not node.children and not already_chosen[0] and not node.is_upload: already_chosen[0] = True return True return False sender_index = peers.index(shell) destinations = peers[:sender_index] + peers[sender_index+1:] tree = file_transfer_tree_node(None, shell, destinations, 0, should_print_bw, path=path)
def do_hide_password(command): """ Usage: :hide_password Do not echo the next typed line. This is useful when entering password. If debugging or logging is enabled, it will be disabled to avoid displaying a password. Therefore, you will have to reenable logging or debugging afterwards if need be. """ warned = False for i in dispatchers.all_instances(): if i.enabled and i.debug: i.debug = False if not warned: console_output('Debugging disabled to avoid displaying ' 'passwords\n') warned = True stdin.set_echo(False) if remote_dispatcher.options.log_file: console_output('Logging disabled to avoid writing passwords\n') remote_dispatcher.options.log_file = None
def do_replicate(command): """ Usage: :replicate SHELL:REMOTE_PATH Copy a path from one remote shell to all others """ if ':' not in command: console_output('Usage: :replicate SHELL:REMOTE_PATH\n') return shell_name, path = command.strip().split(':', 1) if not path: console_output('No remote path given\n') return for shell in dispatchers.all_instances(): if shell.display_name == shell_name: if not shell.enabled: console_output('%s is not enabled\n' % shell_name) return break else: console_output('%s not found\n' % shell_name) return file_transfer.replicate(shell, path)
def main_loop(): global next_signal last_status = None while True: try: if next_signal: current_signal = next_signal next_signal = None sig2chr = {signal.SIGINT: 'c', signal.SIGTSTP: 'z'} ctrl = sig2chr[current_signal] remote_dispatcher.log('> ^%c\n' % ctrl.upper()) control_commands.do_send_ctrl(ctrl) console_output('') the_stdin_thread.prepend_text = None while dispatchers.count_awaited_processes()[0] and \ remote_dispatcher.main_loop_iteration(timeout=0.2): pass # Now it's quiet for r in dispatchers.all_instances(): r.print_unfinished_line() current_status = dispatchers.count_awaited_processes() if current_status != last_status: console_output('') if remote_dispatcher.options.interactive: the_stdin_thread.want_raw_input() last_status = current_status if dispatchers.all_terminated(): # Clear the prompt console_output('') raise asyncore.ExitNow(remote_dispatcher.options.exit_code) if not next_signal: # possible race here with the signal handler remote_dispatcher.main_loop_iteration() except asyncore.ExitNow, e: console_output('') sys.exit(e.args[0])
def do_rename(command: str) -> None: for i in dispatchers.all_instances(): if i.enabled: i.rename(command.encode())
retcode = subprocess.call(data[1:], shell=True) except OSError, e: if e.errno == errno.EINTR: console_output('Child was interrupted\n') retcode = 0 else: raise if retcode > 128 and retcode <= 192: retcode = 128 - retcode if retcode > 0: console_output('Child returned %d\n' % retcode) elif retcode < 0: console_output('Child was terminated by signal %d\n' % -retcode) return for r in dispatchers.all_instances(): try: r.dispatch_command(data) except asyncore.ExitNow, e: raise e except Exception, msg: console_output('%s for %s, disconnecting\n' % (msg, r.display_name)) r.disconnect() else: if r.enabled and r.state is remote_dispatcher.STATE_IDLE: r.change_state(remote_dispatcher.STATE_RUNNING) # The stdin thread uses a synchronous (with ACK) socket to communicate with the # main thread, which is most of the time waiting in the poll() loop. # Socket character protocol: # d: there is new data to send
retcode = subprocess.call(data[1:], shell=True) except OSError, e: if e.errno == errno.EINTR: console_output('Child was interrupted\n') retcode = 0 else: raise if retcode > 128 and retcode <= 192: retcode = 128 - retcode if retcode > 0: console_output('Child returned %d\n' % retcode) elif retcode < 0: console_output('Child was terminated by signal %d\n' % -retcode) return for r in dispatchers.all_instances(): try: r.dispatch_command(data) except asyncore.ExitNow, e: raise e except Exception, msg: console_output('%s for %s, disconnecting\n' % (msg, r.display_name)) r.disconnect() else: if r.enabled and r.state is remote_dispatcher.STATE_IDLE: r.change_state(remote_dispatcher.STATE_RUNNING) # The stdin thread uses a synchronous (with ACK) socket to communicate with the # main thread, which is most of the time waiting in the poll() loop.