├── .gitignore ├── AUTHORS ├── COPYING ├── ChangeLog ├── README.md ├── bin ├── pnuke ├── prsync ├── pscp ├── pslurp ├── pssh └── pssh-askpass ├── doc ├── index.html ├── pssh-HOWTO.html ├── pssh-HOWTO.pdf ├── pssh-HOWTO.ps └── pssh-HOWTO.sgml ├── man └── man1 │ ├── pnuke.1 │ ├── prsync.1 │ ├── pscp.1 │ ├── pslurp.1 │ └── pssh.1 ├── psshlib ├── __init__.py ├── askpass_client.py ├── askpass_server.py ├── cli.py ├── color.py ├── manager.py ├── pardo.py ├── psshutil.py ├── task.py └── version.py ├── pyproject.toml ├── setup.cfg └── test └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | 依云 2 | Andrew McNabb 3 | Brent Chun 4 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Jeffrey Lund 2 | Copyright (c) 2016-2017, lilydjwg 3 | Copyright (c) 2009-2012, Andrew McNabb 4 | Copyright (c) 2003-2008, Brent N. Chun 5 | 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are 10 | met: 11 | 12 | * Redistributions of source code must retain the above copyright 13 | notice, this list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above 16 | copyright notice, this list of conditions and the following 17 | disclaimer in the documentation and/or other materials provided 18 | with the distribution. 19 | 20 | * The names of its contributors may not be used to endorse or 21 | promote products derived from this software without specific 22 | prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 27 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 28 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 29 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 30 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 32 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2023-03-22 依云 2 | * Version 2.3.5 3 | - Add --download to pscp 4 | - Add --fileappend option 5 | - Add --no-headers 6 | - Add --inline to prsync 7 | - Switch to pyproject.toml 8 | - Fix IPv6 address as host 9 | 2020-04-05 依云 10 | * Version 2.3.4 11 | - Add -g/--host-glob option, which accepts a glob to match in hosts 12 | - Fixes 13 | 2018-09-29 Jeffrey Lund 14 | * Version 2.3.3 15 | - Added PSSH_NUMNODES enviroment variable 16 | - Added pardo helpers to psshlib 17 | 2017-09-14 依云 18 | * Version 2.3.2 19 | - Remove support for Python 2.4 20 | - Many small fixes and code cleanup 21 | 2012-02-02 Andrew McNabb 22 | * Version 2.3.1 23 | - Fixed a problem where man pages were omitted from the tar file. 24 | - Note that this release makes no changes to the programs. 25 | 2012-01-24 Andrew McNabb 26 | * Version 2.3 27 | - Added a --inline-stdout option (issue #57). Thanks to 28 | pablo.barbachano for the patch. 29 | - Added a PSSH_HOST environment variable (issue #62). 30 | - Added a --version option (issues #33 and #45). 31 | - Expanded the pssh man page and added man pages for all other 32 | commands (issues #10 and #55). 33 | - Fixed askpass on Mac OS X 10.6.7 (issue #50). 34 | - Many other small fixes. 35 | 2011-02-02 Andrew McNabb 36 | * Version 2.2.2 37 | - Fixed two crashes (issues 35 and 36). One affects Python <= 2.5 38 | and the other affects all of the scripts except pssh. 39 | 2011-01-26 Andrew McNabb 40 | * Version 2.2.1 41 | - Fixed a crash when the -l option was used in conjunction with a 42 | hosts file (issue #34). 43 | 2011-01-21 Andrew McNabb 44 | * Version 2.2 45 | - Added a basic man page for pssh (issue #10). 46 | - Fixed askpass to work correctly in the presence of non-password 47 | prompts from ssh (issue #23). 48 | - Updated the -O option so that it can be specified multiple times 49 | (issue #25). Thanks to soham.mehta for the patch. 50 | - Fixed host file loader to give an error instead of a backtrace 51 | if a file is not found. 52 | - Fixed prsync's "-ssh-args" mangling of its argument (issue #24). 53 | Thanks to jbyers for the patch. 54 | - Fixed some variable names to appease pylint. Thanks to solj for 55 | the patch. 56 | - Improved pscp to be able to copy multiple local files. Thanks 57 | to Carlo Marcelo Arenas Belon for the patch. 58 | - Deprecated the PSSH_HOSTS environment variable that seems to 59 | cause more problems than it's worth. 60 | - Added the ability to set multiple hosts with a single -H flag 61 | (issue #26). Thanks to ilya@sukhanov.net for the patch. 62 | - Stopped passing "-q" to ssh by default (this masked error messages 63 | and reduced usability). 64 | - Removed automatic reading from stdin (deprecated in version 2.1). 65 | Please use the "-I" option instead. 66 | - Added meaningful exit status codes (issue #30). 67 | - Other minor fixes 68 | 2010-02-26 Andrew McNabb 69 | * Version 2.1.1 70 | - Fixed a problem causing PSSH to crash with Python 2.4. 71 | 2010-02-24 Andrew McNabb 72 | * Version 2.1 73 | - Added support for Python 3.0 and 3.1. Although PSSH has only 74 | been lightly tested with Python 3, anything that doesn't work 75 | in Python 3 is officially a bug. 76 | - Added a "-H" option for specifying hosts one-by-one instead of 77 | or in addition to a hosts file. 78 | - Added "-x" and "-X" options for passing extra command-line 79 | arguments to ssh and rsync. Also added a "-S" option to prsync 80 | for the special case of passing extra arguments to ssh 81 | (issue #2). 82 | - Added a "-I" option for specifying that pssh should read from 83 | standard input, and added a deprecation warning when standard 84 | input is used without this option (issue #12). 85 | - Made the command argument optional when the "-I" option is 86 | given, so a script can be passed to pssh on standard input 87 | (issue #5). 88 | - If a username or port is given, these are now included in the 89 | output filename, which allows different connections to be 90 | distinguished from each other (issue #7). 91 | - Added the pssh-askpass wrapper as a standalone script because 92 | setup.py was removing the executable bit from askpass.py. This 93 | fixes a "permission denied" error when using the -A option 94 | (issue #8). 95 | - Fixed a problem where pssh was unnecessarily specifying a 96 | username (issue #14). 97 | - Fixed a delay due to a lost SIGCHLD signal. 98 | - Removed extra spaces between output chunks in outdir files 99 | (issue #6). Thanks to knutsen for the fix. 100 | - Fixed a bug where pscp passed the wrong option for sending scp 101 | a custom port. Thanks to Jan Rafaj for the patch. 102 | - Fixed prsync to send the port as an option to ssh (issue #1). 103 | Thanks to Ryan Brothers for the fix. 104 | 2009-10-20 Andrew McNabb 105 | * Version 2.0 106 | - Rewrote communication code to be more efficient. PSSH now 107 | operates with only one or two threads. 108 | - Added the ability to interrupt PSSH (with CTRL-c). 109 | - Added an option to prompt for a password. 110 | - Refactored code into a distinct library (psshlib). 111 | 2008-10-12 Brent N. Chun 112 | * Version 1.4.3 113 | - Fixed bug in select_wrap. If timeout is None (e.g., the 114 | default for prsync, etc.), then never time out. Bug reported 115 | by Carlo Marcelo Arenas Belon (carenas at sajinet.com.pe). 116 | - Catch getopt exceptions and print usage as well as getopt 117 | exception string. Contribution from Carlo Marcelo Arenas 118 | Belon (carenas at sajinet.com.pe). 119 | - Added contribution from Bas van der Vlies (basv at sara.nl) 120 | to allow comments in hosts file. Comments must begin with 121 | # character (leading whitespace is also allowed). 122 | - Restore file status bugs after reading stdin in pssh. 123 | - Fixed typo bug in pslurp when using options and in 124 | non-recursive mode. 125 | - Removed conflicts with built-in names. 126 | 2008-09-01 Brent N. Chun 127 | * Version 1.4.2 128 | - Fixed minor bug: select returns select.error on an error, 129 | not OSError. 130 | 2008-08-27 Brent N. Chun 131 | * Version 1.4.1 132 | - Removed broken SIGCHLD handler. 133 | - Refining subprocess _cleanup dynamically to an empty lambda 134 | function since subprocess is not thread-safe and we already 135 | call wait on child processes ourselves anyway. 136 | - Adding missing verbose flag to rest of bin/* programs. 137 | 2008-08-24 Brent N. Chun 138 | * Version 1.4.0 139 | - Fixed 64-bit bug in pslurp, pscp, prsync. Previously, the 140 | default select timeout was sys.maxint, but this is a 64-bit 141 | value on 64-machines. Now using None when calling select when 142 | there is no timeout. Bug reported by (buixor at gmail.com). 143 | - Catching EINTR and ignoring it for select, read, write in 144 | BaseThread class. 145 | - Fixed longopts for pnuke, prsync, pscp, pslurp, pssh (bug 146 | reported a Debian user via Andrew Pollock (apollock at 147 | debian.org)). 148 | Reference: "bug #481901: pssh: options mis-specified" 149 | - Added missing environment variables for options for pnuke, 150 | prsync, pscp, pslurp, pssh. 151 | 2008-06-04 Brent N. Chun 152 | * Version 1.3.2 153 | - Added shortopts bug fix from Lev Givon (lev at 154 | columbia.edu) in bin/pssh. 155 | 2007-04-11 Brent N. Chun 156 | * Version 1.3.1 157 | - Reverted I/O back to 1.2.2. style pssh I/O. 158 | 2007-04-10 Brent N. Chun 159 | * Version 1.3.0 160 | - Added contributions from Deepak Giridharagopal (deepak at 161 | brownman.org) 162 | * Added ANSI color to pssh, pscp, etc. output. 163 | * Each status message now includes a timestamp. 164 | * Failures now indicate the cause (e.g., timeout, etc.) 165 | * Intermediate directories are created as needed for output. 166 | * Removed use of setsid in shell exec. 167 | * Using Exception objects rather than raw strings. 168 | * Added support for piping stdin to each ssh process. 169 | * Added -i option to pssh for "inlining" output to stdout. 170 | * Added Python setup.py file for a standard install. 171 | - Switched to BSD license. 172 | 2006-06-18 Brent N. Chun 173 | * Version 1.2.2 174 | - Added patch from Dan Silverstein (dans at pch.net) to fix 175 | parsecmdline bug. 176 | 2005-12-31 Brent N. Chun 177 | * Version 1.2.1 178 | - Changed sys.path so pssh can run without RPM 179 | install. 180 | - Changed RPM library files to install in 181 | /usr/local/lib/python 182 | - make install and make uninstall now work as 183 | expected for installations from source. 184 | 2004-11-10 Brent N. Chun 185 | * Added patch from Dave Barr 186 | - Adds -a, -z flags to prsync 187 | 2004-10-05 Brent N. Chun 188 | * Default user is now current user in all programs (on 189 | suggestion from Jim Wight ). 190 | * Fixed path typo on prsync from 1.1.0 release 191 | * Version 1.1.1 192 | 2004-10-04 Brent N. Chun 193 | * Added patch from Dave Barr 194 | - Adds an ssh options flag (-O) to prsync 195 | * Added patch from Chad Yoshikawa 196 | - Adds a print to stdout flag (-P) to pssh 197 | * Version 1.1.0 198 | 2004-08-21 Brent N. Chun 199 | * All cmds now take -o, -e for stdout, stderr 200 | * Checking return values for all cmds 201 | * Factored common thread structure out of all cmds 202 | * Changed pslurp's dir for local to -L, rather than -o (stdout) 203 | * Version 1.0.0 204 | 2003-11-20 Brent N. Chun 205 | * Added handler for SIGCHLD 206 | * Wait for all threads before returning to main thread 207 | * Kill all straggler processes when done 208 | 2003-11-18 Brent N. Chun 209 | * Added pslurp (scp from remote nodes), updated to 0.2.3 210 | 2003-11-12 Brent N. Chun 211 | * Fixed read bug, so all output is obtained. 212 | * Added timeout option (defaults to None for pscp/prsync) 213 | * Added verbose option for pssh/pnuke (this is -q or not) 214 | * Added environment variables for options 215 | * Fixed usage for pnuke 216 | 2003-09-06 Brent N. Chun 217 | * Added -O for pssh, pscp, and pnuke for passing SSH options 218 | * Changed order of options in usage (required, optional) 219 | 2003-09-06 Brent N. Chun 220 | * Added parallel rsync (prsync) 221 | * Added support for "host[:port] user" lines in hosts files 222 | * Factored a bit of code out into lib/python/psshutil.py 223 | 2003-08-16 Brent N. Chun 224 | * Initial version (0.1.0) 225 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PSSH provides parallel versions of OpenSSH and related tools, including pssh, pscp, prsync, pnuke and pslurp. This project includes psshlib which can be used within custom applications. The source code is written in Python. 2 | 3 | This fork of PSSH is supported on Python 3.5 and later. It was originally written and maintained by Brent N. Chun. Due to his busy schedule, Brent handed over maintenance to Andrew McNabb in October 2009. 4 | 5 | This project originally located at [Google Code](https://code.google.com/p/parallel-ssh/). Since Google Code has been closed, and has not appeared elsewhere, I (lilydjwg) have exported it to GitHub. 6 | 7 | ## Install 8 | 9 | ``` 10 | pip install git+https://github.com/lilydjwg/pssh 11 | ``` 12 | -------------------------------------------------------------------------------- /bin/pnuke: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- Mode: python -*- 3 | 4 | # Copyright (c) 2009-2012, Andrew McNabb 5 | # Copyright (c) 2003-2008, Brent N. Chun 6 | 7 | """Nukes all processes that match pattern running as user on the set of nodes 8 | in hosts.txt. 9 | """ 10 | 11 | import os 12 | import sys 13 | 14 | parent, bindir = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) 15 | if os.path.exists(os.path.join(parent, 'psshlib')): 16 | sys.path.insert(0, parent) 17 | 18 | from psshlib import psshutil 19 | from psshlib.task import Task 20 | from psshlib.manager import Manager, FatalError 21 | from psshlib.cli import common_parser, common_defaults 22 | 23 | _DEFAULT_TIMEOUT = 60 24 | 25 | def option_parser(): 26 | parser = common_parser() 27 | parser.usage = "%prog [OPTIONS] pattern" 28 | parser.epilog = "Example: pnuke -h hosts.txt -l irb2 java" 29 | return parser 30 | 31 | def parse_args(): 32 | parser = option_parser() 33 | defaults = common_defaults(timeout=_DEFAULT_TIMEOUT) 34 | parser.set_defaults(**defaults) 35 | opts, args = parser.parse_args() 36 | 37 | if len(args) < 1: 38 | parser.error('Pattern not specified.') 39 | 40 | if len(args) > 1: 41 | parser.error('Extra arguments given after the pattern.') 42 | 43 | if not opts.host_files and not opts.host_strings: 44 | parser.error('Hosts not specified.') 45 | 46 | return opts, args 47 | 48 | def do_pnuke(hosts, pattern, opts): 49 | if opts.outdir and not os.path.exists(opts.outdir): 50 | os.makedirs(opts.outdir) 51 | if opts.errdir and not os.path.exists(opts.errdir): 52 | os.makedirs(opts.errdir) 53 | manager = Manager(opts) 54 | for host, port, user in hosts: 55 | cmd = ['ssh', host, '-o', 'NumberOfPasswordPrompts=1'] 56 | if opts.options: 57 | for opt in opts.options: 58 | cmd += ['-o', opt] 59 | if user: 60 | cmd += ['-l', user] 61 | if port: 62 | cmd += ['-p', port] 63 | if opts.extra: 64 | cmd.extend(opts.extra) 65 | cmd.append('pkill -9 %s' % pattern) 66 | t = Task(host, port, user, cmd, opts) 67 | manager.add_task(t) 68 | try: 69 | statuses = manager.run() 70 | except FatalError: 71 | sys.exit(1) 72 | 73 | if statuses and min(statuses) < 0: 74 | # At least one process was killed. 75 | sys.exit(3) 76 | for status in statuses: 77 | if status == 255: 78 | sys.exit(4) 79 | for status in statuses: 80 | if status != 0: 81 | sys.exit(5) 82 | 83 | if __name__ == "__main__": 84 | opts, args = parse_args() 85 | pattern = args[0] 86 | try: 87 | hosts = psshutil.read_host_files(opts.host_files, opts.host_glob, 88 | default_user=opts.user) 89 | except IOError: 90 | _, e, _ = sys.exc_info() 91 | sys.stderr.write('Could not open hosts file: %s\n' % e.strerror) 92 | sys.exit(1) 93 | if opts.host_strings: 94 | for s in opts.host_strings: 95 | hosts.extend(psshutil.parse_host_string(s, default_user=opts.user)) 96 | do_pnuke(hosts, pattern, opts) 97 | -------------------------------------------------------------------------------- /bin/prsync: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- Mode: python -*- 3 | 4 | # Copyright (c) 2009-2012, Andrew McNabb 5 | # Copyright (c) 2003-2008, Brent N. Chun 6 | 7 | """Parallel rsync to the set of nodes in hosts.txt. 8 | 9 | For each node, we essentially do a rsync -rv -e ssh local user@host:remote. 10 | Note that remote must be an absolute path. 11 | """ 12 | 13 | import os 14 | import sys 15 | 16 | parent, bindir = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) 17 | if os.path.exists(os.path.join(parent, 'psshlib')): 18 | sys.path.insert(0, parent) 19 | 20 | from psshlib import psshutil 21 | from psshlib.task import Task 22 | from psshlib.manager import Manager, FatalError 23 | from psshlib.cli import common_parser, common_defaults 24 | 25 | def option_parser(): 26 | parser = common_parser() 27 | parser.usage = "%prog [OPTIONS] local remote" 28 | parser.epilog = ("Example: prsync -r -h hosts.txt -l irb2 foo " + 29 | "/home/irb2/foo") 30 | 31 | parser.add_option('-r', '--recursive', dest='recursive', 32 | action='store_true', help='recusively copy directories (OPTIONAL)') 33 | parser.add_option('-a', '--archive', dest='archive', action='store_true', 34 | help='use rsync -a (archive mode) (OPTIONAL)') 35 | parser.add_option('-z', '--compress', dest='compress', action='store_true', 36 | help='use rsync compression (OPTIONAL)') 37 | parser.add_option('-S', '--ssh-args', metavar="ARGS", dest='ssh_args', 38 | action='store', help='extra arguments for ssh') 39 | parser.add_option('-d', '--download', dest='download', action='store_true', 40 | help='download files instead, reversing local and remote (OPTIONAL)') 41 | parser.add_option('-i', '--inline', dest='inline', action='store_true', 42 | help='inline aggregated output and error for each server') 43 | 44 | return parser 45 | 46 | def parse_args(): 47 | parser = option_parser() 48 | defaults = common_defaults() 49 | parser.set_defaults(**defaults) 50 | opts, args = parser.parse_args() 51 | 52 | if len(args) < 1: 53 | parser.error('Paths not specified.') 54 | 55 | if len(args) < 2: 56 | parser.error('Remote path not specified.') 57 | 58 | if not opts.host_files and not opts.host_strings: 59 | parser.error('Hosts not specified.') 60 | 61 | return opts, args 62 | 63 | def do_prsync(hosts, local, remote, opts): 64 | if opts.outdir and not os.path.exists(opts.outdir): 65 | os.makedirs(opts.outdir) 66 | if opts.errdir and not os.path.exists(opts.errdir): 67 | os.makedirs(opts.errdir) 68 | manager = Manager(opts) 69 | for host, port, user in hosts: 70 | ssh = ['ssh'] 71 | if opts.options: 72 | for opt in opts.options: 73 | ssh += ['-o', opt] 74 | if port: 75 | ssh += ['-p', port] 76 | if opts.ssh_args: 77 | ssh += [opts.ssh_args] 78 | 79 | cmd = ['rsync', '-e', ' '.join(ssh)] 80 | if opts.verbose: 81 | cmd.append('-v') 82 | if opts.recursive: 83 | cmd.append('-r') 84 | if opts.archive: 85 | cmd.append('-a') 86 | if opts.compress: 87 | cmd.append('-z') 88 | if opts.extra: 89 | cmd.extend(opts.extra) 90 | if opts.download: 91 | if len(local) != 1: 92 | sys.exit('can only download one target at a time') 93 | if user: 94 | cmd.append('%s@%s:%s' % (user, host, local[0])) 95 | else: 96 | cmd.append('%s:%s' % (host, local[0])) 97 | localpath = "%s/%s" % (remote, host) 98 | os.makedirs(localpath) 99 | cmd.append(localpath) 100 | else: 101 | cmd.extend(local) 102 | if user: 103 | cmd.append('%s@%s:%s' % (user, host, remote)) 104 | else: 105 | cmd.append('%s:%s' % (host, remote)) 106 | t = Task(host, port, user, cmd, opts) 107 | manager.add_task(t) 108 | try: 109 | statuses = manager.run() 110 | except FatalError: 111 | sys.exit(1) 112 | 113 | if statuses and min(statuses) < 0: 114 | # At least one process was killed. 115 | sys.exit(3) 116 | for status in statuses: 117 | if status != 0: 118 | sys.exit(4) 119 | 120 | if __name__ == "__main__": 121 | opts, args = parse_args() 122 | local = args[0:-1] 123 | remote = args[-1] 124 | try: 125 | hosts = psshutil.read_host_files(opts.host_files, opts.host_glob, 126 | default_user=opts.user) 127 | except IOError: 128 | _, e, _ = sys.exc_info() 129 | sys.stderr.write('Could not open hosts file: %s\n' % e.strerror) 130 | sys.exit(1) 131 | if opts.host_strings: 132 | for s in opts.host_strings: 133 | hosts.extend(psshutil.parse_host_string(s, default_user=opts.user)) 134 | do_prsync(hosts, local, remote, opts) 135 | -------------------------------------------------------------------------------- /bin/pscp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- Mode: python -*- 3 | 4 | # Copyright (c) 2009-2012, Andrew McNabb 5 | # Copyright (c) 2003-2008, Brent N. Chun 6 | 7 | # Extend to support download, Tommi Linnakangas 8 | 9 | """Parallel scp to the set of nodes in hosts.txt. 10 | 11 | For each node, we essentially do a scp [-r] local user@host:remote. This 12 | program also uses the -q (quiet) and -C (compression) options. Note that 13 | remote must be an absolute path. 14 | """ 15 | 16 | import os 17 | import sys 18 | 19 | parent, bindir = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) 20 | if os.path.exists(os.path.join(parent, 'psshlib')): 21 | sys.path.insert(0, parent) 22 | 23 | from psshlib import psshutil 24 | from psshlib.task import Task 25 | from psshlib.manager import Manager, FatalError 26 | from psshlib.cli import common_parser, common_defaults 27 | 28 | def option_parser(): 29 | parser = common_parser() 30 | parser.usage = "%prog [OPTIONS] source dest" 31 | parser.epilog = ("Example: pscp -h hosts.txt -l irb2 foo.txt " + 32 | "/home/irb2/foo.txt") 33 | 34 | parser.add_option('-r', '--recursive', dest='recursive', 35 | action='store_true', help='recusively copy directories (OPTIONAL)') 36 | parser.add_option('--download', dest='download', 37 | action='store_true', 38 | help='perform download instead of upload; files copied from hosts are ' 39 | 'store to directories with host name as the directory name') 40 | 41 | return parser 42 | 43 | def parse_args(): 44 | parser = option_parser() 45 | defaults = common_defaults() 46 | parser.set_defaults(**defaults) 47 | opts, args = parser.parse_args() 48 | 49 | if len(args) < 1: 50 | parser.error('Paths not specified.') 51 | 52 | if len(args) < 2: 53 | parser.error('Remote path not specified.') 54 | 55 | if not opts.host_files and not opts.host_strings: 56 | parser.error('Hosts not specified.') 57 | 58 | return opts, args 59 | 60 | def do_pscp(hosts, localargs, remoteargs, opts): 61 | if opts.outdir and not os.path.exists(opts.outdir): 62 | os.makedirs(opts.outdir) 63 | if opts.errdir and not os.path.exists(opts.errdir): 64 | os.makedirs(opts.errdir) 65 | manager = Manager(opts) 66 | for host, port, user in hosts: 67 | cmd = ['scp', '-qC'] 68 | if opts.options: 69 | for opt in opts.options: 70 | cmd += ['-o', opt] 71 | if port: 72 | cmd += ['-P', port] 73 | if opts.recursive: 74 | cmd.append('-r') 75 | if opts.extra: 76 | cmd.extend(opts.extra) 77 | if opts.download: 78 | for remote in remoteargs: 79 | if user: 80 | cmd.append('%s@%s:%s' % (user, host, remote)) 81 | else: 82 | cmd.append('%s:%s' % (host, remote)) 83 | localdir = os.path.join(localargs, host) 84 | if not os.path.exists(localdir): 85 | os.makedirs(localdir) 86 | cmd.append(localdir) 87 | else: 88 | cmd.extend(localargs) 89 | if user: 90 | cmd.append('%s@%s:%s' % (user, host, remoteargs)) 91 | else: 92 | cmd.append('%s:%s' % (host, remoteargs)) 93 | t = Task(host, port, user, cmd, opts) 94 | manager.add_task(t) 95 | try: 96 | statuses = manager.run() 97 | except FatalError: 98 | sys.exit(1) 99 | 100 | if statuses and min([status for status in statuses if isinstance(status, int)]) < 0: 101 | # At least one process was killed. 102 | sys.exit(3) 103 | for status in statuses: 104 | if status != 0: 105 | sys.exit(4) 106 | 107 | if __name__ == "__main__": 108 | opts, args = parse_args() 109 | if opts.download: 110 | localargs = args[-1] 111 | remote = args[0:-1] 112 | else: 113 | localargs = args[0:-1] 114 | remote = args[-1] 115 | try: 116 | hosts = psshutil.read_host_files(opts.host_files, opts.host_glob, 117 | default_user=opts.user) 118 | except IOError: 119 | _, e, _ = sys.exc_info() 120 | sys.stderr.write('Could not open hosts file: %s\n' % e.strerror) 121 | sys.exit(1) 122 | if opts.host_strings: 123 | for s in opts.host_strings: 124 | hosts.extend(psshutil.parse_host_string(s, default_user=opts.user)) 125 | do_pscp(hosts, localargs, remote, opts) 126 | -------------------------------------------------------------------------------- /bin/pslurp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- Mode: python -*- 3 | 4 | # Copyright (c) 2009-2012, Andrew McNabb 5 | # Copyright (c) 2003-2008, Brent N. Chun 6 | 7 | """Parallel scp from the set of nodes in hosts.txt. 8 | 9 | For each node, we essentially do a scp [-r] user@host:remote 10 | outdir//local. This program also uses the -q (quiet) and -C 11 | (compression) options. 12 | """ 13 | 14 | import os 15 | import sys 16 | 17 | parent, bindir = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) 18 | if os.path.exists(os.path.join(parent, 'psshlib')): 19 | sys.path.insert(0, parent) 20 | 21 | from psshlib import psshutil 22 | from psshlib.task import Task 23 | from psshlib.manager import Manager, FatalError 24 | from psshlib.cli import common_parser, common_defaults 25 | 26 | def option_parser(): 27 | parser = common_parser() 28 | parser.usage = "%prog [OPTIONS] remote local" 29 | parser.epilog = ("Example: pslurp -h hosts.txt -L /tmp/outdir -l irb2 " + 30 | " /home/irb2/foo.txt foo.txt") 31 | 32 | parser.add_option('-r', '--recursive', dest='recursive', 33 | action='store_true', help='recusively copy directories (OPTIONAL)') 34 | parser.add_option('-L', '--localdir', dest='localdir', 35 | help='output directory for remote file copies') 36 | 37 | return parser 38 | 39 | def parse_args(): 40 | parser = option_parser() 41 | defaults = common_defaults() 42 | parser.set_defaults(**defaults) 43 | opts, args = parser.parse_args() 44 | 45 | if len(args) < 1: 46 | parser.error('Paths not specified.') 47 | 48 | if len(args) < 2: 49 | parser.error('Local path not specified.') 50 | 51 | if len(args) > 2: 52 | parser.error('Extra arguments given after the local path.') 53 | 54 | if not opts.host_files and not opts.host_strings: 55 | parser.error('Hosts not specified.') 56 | 57 | return opts, args 58 | 59 | def do_pslurp(hosts, remote, local, opts): 60 | if opts.localdir and not os.path.exists(opts.localdir): 61 | os.makedirs(opts.localdir) 62 | if opts.outdir and not os.path.exists(opts.outdir): 63 | os.makedirs(opts.outdir) 64 | if opts.errdir and not os.path.exists(opts.errdir): 65 | os.makedirs(opts.errdir) 66 | for host, port, user in hosts: 67 | if opts.localdir: 68 | dirname = "%s/%s:%s" % (opts.localdir, host, port) 69 | else: 70 | dirname = '%s:%s' % (host, port) 71 | if not os.path.exists(dirname): 72 | os.mkdir(dirname) 73 | manager = Manager(opts) 74 | for host, port, user in hosts: 75 | if opts.localdir: 76 | localpath = "%s/%s:%s/%s" % (opts.localdir, host, port, local) 77 | else: 78 | localpath = "%s:%s/%s" % (host, port, local) 79 | cmd = ['scp', '-qC'] 80 | if opts.options: 81 | for opt in opts.options: 82 | cmd += ['-o', opt] 83 | if port: 84 | cmd += ['-P', port] 85 | if opts.recursive: 86 | cmd.append('-r') 87 | if opts.extra: 88 | cmd.extend(opts.extra) 89 | if user: 90 | cmd.append('%s@%s:%s' % (user, host, remote)) 91 | else: 92 | cmd.append('%s:%s' % (host, remote)) 93 | cmd.append(localpath) 94 | t = Task(host, port, user, cmd, opts) 95 | manager.add_task(t) 96 | try: 97 | statuses = manager.run() 98 | except FatalError: 99 | sys.exit(1) 100 | 101 | if statuses and min(statuses) < 0: 102 | # At least one process was killed. 103 | sys.exit(3) 104 | for status in statuses: 105 | if status == 255: 106 | sys.exit(4) 107 | for status in statuses: 108 | if status != 0: 109 | sys.exit(5) 110 | 111 | if __name__ == "__main__": 112 | opts, args = parse_args() 113 | remote = args[0] 114 | local = args[1] 115 | try: 116 | hosts = psshutil.read_host_files(opts.host_files, opts.host_glob, 117 | default_user=opts.user) 118 | except IOError: 119 | _, e, _ = sys.exc_info() 120 | sys.stderr.write('Could not open hosts file: %s\n' % e.strerror) 121 | sys.exit(1) 122 | if opts.host_strings: 123 | for s in opts.host_strings: 124 | hosts.extend(psshutil.parse_host_string(s, default_user=opts.user)) 125 | do_pslurp(hosts, remote, local, opts) 126 | -------------------------------------------------------------------------------- /bin/pssh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- Mode: python -*- 3 | 4 | # Copyright (c) 2009-2012, Andrew McNabb 5 | # Copyright (c) 2003-2008, Brent N. Chun 6 | 7 | """Parallel ssh to the set of nodes in hosts.txt. 8 | 9 | For each node, this essentially does an "ssh host -l user prog [arg0] [arg1] 10 | ...". The -o option can be used to store stdout from each remote node in a 11 | directory. Each output file in that directory will be named by the 12 | corresponding remote node's hostname or IP address. 13 | """ 14 | 15 | import os 16 | import sys 17 | 18 | parent, bindir = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) 19 | if os.path.exists(os.path.join(parent, 'psshlib')): 20 | sys.path.insert(0, parent) 21 | 22 | from psshlib import psshutil 23 | from psshlib.manager import Manager, FatalError 24 | from psshlib.task import Task 25 | from psshlib.cli import common_parser, common_defaults 26 | 27 | _DEFAULT_TIMEOUT = 60 28 | 29 | def option_parser(): 30 | parser = common_parser() 31 | parser.usage = "%prog [OPTIONS] command [...]" 32 | parser.epilog = "Example: pssh -h hosts.txt -l irb2 -o /tmp/foo uptime" 33 | 34 | parser.add_option('-i', '--inline', dest='inline', action='store_true', 35 | help='inline aggregated output and error for each server') 36 | parser.add_option('--inline-stdout', dest='inline_stdout', 37 | action='store_true', 38 | help='inline standard output for each server') 39 | parser.add_option('-I', '--send-input', dest='send_input', 40 | action='store_true', 41 | help='read from standard input and send as input to ssh') 42 | parser.add_option('-P', '--print', dest='print_out', action='store_true', 43 | help='print output as we get it') 44 | parser.add_option('--no-headers', dest='noheaders', action='store_true', 45 | help="Don't print headers with --inline or --inline-stdout") 46 | 47 | return parser 48 | 49 | def parse_args(): 50 | parser = option_parser() 51 | defaults = common_defaults(timeout=_DEFAULT_TIMEOUT) 52 | parser.set_defaults(**defaults) 53 | opts, args = parser.parse_args() 54 | 55 | if len(args) == 0 and not opts.send_input: 56 | parser.error('Command not specified.') 57 | 58 | if not opts.host_files and not opts.host_strings: 59 | parser.error('Hosts not specified.') 60 | 61 | return opts, args 62 | 63 | def do_pssh(hosts, cmdline, opts): 64 | if opts.outdir and not os.path.exists(opts.outdir): 65 | os.makedirs(opts.outdir) 66 | if opts.errdir and not os.path.exists(opts.errdir): 67 | os.makedirs(opts.errdir) 68 | if opts.send_input: 69 | if hasattr(sys.stdin, 'buffer'): 70 | stdin = sys.stdin.buffer.read() 71 | else: 72 | stdin = sys.stdin.read() 73 | else: 74 | stdin = None 75 | manager = Manager(opts) 76 | for host, port, user in hosts: 77 | cmd = ['ssh', '-T', host, '-o', 'NumberOfPasswordPrompts=1', '-o', 78 | 'SendEnv=PSSH_NODENUM', '-o', 'SendEnv=PSSH_NUMNODES', '-o', 79 | 'SendEnv=PSSH_HOST'] 80 | if opts.options: 81 | for opt in opts.options: 82 | cmd += ['-o', opt] 83 | if user: 84 | cmd += ['-l', user] 85 | if port: 86 | cmd += ['-p', port] 87 | if opts.extra: 88 | cmd.extend(opts.extra) 89 | if cmdline: 90 | cmd.append(cmdline) 91 | t = Task(host, port, user, cmd, opts, stdin) 92 | manager.add_task(t) 93 | try: 94 | statuses = manager.run() 95 | except FatalError: 96 | sys.exit(1) 97 | 98 | if any(x < 0 for x in statuses if x): 99 | # At least one process was killed. 100 | sys.exit(3) 101 | if any(x == 255 for x in statuses): 102 | sys.exit(4) 103 | if any(x != 0 for x in statuses): 104 | sys.exit(5) 105 | 106 | if __name__ == "__main__": 107 | opts, args = parse_args() 108 | cmdline = " ".join(args) 109 | try: 110 | hosts = psshutil.read_host_files(opts.host_files, opts.host_glob, 111 | default_user=opts.user) 112 | except IOError: 113 | _, e, _ = sys.exc_info() 114 | sys.stderr.write('Could not open hosts file: %s\n' % e.strerror) 115 | sys.exit(1) 116 | if opts.host_strings: 117 | for s in opts.host_strings: 118 | hosts.extend(psshutil.parse_host_string(s, default_user=opts.user)) 119 | do_pssh(hosts, cmdline, opts) 120 | -------------------------------------------------------------------------------- /bin/pssh-askpass: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | parent, bindir = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) 7 | if os.path.exists(os.path.join(parent, 'psshlib')): 8 | sys.path.insert(0, parent) 9 | 10 | from psshlib.askpass_client import askpass_main 11 | askpass_main() 12 | -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | pssh HOWTO

pssh HOWTO

0.1, 2003-11-13

Revision History
Revision 0.12003-11-13
Initial version.

1. Installation and Setup

To install the software, become root on your machine and do 147 | the following (on RedHat systems): 148 |

   # rpm -ivh pssh-0.2.3-1.i386.rpm
160 |    Preparing...                ########################################### [100%]
161 |       1:pssh                   ########################################### [100%]

By default, the software installs itself in /usr/localbin and 171 | /usr/local/lib. Thus, you'll next want to 175 | modify your PATH if needed: 179 |

# export PATH=$PATH:/usr/local/bin

2. Preliminaries

All four programs will print their usage and give an example 204 | if no arguments are given. For example, with pssh: 208 |

   # pssh
220 |    Usage: pssh [OPTIONS] -h hosts.txt prog [arg0] ..
221 |    
222 |      -h --hosts   hosts file (each line "host[:port] [user]")
223 |      -l --user    username (OPTIONAL)
224 |      -p --par     max number of parallel threads (OPTIONAL)
225 |      -o --outdir  output directory for stdout files (OPTIONAL)
226 |      -t --timeout timeout in seconds to do ssh to a host (OPTIONAL)
227 |      -v --verbose turn on warning and diagnostic messages (OPTIONAL)
228 |      -O --options SSH options (OPTIONAL)
229 |    
230 |    Example: pssh -h ips.txt -l irb2 -o /tmp/foo uptime

And for pscp:

   # pscp
251 |    Usage: pscp [OPTIONS] -h hosts.txt local remote
252 |    
253 |      -h --hosts     hosts file (each line "host[:port] [login]")
254 |      -r --recursive recusively copy directories (OPTIONAL)
255 |      -l --user      username (OPTIONAL)
256 |      -p --par       max number of parallel threads (OPTIONAL)
257 |      -t --timeout   timeout in seconds to do scp to a host (OPTIONAL)
258 |      -O --options   SSH options (OPTIONAL)
259 |    
260 |    Example: pscp -h hosts.txt -l irb2 foo.txt /home/irb2/foo.txt

Note that before using any of these tools, you will need to start 268 | ssh-agent! This can be done as follows (substitute zsh 273 | with your particular shell).

   # ssh-agent zsh
285 |    # ssh-add
286 |    Enter passphrase for /x/bnc/.ssh/identity: 

3. Examples


3.2. pscp

Here's an example of using pscp to copy files in parallel to 392 | a set of machines.

   # pscp -h ips.txt -l irb2 /etc/hosts /tmp/hosts
404 |    Success on 128.112.152.122:22
405 |    Success on 18.31.0.190:22
406 |    Success on 128.232.103.201:22

Using the -r option will perform a recursive copy for copying 413 | entire directories.


4. Environment Variables

All four programs take similar sets of options. All of these 477 | options can be set using the following environment variables:

   PSSH_HOSTS
489 |    PSSH_USER
490 |    PSSH_PAR
491 |    PSSH_OUTDIR
492 |    PSSH_VERBOSE
493 |    PSSH_OPTIONS

Here are some example settings:

   # export PSSH_HOSTS="/x/bnc/ips.txt"
511 |    # export PSSH_USER="irb2"
512 |    # export PSSH_PAR="32"
513 |    # export PSSH_OUTDIR="/tmp/bar"
514 |    # export PSSH_VERBOSE="0"
515 |    # export PSSH_OPTIONS="UserKnownHostsFile /tmp/known_hosts"

Using the above settings, the examples can be executed succinctly as:

   # pssh hostname
533 |    Success on 128.112.152.122:22
534 |    Success on 18.31.0.190:22
535 |    Success on 128.232.103.201:22
536 |    
537 |    # ls /tmp/bar 
538 |    128.112.152.122  128.232.103.201  18.31.0.190
539 |    
540 |    # cat /tmp/bar/*
541 |    planetlab-1.cs.princeton.edu
542 |    planetlab1.xeno.cl.cam.ac.uk
543 |    planetlab1.lcs.mit.edu
544 |    
545 |    # pscp /etc/hosts /tmp/hosts
546 |    Success on 128.112.152.122:22
547 |    Success on 18.31.0.190:22
548 |    Success on 128.232.103.201:22
549 |    
550 |    # pnuke java   
551 |    Success on 128.112.152.122:22
552 |    Success on 18.31.0.190:22
553 |    Success on 128.232.103.201:22

5. Feedback

Send me email if you're having problems, find bugs, or have any random 567 | comments: Brent Chun 572 | (bnc at theether.org). Thus 573 | far, I've primarily been testing this software on PlanetLab.

-------------------------------------------------------------------------------- /doc/pssh-HOWTO.html: -------------------------------------------------------------------------------- 1 | pssh HOWTO

pssh HOWTO

0.1, 2003-11-13

Revision History
Revision 0.12003-11-13
Initial version.

1. Installation and Setup

To install the software, become root on your machine and do 147 | the following (on RedHat systems): 148 |

   # rpm -ivh pssh-0.2.3-1.i386.rpm
160 |    Preparing...                ########################################### [100%]
161 |       1:pssh                   ########################################### [100%]

By default, the software installs itself in /usr/localbin and 171 | /usr/local/lib. Thus, you'll next want to 175 | modify your PATH if needed: 179 |

# export PATH=$PATH:/usr/local/bin

2. Preliminaries

All four programs will print their usage and give an example 204 | if no arguments are given. For example, with pssh: 208 |

   # pssh
220 |    Usage: pssh [OPTIONS] -h hosts.txt prog [arg0] ..
221 |    
222 |      -h --hosts   hosts file (each line "host[:port] [user]")
223 |      -l --user    username (OPTIONAL)
224 |      -p --par     max number of parallel threads (OPTIONAL)
225 |      -o --outdir  output directory for stdout files (OPTIONAL)
226 |      -t --timeout timeout in seconds to do ssh to a host (OPTIONAL)
227 |      -v --verbose turn on warning and diagnostic messages (OPTIONAL)
228 |      -O --options SSH options (OPTIONAL)
229 |    
230 |    Example: pssh -h ips.txt -l irb2 -o /tmp/foo uptime

And for pscp:

   # pscp
251 |    Usage: pscp [OPTIONS] -h hosts.txt local remote
252 |    
253 |      -h --hosts     hosts file (each line "host[:port] [login]")
254 |      -r --recursive recusively copy directories (OPTIONAL)
255 |      -l --user      username (OPTIONAL)
256 |      -p --par       max number of parallel threads (OPTIONAL)
257 |      -t --timeout   timeout in seconds to do scp to a host (OPTIONAL)
258 |      -O --options   SSH options (OPTIONAL)
259 |    
260 |    Example: pscp -h hosts.txt -l irb2 foo.txt /home/irb2/foo.txt

Note that before using any of these tools, you will need to start 268 | ssh-agent! This can be done as follows (substitute zsh 273 | with your particular shell).

   # ssh-agent zsh
285 |    # ssh-add
286 |    Enter passphrase for /x/bnc/.ssh/identity: 

3. Examples


3.2. pscp

Here's an example of using pscp to copy files in parallel to 392 | a set of machines.

   # pscp -h ips.txt -l irb2 /etc/hosts /tmp/hosts
404 |    Success on 128.112.152.122:22
405 |    Success on 18.31.0.190:22
406 |    Success on 128.232.103.201:22

Using the -r option will perform a recursive copy for copying 413 | entire directories.


4. Environment Variables

All four programs take similar sets of options. All of these 477 | options can be set using the following environment variables:

   PSSH_HOSTS
489 |    PSSH_USER
490 |    PSSH_PAR
491 |    PSSH_OUTDIR
492 |    PSSH_VERBOSE
493 |    PSSH_OPTIONS

Here are some example settings:

   # export PSSH_HOSTS="/x/bnc/ips.txt"
511 |    # export PSSH_USER="irb2"
512 |    # export PSSH_PAR="32"
513 |    # export PSSH_OUTDIR="/tmp/bar"
514 |    # export PSSH_VERBOSE="0"
515 |    # export PSSH_OPTIONS="UserKnownHostsFile /tmp/known_hosts"

Using the above settings, the examples can be executed succinctly as:

   # pssh hostname
533 |    Success on 128.112.152.122:22
534 |    Success on 18.31.0.190:22
535 |    Success on 128.232.103.201:22
536 |    
537 |    # ls /tmp/bar 
538 |    128.112.152.122  128.232.103.201  18.31.0.190
539 |    
540 |    # cat /tmp/bar/*
541 |    planetlab-1.cs.princeton.edu
542 |    planetlab1.xeno.cl.cam.ac.uk
543 |    planetlab1.lcs.mit.edu
544 |    
545 |    # pscp /etc/hosts /tmp/hosts
546 |    Success on 128.112.152.122:22
547 |    Success on 18.31.0.190:22
548 |    Success on 128.232.103.201:22
549 |    
550 |    # pnuke java   
551 |    Success on 128.112.152.122:22
552 |    Success on 18.31.0.190:22
553 |    Success on 128.232.103.201:22

5. Feedback

Send me email if you're having problems, find bugs, or have any random 567 | comments: Brent Chun 572 | (bnc at theether.org). Thus 573 | far, I've primarily been testing this software on PlanetLab.

-------------------------------------------------------------------------------- /doc/pssh-HOWTO.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lilydjwg/pssh/89c5dab4581a84d319f9265b82ce9f118cb3e923/doc/pssh-HOWTO.pdf -------------------------------------------------------------------------------- /doc/pssh-HOWTO.ps: -------------------------------------------------------------------------------- 1 | %!PS-Adobe-2.0 2 | %%Creator: dvips(k) 5.86 Copyright 1999 Radical Eye Software 3 | %%Title: pssh-HOWTO.dvi 4 | %%Pages: 5 5 | %%PageOrder: Ascend 6 | %%BoundingBox: 0 0 596 842 7 | %%DocumentFonts: Helvetica-Bold Times-Bold Times-Roman Courier 8 | %%+ Times-Italic 9 | %%EndComments 10 | %DVIPSWebPage: (www.radicaleye.com) 11 | %DVIPSCommandLine: dvips -q pssh-HOWTO.dvi -o pssh-HOWTO.ps 12 | %DVIPSParameters: dpi=600, compressed 13 | %DVIPSSource: TeX output 2003.11.13:1201 14 | %%BeginProcSet: texc.pro 15 | %! 16 | /TeXDict 300 dict def TeXDict begin/N{def}def/B{bind def}N/S{exch}N/X{S 17 | N}B/A{dup}B/TR{translate}N/isls false N/vsize 11 72 mul N/hsize 8.5 72 18 | mul N/landplus90{false}def/@rigin{isls{[0 landplus90{1 -1}{-1 1}ifelse 0 19 | 0 0]concat}if 72 Resolution div 72 VResolution div neg scale isls{ 20 | landplus90{VResolution 72 div vsize mul 0 exch}{Resolution -72 div hsize 21 | mul 0}ifelse TR}if Resolution VResolution vsize -72 div 1 add mul TR[ 22 | matrix currentmatrix{A A round sub abs 0.00001 lt{round}if}forall round 23 | exch round exch]setmatrix}N/@landscape{/isls true N}B/@manualfeed{ 24 | statusdict/manualfeed true put}B/@copies{/#copies X}B/FMat[1 0 0 -1 0 0] 25 | N/FBB[0 0 0 0]N/nn 0 N/IEn 0 N/ctr 0 N/df-tail{/nn 8 dict N nn begin 26 | /FontType 3 N/FontMatrix fntrx N/FontBBox FBB N string/base X array 27 | /BitMaps X/BuildChar{CharBuilder}N/Encoding IEn N end A{/foo setfont}2 28 | array copy cvx N load 0 nn put/ctr 0 N[}B/sf 0 N/df{/sf 1 N/fntrx FMat N 29 | df-tail}B/dfs{div/sf X/fntrx[sf 0 0 sf neg 0 0]N df-tail}B/E{pop nn A 30 | definefont setfont}B/Cw{Cd A length 5 sub get}B/Ch{Cd A length 4 sub get 31 | }B/Cx{128 Cd A length 3 sub get sub}B/Cy{Cd A length 2 sub get 127 sub} 32 | B/Cdx{Cd A length 1 sub get}B/Ci{Cd A type/stringtype ne{ctr get/ctr ctr 33 | 1 add N}if}B/id 0 N/rw 0 N/rc 0 N/gp 0 N/cp 0 N/G 0 N/CharBuilder{save 3 34 | 1 roll S A/base get 2 index get S/BitMaps get S get/Cd X pop/ctr 0 N Cdx 35 | 0 Cx Cy Ch sub Cx Cw add Cy setcachedevice Cw Ch true[1 0 0 -1 -.1 Cx 36 | sub Cy .1 sub]/id Ci N/rw Cw 7 add 8 idiv string N/rc 0 N/gp 0 N/cp 0 N{ 37 | rc 0 ne{rc 1 sub/rc X rw}{G}ifelse}imagemask restore}B/G{{id gp get/gp 38 | gp 1 add N A 18 mod S 18 idiv pl S get exec}loop}B/adv{cp add/cp X}B 39 | /chg{rw cp id gp 4 index getinterval putinterval A gp add/gp X adv}B/nd{ 40 | /cp 0 N rw exit}B/lsh{rw cp 2 copy get A 0 eq{pop 1}{A 255 eq{pop 254}{ 41 | A A add 255 and S 1 and or}ifelse}ifelse put 1 adv}B/rsh{rw cp 2 copy 42 | get A 0 eq{pop 128}{A 255 eq{pop 127}{A 2 idiv S 128 and or}ifelse} 43 | ifelse put 1 adv}B/clr{rw cp 2 index string putinterval adv}B/set{rw cp 44 | fillstr 0 4 index getinterval putinterval adv}B/fillstr 18 string 0 1 17 45 | {2 copy 255 put pop}for N/pl[{adv 1 chg}{adv 1 chg nd}{1 add chg}{1 add 46 | chg nd}{adv lsh}{adv lsh nd}{adv rsh}{adv rsh nd}{1 add adv}{/rc X nd}{ 47 | 1 add set}{1 add clr}{adv 2 chg}{adv 2 chg nd}{pop nd}]A{bind pop} 48 | forall N/D{/cc X A type/stringtype ne{]}if nn/base get cc ctr put nn 49 | /BitMaps get S ctr S sf 1 ne{A A length 1 sub A 2 index S get sf div put 50 | }if put/ctr ctr 1 add N}B/I{cc 1 add D}B/bop{userdict/bop-hook known{ 51 | bop-hook}if/SI save N @rigin 0 0 moveto/V matrix currentmatrix A 1 get A 52 | mul exch 0 get A mul add .99 lt{/QV}{/RV}ifelse load def pop pop}N/eop{ 53 | SI restore userdict/eop-hook known{eop-hook}if showpage}N/@start{ 54 | userdict/start-hook known{start-hook}if pop/VResolution X/Resolution X 55 | 1000 div/DVImag X/IEn 256 array N 2 string 0 1 255{IEn S A 360 add 36 4 56 | index cvrs cvn put}for pop 65781.76 div/vsize X 65781.76 div/hsize X}N 57 | /p{show}N/RMat[1 0 0 -1 0 0]N/BDot 260 string N/Rx 0 N/Ry 0 N/V{}B/RV/v{ 58 | /Ry X/Rx X V}B statusdict begin/product where{pop false[(Display)(NeXT) 59 | (LaserWriter 16/600)]{A length product length le{A length product exch 0 60 | exch getinterval eq{pop true exit}if}{pop}ifelse}forall}{false}ifelse 61 | end{{gsave TR -.1 .1 TR 1 1 scale Rx Ry false RMat{BDot}imagemask 62 | grestore}}{{gsave TR -.1 .1 TR Rx Ry scale 1 1 false RMat{BDot} 63 | imagemask grestore}}ifelse B/QV{gsave newpath transform round exch round 64 | exch itransform moveto Rx 0 rlineto 0 Ry neg rlineto Rx neg 0 rlineto 65 | fill grestore}B/a{moveto}B/delta 0 N/tail{A/delta X 0 rmoveto}B/M{S p 66 | delta add tail}B/b{S p tail}B/c{-4 M}B/d{-3 M}B/e{-2 M}B/f{-1 M}B/g{0 M} 67 | B/h{1 M}B/i{2 M}B/j{3 M}B/k{4 M}B/w{0 rmoveto}B/l{p -4 w}B/m{p -3 w}B/n{ 68 | p -2 w}B/o{p -1 w}B/q{p 1 w}B/r{p 2 w}B/s{p 3 w}B/t{p 4 w}B/x{0 S 69 | rmoveto}B/y{3 2 roll p a}B/bos{/SS save N}B/eos{SS restore}B end 70 | 71 | %%EndProcSet 72 | %%BeginProcSet: 8r.enc 73 | % @@psencodingfile@{ 74 | % author = "S. Rahtz, P. MacKay, Alan Jeffrey, B. Horn, K. Berry", 75 | % version = "0.6", 76 | % date = "22 June 1996", 77 | % filename = "8r.enc", 78 | % email = "kb@@mail.tug.org", 79 | % address = "135 Center Hill Rd. // Plymouth, MA 02360", 80 | % codetable = "ISO/ASCII", 81 | % checksum = "119 662 4424", 82 | % docstring = "Encoding for TrueType or Type 1 fonts to be used with TeX." 83 | % @} 84 | % 85 | % Idea is to have all the characters normally included in Type 1 fonts 86 | % available for typesetting. This is effectively the characters in Adobe 87 | % Standard Encoding + ISO Latin 1 + extra characters from Lucida. 88 | % 89 | % Character code assignments were made as follows: 90 | % 91 | % (1) the Windows ANSI characters are almost all in their Windows ANSI 92 | % positions, because some Windows users cannot easily reencode the 93 | % fonts, and it makes no difference on other systems. The only Windows 94 | % ANSI characters not available are those that make no sense for 95 | % typesetting -- rubout (127 decimal), nobreakspace (160), softhyphen 96 | % (173). quotesingle and grave are moved just because it's such an 97 | % irritation not having them in TeX positions. 98 | % 99 | % (2) Remaining characters are assigned arbitrarily to the lower part 100 | % of the range, avoiding 0, 10 and 13 in case we meet dumb software. 101 | % 102 | % (3) Y&Y Lucida Bright includes some extra text characters; in the 103 | % hopes that other PostScript fonts, perhaps created for public 104 | % consumption, will include them, they are included starting at 0x12. 105 | % 106 | % (4) Remaining positions left undefined are for use in (hopefully) 107 | % upward-compatible revisions, if someday more characters are generally 108 | % available. 109 | % 110 | % (5) hyphen appears twice for compatibility with both ASCII and Windows. 111 | % 112 | /TeXBase1Encoding [ 113 | % 0x00 (encoded characters from Adobe Standard not in Windows 3.1) 114 | /.notdef /dotaccent /fi /fl 115 | /fraction /hungarumlaut /Lslash /lslash 116 | /ogonek /ring /.notdef 117 | /breve /minus /.notdef 118 | % These are the only two remaining unencoded characters, so may as 119 | % well include them. 120 | /Zcaron /zcaron 121 | % 0x10 122 | /caron /dotlessi 123 | % (unusual TeX characters available in, e.g., Lucida Bright) 124 | /dotlessj /ff /ffi /ffl 125 | /.notdef /.notdef /.notdef /.notdef 126 | /.notdef /.notdef /.notdef /.notdef 127 | % very contentious; it's so painful not having quoteleft and quoteright 128 | % at 96 and 145 that we move the things normally found there down to here. 129 | /grave /quotesingle 130 | % 0x20 (ASCII begins) 131 | /space /exclam /quotedbl /numbersign 132 | /dollar /percent /ampersand /quoteright 133 | /parenleft /parenright /asterisk /plus /comma /hyphen /period /slash 134 | % 0x30 135 | /zero /one /two /three /four /five /six /seven 136 | /eight /nine /colon /semicolon /less /equal /greater /question 137 | % 0x40 138 | /at /A /B /C /D /E /F /G /H /I /J /K /L /M /N /O 139 | % 0x50 140 | /P /Q /R /S /T /U /V /W 141 | /X /Y /Z /bracketleft /backslash /bracketright /asciicircum /underscore 142 | % 0x60 143 | /quoteleft /a /b /c /d /e /f /g /h /i /j /k /l /m /n /o 144 | % 0x70 145 | /p /q /r /s /t /u /v /w 146 | /x /y /z /braceleft /bar /braceright /asciitilde 147 | /.notdef % rubout; ASCII ends 148 | % 0x80 149 | /.notdef /.notdef /quotesinglbase /florin 150 | /quotedblbase /ellipsis /dagger /daggerdbl 151 | /circumflex /perthousand /Scaron /guilsinglleft 152 | /OE /.notdef /.notdef /.notdef 153 | % 0x90 154 | /.notdef /.notdef /.notdef /quotedblleft 155 | /quotedblright /bullet /endash /emdash 156 | /tilde /trademark /scaron /guilsinglright 157 | /oe /.notdef /.notdef /Ydieresis 158 | % 0xA0 159 | /.notdef % nobreakspace 160 | /exclamdown /cent /sterling 161 | /currency /yen /brokenbar /section 162 | /dieresis /copyright /ordfeminine /guillemotleft 163 | /logicalnot 164 | /hyphen % Y&Y (also at 45); Windows' softhyphen 165 | /registered 166 | /macron 167 | % 0xD0 168 | /degree /plusminus /twosuperior /threesuperior 169 | /acute /mu /paragraph /periodcentered 170 | /cedilla /onesuperior /ordmasculine /guillemotright 171 | /onequarter /onehalf /threequarters /questiondown 172 | % 0xC0 173 | /Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla 174 | /Egrave /Eacute /Ecircumflex /Edieresis 175 | /Igrave /Iacute /Icircumflex /Idieresis 176 | % 0xD0 177 | /Eth /Ntilde /Ograve /Oacute 178 | /Ocircumflex /Otilde /Odieresis /multiply 179 | /Oslash /Ugrave /Uacute /Ucircumflex 180 | /Udieresis /Yacute /Thorn /germandbls 181 | % 0xE0 182 | /agrave /aacute /acircumflex /atilde 183 | /adieresis /aring /ae /ccedilla 184 | /egrave /eacute /ecircumflex /edieresis 185 | /igrave /iacute /icircumflex /idieresis 186 | % 0xF0 187 | /eth /ntilde /ograve /oacute 188 | /ocircumflex /otilde /odieresis /divide 189 | /oslash /ugrave /uacute /ucircumflex 190 | /udieresis /yacute /thorn /ydieresis 191 | ] def 192 | 193 | %%EndProcSet 194 | %%BeginProcSet: texps.pro 195 | %! 196 | TeXDict begin/rf{findfont dup length 1 add dict begin{1 index/FID ne 2 197 | index/UniqueID ne and{def}{pop pop}ifelse}forall[1 index 0 6 -1 roll 198 | exec 0 exch 5 -1 roll VResolution Resolution div mul neg 0 0]/Metrics 199 | exch def dict begin Encoding{exch dup type/integertype ne{pop pop 1 sub 200 | dup 0 le{pop}{[}ifelse}{FontMatrix 0 get div Metrics 0 get div def} 201 | ifelse}forall Metrics/Metrics currentdict end def[2 index currentdict 202 | end definefont 3 -1 roll makefont/setfont cvx]cvx def}def/ObliqueSlant{ 203 | dup sin S cos div neg}B/SlantFont{4 index mul add}def/ExtendFont{3 -1 204 | roll mul exch}def/ReEncodeFont{CharStrings rcheck{/Encoding false def 205 | dup[exch{dup CharStrings exch known not{pop/.notdef/Encoding true def} 206 | if}forall Encoding{]exch pop}{cleartomark}ifelse}if/Encoding exch def} 207 | def end 208 | 209 | %%EndProcSet 210 | %%BeginProcSet: special.pro 211 | %! 212 | TeXDict begin/SDict 200 dict N SDict begin/@SpecialDefaults{/hs 612 N 213 | /vs 792 N/ho 0 N/vo 0 N/hsc 1 N/vsc 1 N/ang 0 N/CLIP 0 N/rwiSeen false N 214 | /rhiSeen false N/letter{}N/note{}N/a4{}N/legal{}N}B/@scaleunit 100 N 215 | /@hscale{@scaleunit div/hsc X}B/@vscale{@scaleunit div/vsc X}B/@hsize{ 216 | /hs X/CLIP 1 N}B/@vsize{/vs X/CLIP 1 N}B/@clip{/CLIP 2 N}B/@hoffset{/ho 217 | X}B/@voffset{/vo X}B/@angle{/ang X}B/@rwi{10 div/rwi X/rwiSeen true N}B 218 | /@rhi{10 div/rhi X/rhiSeen true N}B/@llx{/llx X}B/@lly{/lly X}B/@urx{ 219 | /urx X}B/@ury{/ury X}B/magscale true def end/@MacSetUp{userdict/md known 220 | {userdict/md get type/dicttype eq{userdict begin md length 10 add md 221 | maxlength ge{/md md dup length 20 add dict copy def}if end md begin 222 | /letter{}N/note{}N/legal{}N/od{txpose 1 0 mtx defaultmatrix dtransform S 223 | atan/pa X newpath clippath mark{transform{itransform moveto}}{transform{ 224 | itransform lineto}}{6 -2 roll transform 6 -2 roll transform 6 -2 roll 225 | transform{itransform 6 2 roll itransform 6 2 roll itransform 6 2 roll 226 | curveto}}{{closepath}}pathforall newpath counttomark array astore/gc xdf 227 | pop ct 39 0 put 10 fz 0 fs 2 F/|______Courier fnt invertflag{PaintBlack} 228 | if}N/txpose{pxs pys scale ppr aload pop por{noflips{pop S neg S TR pop 1 229 | -1 scale}if xflip yflip and{pop S neg S TR 180 rotate 1 -1 scale ppr 3 230 | get ppr 1 get neg sub neg ppr 2 get ppr 0 get neg sub neg TR}if xflip 231 | yflip not and{pop S neg S TR pop 180 rotate ppr 3 get ppr 1 get neg sub 232 | neg 0 TR}if yflip xflip not and{ppr 1 get neg ppr 0 get neg TR}if}{ 233 | noflips{TR pop pop 270 rotate 1 -1 scale}if xflip yflip and{TR pop pop 234 | 90 rotate 1 -1 scale ppr 3 get ppr 1 get neg sub neg ppr 2 get ppr 0 get 235 | neg sub neg TR}if xflip yflip not and{TR pop pop 90 rotate ppr 3 get ppr 236 | 1 get neg sub neg 0 TR}if yflip xflip not and{TR pop pop 270 rotate ppr 237 | 2 get ppr 0 get neg sub neg 0 S TR}if}ifelse scaleby96{ppr aload pop 4 238 | -1 roll add 2 div 3 1 roll add 2 div 2 copy TR .96 dup scale neg S neg S 239 | TR}if}N/cp{pop pop showpage pm restore}N end}if}if}N/normalscale{ 240 | Resolution 72 div VResolution 72 div neg scale magscale{DVImag dup scale 241 | }if 0 setgray}N/psfts{S 65781.76 div N}N/startTexFig{/psf$SavedState 242 | save N userdict maxlength dict begin/magscale true def normalscale 243 | currentpoint TR/psf$ury psfts/psf$urx psfts/psf$lly psfts/psf$llx psfts 244 | /psf$y psfts/psf$x psfts currentpoint/psf$cy X/psf$cx X/psf$sx psf$x 245 | psf$urx psf$llx sub div N/psf$sy psf$y psf$ury psf$lly sub div N psf$sx 246 | psf$sy scale psf$cx psf$sx div psf$llx sub psf$cy psf$sy div psf$ury sub 247 | TR/showpage{}N/erasepage{}N/copypage{}N/p 3 def @MacSetUp}N/doclip{ 248 | psf$llx psf$lly psf$urx psf$ury currentpoint 6 2 roll newpath 4 copy 4 2 249 | roll moveto 6 -1 roll S lineto S lineto S lineto closepath clip newpath 250 | moveto}N/endTexFig{end psf$SavedState restore}N/@beginspecial{SDict 251 | begin/SpecialSave save N gsave normalscale currentpoint TR 252 | @SpecialDefaults count/ocount X/dcount countdictstack N}N/@setspecial{ 253 | CLIP 1 eq{newpath 0 0 moveto hs 0 rlineto 0 vs rlineto hs neg 0 rlineto 254 | closepath clip}if ho vo TR hsc vsc scale ang rotate rwiSeen{rwi urx llx 255 | sub div rhiSeen{rhi ury lly sub div}{dup}ifelse scale llx neg lly neg TR 256 | }{rhiSeen{rhi ury lly sub div dup scale llx neg lly neg TR}if}ifelse 257 | CLIP 2 eq{newpath llx lly moveto urx lly lineto urx ury lineto llx ury 258 | lineto closepath clip}if/showpage{}N/erasepage{}N/copypage{}N newpath}N 259 | /@endspecial{count ocount sub{pop}repeat countdictstack dcount sub{end} 260 | repeat grestore SpecialSave restore end}N/@defspecial{SDict begin}N 261 | /@fedspecial{end}B/li{lineto}B/rl{rlineto}B/rc{rcurveto}B/np{/SaveX 262 | currentpoint/SaveY X N 1 setlinecap newpath}N/st{stroke SaveX SaveY 263 | moveto}N/fil{fill SaveX SaveY moveto}N/ellipse{/endangle X/startangle X 264 | /yrad X/xrad X/savematrix matrix currentmatrix N TR xrad yrad scale 0 0 265 | 1 startangle endangle arc savematrix setmatrix}N end 266 | 267 | %%EndProcSet 268 | %%BeginProcSet: color.pro 269 | %! 270 | TeXDict begin/setcmykcolor where{pop}{/setcmykcolor{dup 10 eq{pop 271 | setrgbcolor}{1 sub 4 1 roll 3{3 index add neg dup 0 lt{pop 0}if 3 1 roll 272 | }repeat setrgbcolor pop}ifelse}B}ifelse/TeXcolorcmyk{setcmykcolor}def 273 | /TeXcolorrgb{setrgbcolor}def/TeXcolorgrey{setgray}def/TeXcolorgray{ 274 | setgray}def/TeXcolorhsb{sethsbcolor}def/currentcmykcolor where{pop}{ 275 | /currentcmykcolor{currentrgbcolor 10}B}ifelse/DC{exch dup userdict exch 276 | known{pop pop}{X}ifelse}B/GreenYellow{0.15 0 0.69 0 setcmykcolor}DC 277 | /Yellow{0 0 1 0 setcmykcolor}DC/Goldenrod{0 0.10 0.84 0 setcmykcolor}DC 278 | /Dandelion{0 0.29 0.84 0 setcmykcolor}DC/Apricot{0 0.32 0.52 0 279 | setcmykcolor}DC/Peach{0 0.50 0.70 0 setcmykcolor}DC/Melon{0 0.46 0.50 0 280 | setcmykcolor}DC/YellowOrange{0 0.42 1 0 setcmykcolor}DC/Orange{0 0.61 281 | 0.87 0 setcmykcolor}DC/BurntOrange{0 0.51 1 0 setcmykcolor}DC 282 | /Bittersweet{0 0.75 1 0.24 setcmykcolor}DC/RedOrange{0 0.77 0.87 0 283 | setcmykcolor}DC/Mahogany{0 0.85 0.87 0.35 setcmykcolor}DC/Maroon{0 0.87 284 | 0.68 0.32 setcmykcolor}DC/BrickRed{0 0.89 0.94 0.28 setcmykcolor}DC/Red{ 285 | 0 1 1 0 setcmykcolor}DC/OrangeRed{0 1 0.50 0 setcmykcolor}DC/RubineRed{ 286 | 0 1 0.13 0 setcmykcolor}DC/WildStrawberry{0 0.96 0.39 0 setcmykcolor}DC 287 | /Salmon{0 0.53 0.38 0 setcmykcolor}DC/CarnationPink{0 0.63 0 0 288 | setcmykcolor}DC/Magenta{0 1 0 0 setcmykcolor}DC/VioletRed{0 0.81 0 0 289 | setcmykcolor}DC/Rhodamine{0 0.82 0 0 setcmykcolor}DC/Mulberry{0.34 0.90 290 | 0 0.02 setcmykcolor}DC/RedViolet{0.07 0.90 0 0.34 setcmykcolor}DC 291 | /Fuchsia{0.47 0.91 0 0.08 setcmykcolor}DC/Lavender{0 0.48 0 0 292 | setcmykcolor}DC/Thistle{0.12 0.59 0 0 setcmykcolor}DC/Orchid{0.32 0.64 0 293 | 0 setcmykcolor}DC/DarkOrchid{0.40 0.80 0.20 0 setcmykcolor}DC/Purple{ 294 | 0.45 0.86 0 0 setcmykcolor}DC/Plum{0.50 1 0 0 setcmykcolor}DC/Violet{ 295 | 0.79 0.88 0 0 setcmykcolor}DC/RoyalPurple{0.75 0.90 0 0 setcmykcolor}DC 296 | /BlueViolet{0.86 0.91 0 0.04 setcmykcolor}DC/Periwinkle{0.57 0.55 0 0 297 | setcmykcolor}DC/CadetBlue{0.62 0.57 0.23 0 setcmykcolor}DC 298 | /CornflowerBlue{0.65 0.13 0 0 setcmykcolor}DC/MidnightBlue{0.98 0.13 0 299 | 0.43 setcmykcolor}DC/NavyBlue{0.94 0.54 0 0 setcmykcolor}DC/RoyalBlue{1 300 | 0.50 0 0 setcmykcolor}DC/Blue{1 1 0 0 setcmykcolor}DC/Cerulean{0.94 0.11 301 | 0 0 setcmykcolor}DC/Cyan{1 0 0 0 setcmykcolor}DC/ProcessBlue{0.96 0 0 0 302 | setcmykcolor}DC/SkyBlue{0.62 0 0.12 0 setcmykcolor}DC/Turquoise{0.85 0 303 | 0.20 0 setcmykcolor}DC/TealBlue{0.86 0 0.34 0.02 setcmykcolor}DC 304 | /Aquamarine{0.82 0 0.30 0 setcmykcolor}DC/BlueGreen{0.85 0 0.33 0 305 | setcmykcolor}DC/Emerald{1 0 0.50 0 setcmykcolor}DC/JungleGreen{0.99 0 306 | 0.52 0 setcmykcolor}DC/SeaGreen{0.69 0 0.50 0 setcmykcolor}DC/Green{1 0 307 | 1 0 setcmykcolor}DC/ForestGreen{0.91 0 0.88 0.12 setcmykcolor}DC 308 | /PineGreen{0.92 0 0.59 0.25 setcmykcolor}DC/LimeGreen{0.50 0 1 0 309 | setcmykcolor}DC/YellowGreen{0.44 0 0.74 0 setcmykcolor}DC/SpringGreen{ 310 | 0.26 0 0.76 0 setcmykcolor}DC/OliveGreen{0.64 0 0.95 0.40 setcmykcolor} 311 | DC/RawSienna{0 0.72 1 0.45 setcmykcolor}DC/Sepia{0 0.83 1 0.70 312 | setcmykcolor}DC/Brown{0 0.81 1 0.60 setcmykcolor}DC/Tan{0.14 0.42 0.56 0 313 | setcmykcolor}DC/Gray{0 0 0 0.50 setcmykcolor}DC/Black{0 0 0 1 314 | setcmykcolor}DC/White{0 0 0 0 setcmykcolor}DC end 315 | 316 | %%EndProcSet 317 | TeXDict begin 39158280 55380996 1000 600 600 (pssh-HOWTO.dvi) 318 | @start /Fa 138[61 1[55 2[61 1[61 2[55 2[61 2[55 1[55 319 | 47[55 55 55 2[28 46[{TeXBase1Encoding ReEncodeFont}12 320 | 99.6264 /Helvetica-Bold rf /Fb 136[55 2[23 32 32 1[42 321 | 42 42 1[23 2[23 42 42 1[37 42 2[42 9[69 2[46 4[60 6[60 322 | 18[42 42 42 42 4[28 45[{TeXBase1Encoding ReEncodeFont}23 323 | 83.022 /Times-Italic rf /Fc 133[45 45 45 45 45 45 45 324 | 45 45 1[45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 325 | 45 1[45 1[45 1[45 4[45 45 45 45 45 1[45 45 45 1[45 45 326 | 1[45 45 1[45 45 45 1[45 45 3[45 2[45 45 45 1[45 45 1[45 327 | 45 45 45 45 45 45 2[45 45 45 2[45 45 45 45 34[{ 328 | TeXBase1Encoding ReEncodeFont}65 74.7198 /Courier rf 329 | /Fd 135[66 1[66 73 40 66 47 1[73 73 73 106 33 66 1[33 330 | 3[66 73 66 73 66 10[80 2[80 2[80 6[33 2[73 80 15[66 66 331 | 66 66 66 2[33 46[{TeXBase1Encoding ReEncodeFont}30 119.552 332 | /Helvetica-Bold rf /Fe 134[42 42 60 42 42 23 32 28 1[42 333 | 42 42 65 23 42 23 23 42 42 28 37 42 37 42 37 11[60 51 334 | 46 55 1[46 1[60 1[51 2[28 60 1[46 2[55 55 60 6[23 6[42 335 | 42 42 1[23 21 28 21 2[28 28 28 5[28 30[46 2[{ 336 | TeXBase1Encoding ReEncodeFont}50 83.022 /Times-Roman 337 | rf /Ff 135[42 1[42 46 28 32 37 1[46 42 46 69 23 46 1[23 338 | 3[37 46 37 46 42 10[60 2[46 2[51 6[32 2[51 55 15[42 42 339 | 42 42 42 2[21 46[{TeXBase1Encoding ReEncodeFont}30 83.022 340 | /Times-Bold rf /Fg 138[88 48 80 56 2[88 88 1[40 3[88 341 | 1[48 80 2[88 80 12[88 16[104 104 66[{TeXBase1Encoding ReEncodeFont}15 342 | 143.462 /Helvetica-Bold rf /Fh 140[115 2[126 7[126 16[195 343 | 2[126 4[161 6[149 72[{TeXBase1Encoding ReEncodeFont}7 344 | 206.584 /Helvetica-Bold rf end 345 | %%EndProlog 346 | %%BeginSetup 347 | %%Feature: *Resolution 600dpi 348 | TeXDict begin 349 | %%PaperSize: A4 350 | 351 | %%EndSetup 352 | %%Page: 1 1 353 | 1 0 bop Black 0 TeXcolorgray Black Black 1284 140 a Fh(pssh)58 354 | b(HO)-10 b(WT)i(O)1550 416 y Fg(Brent)38 b(Chun)-2 1175 355 | y(T)-11 b(ab)o(le)37 b(of)j(Contents)p 0 TeXcolorgray 356 | -2 1355 a Ff(1.)20 b(Installation)g(and)g(Setup)p Black 357 | Black 4 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 358 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 359 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 360 | Black Black(.)p Black Black(.)p Black Black(.)p Black 361 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 362 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 363 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 364 | Black(.)p Black Black(.)p Black Black(.)p Black Black 365 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 366 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 367 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 368 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 369 | Black Black(.)p Black Black(.)p Black Black(.)p Black 370 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 371 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 372 | Black(.)p Black Black(.)p Black Black(.)p Black Black 373 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 374 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 375 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 376 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 377 | Black Black(.)p Black Black(.)p Black Black(.)p Black 378 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 379 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 380 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 381 | Black(.)p Black Black(.)p Black Black(.)p Black Black 382 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 383 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 384 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 385 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 386 | Black Black(.)p Black Black(.)p Black Black(.)p Black 387 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 388 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 389 | Black(.)p Black Black(.)p Black Black(.)p Black Black 390 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 391 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 392 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 393 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 394 | Black Black(.)p Black Black(.)p Black Black(.)p Black 395 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 396 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 397 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 398 | Black(.)p Black Black(.)p Black Black(.)p Black Black 399 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 400 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 401 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 402 | Black Black(.)p Black Black(.)p Black 0 TeXcolorgray 403 | 4 w(2)p Black 0 TeXcolorgray -2 1483 a(2.)g(Pr)o(eliminaries)p 404 | Black Black 19 w(.)p Black Black(.)p Black Black -1 w(.)p 405 | Black Black(.)p Black Black(.)p Black Black(.)p Black 406 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 407 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 408 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 409 | Black(.)p Black Black(.)p Black Black(.)p Black Black(.)p 410 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 411 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 412 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 413 | Black Black(.)p Black Black(.)p Black Black(.)p Black 414 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 415 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 416 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 417 | Black(.)p Black Black(.)p Black Black(.)p Black Black 418 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 419 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 420 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 421 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 422 | Black Black(.)p Black Black(.)p Black Black(.)p Black 423 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 424 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 425 | Black(.)p Black Black(.)p Black Black(.)p Black Black 426 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 427 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 428 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 429 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 430 | Black Black(.)p Black Black(.)p Black Black(.)p Black 431 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 432 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 433 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 434 | Black(.)p Black Black(.)p Black Black(.)p Black Black 435 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 436 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 437 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 438 | Black Black(.)p Black Black(.)p Black Black(.)p Black 439 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 440 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 441 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 442 | Black(.)p Black Black(.)p Black Black(.)p Black Black 443 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 444 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 445 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 446 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 447 | Black Black(.)p Black Black(.)p Black Black(.)p Black 448 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 449 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 450 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 451 | Black(.)p Black Black(.)p Black Black(.)p Black Black 452 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 453 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 454 | Black Black(.)p Black 0 TeXcolorgray 4 w(2)p Black 0 455 | TeXcolorgray -2 1611 a(3.)g(Examples)p Black Black 7 456 | w(.)p Black Black -1 w(.)p Black Black(.)p Black Black(.)p 457 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 458 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 459 | Black Black(.)p Black Black(.)p Black Black(.)p Black 460 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 461 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 462 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 463 | Black(.)p Black Black(.)p Black Black(.)p Black Black(.)p 464 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 465 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 466 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 467 | Black Black(.)p Black Black(.)p Black Black(.)p Black 468 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 469 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 470 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 471 | Black(.)p Black Black(.)p Black Black(.)p Black Black 472 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 473 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 474 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 475 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 476 | Black Black(.)p Black Black(.)p Black Black(.)p Black 477 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 478 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 479 | Black(.)p Black Black(.)p Black Black(.)p Black Black 480 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 481 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 482 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 483 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 484 | Black Black(.)p Black Black(.)p Black Black(.)p Black 485 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 486 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 487 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 488 | Black(.)p Black Black(.)p Black Black(.)p Black Black 489 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 490 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 491 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 492 | Black Black(.)p Black Black(.)p Black Black(.)p Black 493 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 494 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 495 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 496 | Black(.)p Black Black(.)p Black Black(.)p Black Black 497 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 498 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 499 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 500 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 501 | Black Black(.)p Black Black(.)p Black Black(.)p Black 502 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 503 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 504 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 505 | Black(.)p Black Black(.)p Black Black(.)p Black Black 506 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 507 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 508 | Black Black(.)p Black 0 TeXcolorgray 4 w(2)p Black 0 509 | TeXcolorgray 197 1739 a Fe(3.1.)f(pssh)p Black Black 510 | 6 w(.)p Black Black(.)p Black Black -1 w(.)p Black Black(.)p 511 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 512 | Black Black(.)p Black Black(.)p Black Black(.)p Black 513 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 514 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 515 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 516 | Black(.)p Black Black(.)p Black Black(.)p Black Black(.)p 517 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 518 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 519 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 520 | Black Black(.)p Black Black(.)p Black Black(.)p Black 521 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 522 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 523 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 524 | Black(.)p Black Black(.)p Black Black(.)p Black Black 525 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 526 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 527 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 528 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 529 | Black Black(.)p Black Black(.)p Black Black(.)p Black 530 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 531 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 532 | Black(.)p Black Black(.)p Black Black(.)p Black Black 533 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 534 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 535 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 536 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 537 | Black Black(.)p Black Black(.)p Black Black(.)p Black 538 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 539 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 540 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 541 | Black(.)p Black Black(.)p Black Black(.)p Black Black 542 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 543 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 544 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 545 | Black Black(.)p Black Black(.)p Black Black(.)p Black 546 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 547 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 548 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 549 | Black(.)p Black Black(.)p Black Black(.)p Black Black 550 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 551 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 552 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 553 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 554 | Black Black(.)p Black Black(.)p Black Black(.)p Black 555 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 556 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 557 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 558 | Black(.)p Black Black(.)p Black Black(.)p Black Black 559 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 560 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 561 | Black Black(.)p Black 0 TeXcolorgray 4 w(3)p Black 0 562 | TeXcolorgray 197 1846 a(3.2.)g(pscp)p Black Black 1 w(.)p 563 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 564 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 565 | Black Black(.)p Black Black(.)p Black Black(.)p Black 566 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 567 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 568 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 569 | Black(.)p Black Black(.)p Black Black(.)p Black Black(.)p 570 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 571 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 572 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 573 | Black Black(.)p Black Black(.)p Black Black(.)p Black 574 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 575 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 576 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 577 | Black(.)p Black Black(.)p Black Black(.)p Black Black 578 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 579 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 580 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 581 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 582 | Black Black(.)p Black Black(.)p Black Black(.)p Black 583 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 584 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 585 | Black(.)p Black Black(.)p Black Black(.)p Black Black 586 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 587 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 588 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 589 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 590 | Black Black(.)p Black Black(.)p Black Black(.)p Black 591 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 592 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 593 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 594 | Black(.)p Black Black(.)p Black Black(.)p Black Black 595 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 596 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 597 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 598 | Black Black(.)p Black Black(.)p Black Black(.)p Black 599 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 600 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 601 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 602 | Black(.)p Black Black(.)p Black Black(.)p Black Black 603 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 604 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 605 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 606 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 607 | Black Black(.)p Black Black(.)p Black Black(.)p Black 608 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 609 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 610 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 611 | Black(.)p Black Black(.)p Black Black(.)p Black Black 612 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 613 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 614 | Black Black(.)p Black 0 TeXcolorgray 4 w(3)p Black 0 615 | TeXcolorgray 197 1954 a(3.3.)g(pnuk)o(e)p Black Black 616 | 14 w(.)p Black Black -2 w(.)p Black Black(.)p Black Black 617 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 618 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 619 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 620 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 621 | Black Black(.)p Black Black(.)p Black Black(.)p Black 622 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 623 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 624 | Black(.)p Black Black(.)p Black Black(.)p Black Black 625 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 626 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 627 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 628 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 629 | Black Black(.)p Black Black(.)p Black Black(.)p Black 630 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 631 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 632 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 633 | Black(.)p Black Black(.)p Black Black(.)p Black Black 634 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 635 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 636 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 637 | Black Black(.)p Black Black(.)p Black Black(.)p Black 638 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 639 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 640 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 641 | Black(.)p Black Black(.)p Black Black(.)p Black Black 642 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 643 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 644 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 645 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 646 | Black Black(.)p Black Black(.)p Black Black(.)p Black 647 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 648 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 649 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 650 | Black(.)p Black Black(.)p Black Black(.)p Black Black(.)p 651 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 652 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 653 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 654 | Black Black(.)p Black Black(.)p Black Black(.)p Black 655 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 656 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 657 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 658 | Black(.)p Black Black(.)p Black Black(.)p Black Black 659 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 660 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 661 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 662 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 663 | Black Black(.)p Black Black(.)p Black Black(.)p Black 664 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 665 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 666 | Black(.)p Black Black(.)p Black 0 TeXcolorgray 4 w(3)p 667 | Black 0 TeXcolorgray -2 2082 a Ff(4.)h(En)m(vir)o(onment)f(V)-8 668 | b(ariables)p Black Black 10 w(.)p Black Black(.)p Black 669 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 670 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 671 | Black(.)p Black Black(.)p Black Black(.)p Black Black 672 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 673 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 674 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 675 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 676 | Black Black(.)p Black Black(.)p Black Black(.)p Black 677 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 678 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 679 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 680 | Black(.)p Black Black(.)p Black Black(.)p Black Black 681 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 682 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 683 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 684 | Black Black(.)p Black Black(.)p Black Black(.)p Black 685 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 686 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 687 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 688 | Black(.)p Black Black(.)p Black Black(.)p Black Black 689 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 690 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 691 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 692 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 693 | Black Black(.)p Black Black(.)p Black Black(.)p Black 694 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 695 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 696 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 697 | Black(.)p Black Black(.)p Black Black(.)p Black Black(.)p 698 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 699 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 700 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 701 | Black Black(.)p Black Black(.)p Black Black(.)p Black 702 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 703 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 704 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 705 | Black(.)p Black Black(.)p Black Black(.)p Black Black 706 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 707 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 708 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 709 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 710 | Black Black(.)p Black Black(.)p Black Black(.)p Black 711 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 712 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 713 | Black(.)p Black Black(.)p Black 0 TeXcolorgray 4 w(4)p 714 | Black 0 TeXcolorgray -2 2210 a(5.)20 b(F)n(eedback)p 715 | Black Black 14 w(.)p Black Black -2 w(.)p Black Black(.)p 716 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 717 | Black Black(.)p Black Black(.)p Black Black(.)p Black 718 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 719 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 720 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 721 | Black(.)p Black Black(.)p Black Black(.)p Black Black 722 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 723 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 724 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 725 | Black Black(.)p Black Black(.)p Black Black(.)p Black 726 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 727 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 728 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 729 | Black(.)p Black Black(.)p Black Black(.)p Black Black 730 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 731 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 732 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 733 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 734 | Black Black(.)p Black Black(.)p Black Black(.)p Black 735 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 736 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 737 | Black(.)p Black Black(.)p Black Black(.)p Black Black 738 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 739 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 740 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 741 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 742 | Black Black(.)p Black Black(.)p Black Black(.)p Black 743 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 744 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 745 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 746 | Black(.)p Black Black(.)p Black Black(.)p Black Black 747 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 748 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 749 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 750 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 751 | Black Black(.)p Black Black(.)p Black Black(.)p Black 752 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 753 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 754 | Black(.)p Black Black(.)p Black Black(.)p Black Black 755 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 756 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 757 | Black Black(.)p Black Black -1 w(.)p Black Black(.)p 758 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 759 | Black Black(.)p Black Black(.)p Black Black(.)p Black 760 | Black -1 w(.)p Black Black(.)p Black Black(.)p Black 761 | Black(.)p Black Black -1 w(.)p Black Black(.)p Black 762 | Black(.)p Black Black(.)p Black Black -1 w(.)p Black 763 | Black(.)p Black Black(.)p Black Black(.)p Black Black 764 | -1 w(.)p Black Black(.)p Black Black(.)p Black Black(.)p 765 | Black Black -1 w(.)p Black Black(.)p Black Black(.)p 766 | Black Black(.)p Black Black(.)p Black Black -1 w(.)p 767 | Black Black(.)p Black Black(.)p Black 0 TeXcolorgray 768 | 4 w(5)p Black Black Black eop 769 | %%Page: 2 2 770 | 2 1 bop Black 0 TeXcolorgray Black Black -2 77 a Fd(1.)34 771 | b(Installation)h(and)f(Setup)-2 244 y Fe(T)-7 b(o)20 772 | b(install)h(the)f(softw)o(are,)g(become)f Fc(root)h Fe(on)f(your)g 773 | (machine)g(and)h(do)g(the)g(follo)n(wing)f(\(on)g(RedHat)h(systems\):) 774 | 132 425 y Fc(#)45 b(rpm)f(-ivh)g(pssh-0.2.3-1.i386.rpm)132 775 | 522 y(Preparing...)716 b(####################################)o(######) 776 | o(#)39 b([100\045])267 619 y(1:pssh)851 b 777 | (####################################)o(######)o(#)39 778 | b([100\045])-2 810 y Fe(By)20 b(def)o(ault,)g(the)g(softw)o(are)g 779 | (installs)h(itself)f(in)h Fc(/usr/localbin)d Fe(and)i 780 | Fc(/usr/local/lib)p Fe(.)e(Thus,)i(you')o(ll)f(ne)o(xt)g(w)o(ant)i(to)f 781 | (modify)-2 918 y(your)f Fc(PATH)h Fe(if)g(needed:)-2 782 | 1098 y Fc(#)44 b(export)g(PATH=$PATH:/usr/local/bin)-2 783 | 1509 y Fd(2.)34 b(Preliminaries)-2 1677 y Fe(All)21 b(four)e(programs)f 784 | (will)j(print)f(their)f(usage)h(and)g(gi)n(v)o(e)f(an)h(e)o(xample)f 785 | (if)i(no)e(ar)o(guments)g(are)h(gi)n(v)o(en.)e(F)o(or)i(e)o(xample,)f 786 | (with)h Fc(pssh)p Fe(:)132 1857 y Fc(#)45 b(pssh)132 787 | 1954 y(Usage:)f(pssh)g([OPTIONS])g(-h)g(hosts.txt)f(prog)i([arg0])e(..) 788 | 222 2148 y(-h)h(--hosts)134 b(hosts)44 b(file)g(\(each)g(line)g 789 | ("host[:port])f([user]"\))222 2245 y(-l)h(--user)179 790 | b(username)43 b(\(OPTIONAL\))222 2343 y(-p)h(--par)224 791 | b(max)44 b(number)g(of)g(parallel)g(threads)g(\(OPTIONAL\))222 792 | 2440 y(-o)g(--outdir)89 b(output)44 b(directory)f(for)h(stdout)g(files) 793 | g(\(OPTIONAL\))222 2537 y(-t)g(--timeout)g(timeout)f(in)i(seconds)f(to) 794 | g(do)h(ssh)f(to)g(a)h(host)f(\(OPTIONAL\))222 2634 y(-v)g(--verbose)g 795 | (turn)g(on)g(warning)g(and)g(diagnostic)g(messages)f(\(OPTIONAL\))222 796 | 2731 y(-O)h(--options)g(SSH)g(options)g(\(OPTIONAL\))132 797 | 2925 y(Example:)g(pssh)g(-h)g(ips.txt)g(-l)h(irb2)f(-o)g(/tmp/foo)g 798 | (uptime)-2 3116 y Fe(And)19 b(for)h Fc(pscp)p Fe(:)132 799 | 3296 y Fc(#)45 b(pscp)132 3394 y(Usage:)f(pscp)g([OPTIONS])g(-h)g 800 | (hosts.txt)f(local)h(remote)222 3588 y(-h)g(--hosts)223 801 | b(hosts)44 b(file)h(\(each)f(line)g("host[:port])f([login]"\))222 802 | 3685 y(-r)h(--recursive)f(recusively)h(copy)g(directories)f 803 | (\(OPTIONAL\))222 3782 y(-l)h(--user)268 b(username)44 804 | b(\(OPTIONAL\))222 3879 y(-p)g(--par)313 b(max)45 b(number)f(of)g 805 | (parallel)g(threads)f(\(OPTIONAL\))222 3976 y(-t)h(--timeout)133 806 | b(timeout)44 b(in)h(seconds)e(to)i(do)f(scp)h(to)f(a)h(host)f 807 | (\(OPTIONAL\))222 4074 y(-O)g(--options)133 b(SSH)45 808 | b(options)e(\(OPTIONAL\))132 4268 y(Example:)h(pscp)g(-h)g(hosts.txt)g 809 | (-l)g(irb2)g(foo.txt)g(/home/irb2/foo.txt)-2 4459 y Fe(Note)20 810 | b(that)g(before)f(using)h(an)o(y)f(of)h(these)g(tools,)g(you)g 811 | Fb(will)h(need)e(to)i(start)g(ssh-a)o(g)o(ent)q Fe(!)e(This)i(can)f(be) 812 | g(done)f(as)i(follo)n(ws)f(\(substitute)-2 4567 y Fc(zsh)g 813 | Fe(with)g(your)f(particular)g(shell\).)132 4747 y Fc(#)45 814 | b(ssh-agent)e(zsh)132 4844 y(#)i(ssh-add)132 4941 y(Enter)f(passphrase) 815 | f(for)i(/x/bnc/.ssh/identity:)p Black 3842 5569 a Fb(2)p 816 | Black eop 817 | %%Page: 3 3 818 | 3 2 bop Black 0 TeXcolorgray Black 3425 -132 a Fb(pssh)21 819 | b(HO)l(WT)o(O)p Black -2 74 a Fd(3.)34 b(Examples)-2 820 | 361 y Fa(3.1.)28 b(pssh)-2 519 y Fe(The)20 b(follo)n(wing)e(e)o(xample) 821 | h(runs)h Fc(hostname)f Fe(on)h(three)g(machines)f(\(IPs)h(or)g 822 | (hostnames\))f(speci\002ed)h(in)g(the)h(\002le)g Fc(ips.txt)e 823 | Fe(using)-2 627 y(login)g Fc(irb2)h Fe(and)g(sa)n(v)o(es)h(the)f 824 | (output)f(in)h Fc(/tmp/foo)p Fe(.)132 807 y Fc(#)45 b(cat)f(ips.txt)132 825 | 904 y(128.112.152.122)132 1001 y(18.31.0.190)132 1099 826 | y(128.232.103.201)132 1293 y(#)h(pssh)f(-h)h(ips.txt)e(-l)i(irb2)f(-o)g 827 | (/tmp/foo)g(hostname)132 1390 y(Success)g(on)g(128.112.152.122:22)132 828 | 1487 y(Success)g(on)g(18.31.0.190:22)132 1584 y(Success)g(on)g 829 | (128.232.103.201:22)132 1779 y(#)h(ls)f(/tmp/foo)132 830 | 1876 y(128.112.152.122)87 b(128.232.103.201)h(18.31.0.190)132 831 | 2070 y(#)45 b(cat)f(/tmp/foo/*)132 2167 y(planetlab-1.cs.princeton.edu) 832 | 132 2264 y(planetlab1.xeno.cl.cam.ac.uk)132 2361 y 833 | (planetlab1.lcs.mit.edu)-2 2552 y Fe(By)20 b(def)o(ault,)g 834 | Fc(pssh)g Fe(uses)g(at)h(most)f(32)g(ssh)h(processes)f(in)g(parallel)g 835 | (to)g(ssh)h(to)g(the)f(v)n(arious)f(nodes.)g(\(This)h(is)h(some)n(what) 836 | e(important)g(if)-2 2660 y(you')l(re)f(controlling)g(hundreds)g(or)i 837 | (thousands)f(of)h(machines.\))f(By)h(def)o(ault,)g(it)g(also)h(uses)g 838 | (a)f(timeout)g(of)g(one)f(minute)h(to)g(ssh)h(to)f(a)-2 839 | 2768 y(node)f(and)h(obtain)f(a)h(result.)g(F)o(or)g(ssh)h(commands)e 840 | (that)h(tak)o(e)g(longer)f(than)h(this)g(\(e.g.,)g Fc(sleep)43 841 | b(61)p Fe(\),)20 b(the)h(-t)f(option)f(can)h(be)g(used.)-2 842 | 2876 y(Note)g(that)g Fc(pssh)g Fe(and)g Fc(pnuke)g Fe(ha)n(v)o(e)f(a)i 843 | (def)o(ault)f(timeout)f(of)h(one)g(minute.)f Fc(pscp)h 844 | Fe(and)f Fc(prsync)h Fe(ha)n(v)o(e)g(no)g(def)o(ault)f(timeout,)g(b)n 845 | (ut)h(one)-2 2984 y(can)g(be)g(speci\002ed)g(using)f(the)i(-t)f 846 | (option.)-2 3321 y Fa(3.2.)28 b(pscp)-2 3479 y Fe(Here')-5 847 | b(s)20 b(an)g(e)o(xample)f(of)h(using)g Fc(pscp)g Fe(to)g(cop)o(y)f 848 | (\002les)j(in)e(parallel)g(to)g(a)h(set)g(of)f(machines.)132 849 | 3659 y Fc(#)45 b(pscp)f(-h)h(ips.txt)e(-l)i(irb2)f(/etc/hosts)f 850 | (/tmp/hosts)132 3756 y(Success)h(on)g(128.112.152.122:22)132 851 | 3853 y(Success)g(on)g(18.31.0.190:22)132 3950 y(Success)g(on)g 852 | (128.232.103.201:22)-2 4141 y Fe(Using)20 b(the)g(-r)g(option)f(will)i 853 | (perform)d(a)j(recursi)n(v)o(e)d(cop)o(y)i(for)f(cop)o(ying)g(entire)h 854 | (directories.)-2 4478 y Fa(3.3.)28 b(pn)o(uke)-2 4636 855 | y Fe(The)20 b Fc(pnuke)g Fe(command)e(is)j(useful)f(when)f(you)g(w)o 856 | (ant)i(to)f(kill)h(a)f(b)n(unch)f(of)h(processes)g(on)g(a)g(set)h(of)f 857 | (machines.)f(F)o(or)h(e)o(xample,)-2 4744 y(suppose)f(you')l(v)o(e)f 858 | (got)i(a)g(b)n(unch)f(of)h Fc(java)g Fe(processes)g(running)e(on)i 859 | (three)g(nodes)f(that)i(you')l(d)d(lik)o(e)i(to)h(nuk)o(e)e(\(let')-5 860 | b(s)21 b(use)f(the)g(three)-2 4852 y(machines)f(from)g(the)h 861 | Fc(pssh)h Fe(e)o(xample\).)d(Here)i(you)f(w)o(ould)h(do)f(the)i(follo)n 862 | (wing:)132 5032 y Fc(#)45 b(pnuke)f(-h)g(ips.txt)g(-l)h(irb2)f(java)132 863 | 5129 y(Success)g(on)g(128.112.152.122:22)p Black 3842 864 | 5569 a Fb(3)p Black eop 865 | %%Page: 4 4 866 | 4 3 bop Black 0 TeXcolorgray Black 3425 -132 a Fb(pssh)21 867 | b(HO)l(WT)o(O)p Black 132 72 a Fc(Success)44 b(on)g(18.31.0.190:22)132 868 | 170 y(Success)g(on)g(128.232.103.201:22)-2 361 y Fe(The)20 869 | b(result)g(of)g(the)g(abo)o(v)o(e)e(is)k(to)e(send)g 870 | Fc(kill)44 b(-9)20 b Fe(to)h(all)g(processes)e(o)n(wned)g(by)h 871 | Fc(irb2)g Fe(with)h(the)f(string)g(ja)n(v)n(a)g(in)g(their)g(name)g 872 | (\(as)-2 468 y(reported)e(by)i Fc(ps)44 b(-ef)p Fe(\).)-2 873 | 921 y Fd(4.)34 b(En)-5 b(vir)n(onment)33 b(V)-7 b(ariab)o(les)-2 874 | 1089 y Fe(All)21 b(four)e(programs)f(tak)o(e)i(similar)h(sets)g(of)f 875 | (options.)f(All)i(of)f(these)g(options)f(can)h(be)g(set)h(using)f(the)g 876 | (follo)n(wing)f(en)m(vironment)-2 1197 y(v)n(ariables:)132 877 | 1377 y Fc(PSSH_HOSTS)132 1474 y(PSSH_USER)132 1571 y(PSSH_PAR)132 878 | 1668 y(PSSH_OUTDIR)132 1765 y(PSSH_VERBOSE)132 1863 y(PSSH_OPTIONS)-2 879 | 2054 y Fe(Here)h(are)g(some)g(e)o(xample)f(settings:)132 880 | 2234 y Fc(#)45 b(export)f(PSSH_HOSTS="/x/bnc/ips.txt")132 881 | 2331 y(#)h(export)f(PSSH_USER="irb2")132 2428 y(#)h(export)f 882 | (PSSH_PAR="32")132 2525 y(#)h(export)f(PSSH_OUTDIR="/tmp/bar")132 883 | 2622 y(#)h(export)f(PSSH_VERBOSE="0")132 2719 y(#)h(export)f 884 | (PSSH_OPTIONS="UserKnownHostsFile)39 b(/tmp/known_hosts")-2 885 | 2910 y Fe(Using)20 b(the)g(abo)o(v)o(e)f(settings,)h(the)g(e)o(xamples) 886 | f(can)h(be)g(e)o(x)o(ecuted)f(succinctly)g(as:)132 3090 887 | y Fc(#)45 b(pssh)f(hostname)132 3188 y(Success)g(on)g 888 | (128.112.152.122:22)132 3285 y(Success)g(on)g(18.31.0.190:22)132 889 | 3382 y(Success)g(on)g(128.232.103.201:22)132 3576 y(#)h(ls)f(/tmp/bar) 890 | 132 3673 y(128.112.152.122)87 b(128.232.103.201)h(18.31.0.190)132 891 | 3868 y(#)45 b(cat)f(/tmp/bar/*)132 3965 y(planetlab-1.cs.princeton.edu) 892 | 132 4062 y(planetlab1.xeno.cl.cam.ac.uk)132 4159 y 893 | (planetlab1.lcs.mit.edu)132 4353 y(#)h(pscp)f(/etc/hosts)f(/tmp/hosts) 894 | 132 4450 y(Success)h(on)g(128.112.152.122:22)132 4548 895 | y(Success)g(on)g(18.31.0.190:22)132 4645 y(Success)g(on)g 896 | (128.232.103.201:22)132 4839 y(#)h(pnuke)f(java)132 4936 897 | y(Success)g(on)g(128.112.152.122:22)132 5033 y(Success)g(on)g 898 | (18.31.0.190:22)132 5130 y(Success)g(on)g(128.232.103.201:22)p 899 | Black 3842 5569 a Fb(4)p Black eop 900 | %%Page: 5 5 901 | 5 4 bop Black 0 TeXcolorgray Black 3425 -132 a Fb(pssh)21 902 | b(HO)l(WT)o(O)p Black -2 74 a Fd(5.)34 b(Feedbac)n(k)-2 903 | 242 y Fe(Send)20 b(me)g(email)g(if)g(you')l(re)f(ha)n(ving)g(problems,) 904 | f(\002nd)i(b)n(ugs,)g(or)g(ha)n(v)o(e)f(an)o(y)h(random)e(comments:)h 905 | (Brent)i(Chun)-2 350 y(\(http://www)-5 b(.theether)g(.or)o(g\))15 906 | b(\(bnc)k(at)i(theether)-5 b(.or)o(g\).)17 b(Thus)j(f)o(ar)m(,)f(I')l 907 | (v)o(e)g(primarily)g(been)h(testing)g(this)h(softw)o(are)e(on)h 908 | (PlanetLab)-2 458 y(\(http://www)-5 b(.planet-lab)m(.or)o(g\))o(.)p 909 | Black 3842 5569 a Fb(5)p Black eop 910 | %%Trailer 911 | end 912 | userdict /end-hook known{end-hook}if 913 | %%EOF 914 | -------------------------------------------------------------------------------- /doc/pssh-HOWTO.sgml: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | pssh HOWTO 5 | 6 | 0.1, 2003-11-13 7 | 8 | 9 | 10 | Brent 11 | Chun 12 | 13 | 14 | 15 | 16 | 17 | 0.1 18 | 2003-11-13 19 | Initial version. 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Installation and Setup 31 | 32 | 33 | To install the software, become root on your machine and do 34 | the following (on RedHat systems): 35 | 36 | 37 | 38 | # rpm -ivh pssh-0.2.3-1.i386.rpm 39 | Preparing... ########################################### [100%] 40 | 1:pssh ########################################### [100%] 41 | 42 | 43 | By default, the software installs itself in /usr/localbin and 44 | /usr/local/lib. Thus, you'll next want to 45 | modify your PATH if needed: 46 | 47 | 48 | 49 | # export PATH=$PATH:/usr/local/bin 50 | 51 | 52 | 53 | 54 | Preliminaries 55 | 56 | 57 | All four programs will print their usage and give an example 58 | if no arguments are given. For example, with pssh: 59 | 60 | 61 | 62 | # pssh 63 | Usage: pssh [OPTIONS] -h hosts.txt prog [arg0] .. 64 | 65 | -h --hosts hosts file (each line "host[:port] [user]") 66 | -l --user username (OPTIONAL) 67 | -p --par max number of parallel threads (OPTIONAL) 68 | -o --outdir output directory for stdout files (OPTIONAL) 69 | -t --timeout timeout in seconds to do ssh to a host (OPTIONAL) 70 | -v --verbose turn on warning and diagnostic messages (OPTIONAL) 71 | -O --options SSH options (OPTIONAL) 72 | 73 | Example: pssh -h ips.txt -l irb2 -o /tmp/foo uptime 74 | 75 | 76 | And for pscp: 77 | 78 | 79 | 80 | # pscp 81 | Usage: pscp [OPTIONS] -h hosts.txt local remote 82 | 83 | -h --hosts hosts file (each line "host[:port] [login]") 84 | -r --recursive recusively copy directories (OPTIONAL) 85 | -l --user username (OPTIONAL) 86 | -p --par max number of parallel threads (OPTIONAL) 87 | -t --timeout timeout in seconds to do scp to a host (OPTIONAL) 88 | -O --options SSH options (OPTIONAL) 89 | 90 | Example: pscp -h hosts.txt -l irb2 foo.txt /home/irb2/foo.txt 91 | 92 | 93 | Note that before using any of these tools, you will need to start 94 | ssh-agent! This can be done as follows (substitute zsh 95 | with your particular shell). 96 | 97 | 98 | 99 | # ssh-agent zsh 100 | # ssh-add 101 | Enter passphrase for /x/bnc/.ssh/identity: 102 | 103 | 104 | 105 | 106 | Examples 107 | 108 | 109 | pssh 110 | 111 | 112 | The following example runs hostname on three machines (IPs 113 | or hostnames) specified in the file ips.txt using login 114 | irb2 and saves the output in /tmp/foo. 115 | 116 | 117 | 118 | # cat ips.txt 119 | 128.112.152.122 120 | 18.31.0.190 121 | 128.232.103.201 122 | 123 | # pssh -h ips.txt -l irb2 -o /tmp/foo hostname 124 | Success on 128.112.152.122:22 125 | Success on 18.31.0.190:22 126 | Success on 128.232.103.201:22 127 | 128 | # ls /tmp/foo 129 | 128.112.152.122 128.232.103.201 18.31.0.190 130 | 131 | # cat /tmp/foo/* 132 | planetlab-1.cs.princeton.edu 133 | planetlab1.xeno.cl.cam.ac.uk 134 | planetlab1.lcs.mit.edu 135 | 136 | 137 | By default, pssh uses at most 32 ssh processes in parallel to 138 | ssh to the various nodes. (This is somewhat important if you're 139 | controlling hundreds or thousands of machines.) By default, it also 140 | uses a timeout of one minute to ssh to a node and obtain a result. 141 | For ssh commands that take longer than this (e.g., sleep 61), 142 | the -t option can be used. Note that pssh and 143 | pnuke 144 | have a default timeout of one minute. 145 | pscp and prsync have no default timeout, but 146 | one can be specified using the -t option. 147 | 148 | 149 | 150 | 151 | 152 | pscp 153 | 154 | Here's an example of using pscp to copy files in parallel to 155 | a set of machines. 156 | 157 | 158 | 159 | # pscp -h ips.txt -l irb2 /etc/hosts /tmp/hosts 160 | Success on 128.112.152.122:22 161 | Success on 18.31.0.190:22 162 | Success on 128.232.103.201:22 163 | 164 | 165 | Using the -r option will perform a recursive copy for copying 166 | entire directories. 167 | 168 | 169 | 170 | 171 | 172 | pnuke 173 | 174 | 175 | The pnuke command is useful when you want to kill a bunch of 176 | processes on a set of machines. For example, suppose you've got a 177 | bunch of java processes running on three nodes that 178 | you'd like to nuke (let's use the three machines from the pssh 179 | example). Here you would do the following: 180 | 181 | 182 | 183 | # pnuke -h ips.txt -l irb2 java 184 | Success on 128.112.152.122:22 185 | Success on 18.31.0.190:22 186 | Success on 128.232.103.201:22 187 | 188 | 189 | The result of the above is to send kill -9 to all processes 190 | owned by irb2 with the string java in their name (as reported 191 | by ps -ef). 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | Environment Variables 200 | 201 | All four programs take similar sets of options. All of these 202 | options can be set using the following environment variables: 203 | 204 | 205 | 206 | PSSH_HOSTS 207 | PSSH_USER 208 | PSSH_PAR 209 | PSSH_OUTDIR 210 | PSSH_VERBOSE 211 | PSSH_OPTIONS 212 | 213 | 214 | Here are some example settings: 215 | 216 | 217 | 218 | # export PSSH_HOSTS="/x/bnc/ips.txt" 219 | # export PSSH_USER="irb2" 220 | # export PSSH_PAR="32" 221 | # export PSSH_OUTDIR="/tmp/bar" 222 | # export PSSH_VERBOSE="0" 223 | # export PSSH_OPTIONS="UserKnownHostsFile /tmp/known_hosts" 224 | 225 | 226 | Using the above settings, the examples can be executed succinctly as: 227 | 228 | 229 | 230 | # pssh hostname 231 | Success on 128.112.152.122:22 232 | Success on 18.31.0.190:22 233 | Success on 128.232.103.201:22 234 | 235 | # ls /tmp/bar 236 | 128.112.152.122 128.232.103.201 18.31.0.190 237 | 238 | # cat /tmp/bar/* 239 | planetlab-1.cs.princeton.edu 240 | planetlab1.xeno.cl.cam.ac.uk 241 | planetlab1.lcs.mit.edu 242 | 243 | # pscp /etc/hosts /tmp/hosts 244 | Success on 128.112.152.122:22 245 | Success on 18.31.0.190:22 246 | Success on 128.232.103.201:22 247 | 248 | # pnuke java 249 | Success on 128.112.152.122:22 250 | Success on 18.31.0.190:22 251 | Success on 128.232.103.201:22 252 | 253 | 254 | 255 | Feedback 256 | 257 | Send me email if you're having problems, find bugs, or have any random 258 | comments: Brent Chun 259 | (bnc at theether.org). Thus 260 | far, I've primarily been testing this software on PlanetLab. 262 | 263 | 264 | 265 | 266 |
-------------------------------------------------------------------------------- /man/man1/pnuke.1: -------------------------------------------------------------------------------- 1 | .\" Man page for pssh. See "man 7 man" and "man man-pages" for formatting info. 2 | .TH pnuke 1 "January 24, 2012" 3 | 4 | .SH NAME 5 | pnuke \(em parallel process kill program 6 | 7 | 8 | .SH SYNOPSIS 9 | .B pnuke 10 | .RB [ \-vA ] 11 | .RB [ \-h 12 | .IR hosts_file ] 13 | .RB [ \-H 14 | .RI [ user @] host [: port ]] 15 | .RB [ \-l 16 | .IR user ] 17 | .RB [ \-p 18 | .IR par ] 19 | .RB [ \-o 20 | .IR outdir ] 21 | .RB [ \-e 22 | .IR errdir ] 23 | .RB [ \-t 24 | .IR timeout ] 25 | .RB [ \-O 26 | .IR options ] 27 | .RB [ \-x 28 | .IR args ] 29 | .RB [ \-X 30 | .IR arg ] 31 | .I pattern 32 | 33 | 34 | .SH DESCRIPTION 35 | .PP 36 | .B pnuke 37 | is a program for killing processes in parallel on a number of hosts. It 38 | provides features such as passing a password to ssh, saving output to files, 39 | and timing out. 40 | 41 | 42 | .SH OPTIONS 43 | 44 | .TP 45 | .BI \-h " host_file" 46 | .PD 0 47 | .TP 48 | .BI \-\-hosts " host_file" 49 | Read hosts from the given 50 | .IR host_file . 51 | Lines in the host file are of the form 52 | .RI [ user @] host [: port ] 53 | and can include blank lines and comments (lines beginning with "#"). 54 | If multiple host files are given (the 55 | .B \-h 56 | option is used more than once), then pnuke behaves as though these files 57 | were concatenated together. 58 | If a host is specified multiple times, then pnuke will connect the 59 | given number of times. 60 | 61 | .TP 62 | .B \-H 63 | .RI [ user @] host [: port ] 64 | .PD 0 65 | .TP 66 | .B \-\-host 67 | .RI [ user @] host [: port ] 68 | .PD 0 69 | .TP 70 | .B \-H 71 | .RI \(dq[ user @] host [: port ] 72 | [ 73 | .RI [ user @] host [: port 74 | ] ... ]\(dq 75 | .PD 0 76 | .TP 77 | .B \-\-host 78 | .RI \(dq[ user @] host [: port ] 79 | [ 80 | .RI [ user @] host [: port 81 | ] ... ]\(dq 82 | .PD 0 83 | .IP 84 | Add the given host strings to the list of hosts. This option may be given 85 | multiple times, and may be used in conjunction with the 86 | .B \-h 87 | option. 88 | 89 | .TP 90 | .BI \-l " user" 91 | .PD 0 92 | .TP 93 | .BI \-\-user " user" 94 | Use the given username as the default for any host entries that don't 95 | specifically specify a user. 96 | 97 | .TP 98 | .BI \-p " parallelism" 99 | .PD 0 100 | .TP 101 | .BI \-\-par " parallelism" 102 | Use the given number as the maximum number of concurrent connections. 103 | 104 | .TP 105 | .BI \-t " timeout" 106 | .PD 0 107 | .TP 108 | .BI \-\-timeout " timeout" 109 | Make connections time out after the given number of seconds. With a value 110 | of 0, pnuke will not timeout any connections. 111 | 112 | .TP 113 | .BI \-o " outdir" 114 | .PD 0 115 | .TP 116 | .BI \-\-outdir " outdir" 117 | Save standard output to files in the given directory. Filenames are of the 118 | form 119 | .RI [ user @] host [: port ][. num ] 120 | where the user and port are only included for hosts that explicitly 121 | specify them. The number is a counter that is incremented each time for hosts 122 | that are specified more than once. 123 | 124 | .TP 125 | .BI \-e " errdir" 126 | .PD 0 127 | .TP 128 | .BI \-\-errdir " errdir" 129 | Save standard error to files in the given directory. Filenames are of the 130 | same form as with the 131 | .B \-o 132 | option. 133 | 134 | .TP 135 | .BI \-x " args" 136 | .PD 0 137 | .TP 138 | .BI \-\-extra-args " args" 139 | Passes extra SSH command-line arguments (see the 140 | .BR ssh (1) 141 | man page for more information about SSH arguments). 142 | This option may be specified multiple times. 143 | The arguments are processed to split on whitespace, protect text within 144 | quotes, and escape with backslashes. 145 | To pass arguments without such processing, use the 146 | .B \-X 147 | option instead. 148 | 149 | .TP 150 | .BI \-X " arg" 151 | .PD 0 152 | .TP 153 | .BI \-\-extra-arg " arg" 154 | Passes a single SSH command-line argument (see the 155 | .BR ssh (1) 156 | man page for more information about SSH arguments). Unlike the 157 | .B \-x 158 | option, no processing is performed on the argument, including word splitting. 159 | To pass multiple command-line arguments, use the option once for each 160 | argument. 161 | 162 | .TP 163 | .BI \-O " options" 164 | .PD 0 165 | .TP 166 | .BI \-\-options " options" 167 | SSH options in the format used in the SSH configuration file (see the 168 | .BR ssh_config (5) 169 | man page for more information). This option may be specified multiple 170 | times. 171 | 172 | .TP 173 | .B \-A 174 | .PD 0 175 | .TP 176 | .B \-\-askpass 177 | Prompt for a password and pass it to ssh. The password may be used for 178 | either to unlock a key or for password authentication. 179 | The password is transferred in a fairly secure manner (e.g., it will not show 180 | up in argument lists). However, be aware that a root user on your system 181 | could potentially intercept the password. 182 | 183 | .TP 184 | .B \-v 185 | .PD 0 186 | .TP 187 | .B \-\-verbose 188 | Include error messages from ssh with the 189 | .B \-i 190 | and 191 | .B \-e 192 | options. 193 | 194 | 195 | .\" .SH EXAMPLES 196 | 197 | .\" .PP 198 | .\" Connect to host1 and host2, and print "hello, world" from each: 199 | .\" .RS 200 | .\" pssh -i -H "host1 host2" echo "hello, world" 201 | .\" .RE 202 | 203 | 204 | .SH TIPS 205 | 206 | .\" .PP 207 | .\" If you have a set of hosts that you connect to frequently with specific 208 | .\" options, it may be helpful to create an alias such as: 209 | .\" .RS 210 | .\" alias pssh_servers="pssh -h /path/to/server_list.txt -l root -A" 211 | .\" .RE 212 | 213 | .PP 214 | The ssh_config file can include an arbitrary number of Host sections. Each 215 | host entry specifies ssh options which apply only to the given host. Host 216 | definitions can even behave like aliases if the HostName option is included. 217 | This ssh feature, in combination with pssh host files, provides a tremendous 218 | amount of flexibility. 219 | 220 | .PP 221 | Internally uses the 222 | .B pkill 223 | command and sends signal 9 (the unblockable KILL 224 | signal). 225 | 226 | 227 | .SH EXIT STATUS 228 | 229 | .PP 230 | The exit status codes from pnuke are as follows: 231 | 232 | .TP 233 | .B 0 234 | Success 235 | 236 | .TP 237 | .B 1 238 | Miscellaneous error 239 | 240 | .TP 241 | .B 2 242 | Syntax or usage error 243 | 244 | .TP 245 | .B 3 246 | At least one process was killed by a signal or timed out. 247 | 248 | .TP 249 | .B 4 250 | All processes completed, but at least one ssh process reported an error 251 | (exit status 255). 252 | 253 | .TP 254 | .B 5 255 | There were no ssh errors, but at least one remote command had a non-zero exit 256 | status. 257 | 258 | 259 | .SH AUTHORS 260 | .PP 261 | Written by 262 | Brent N. Chun and 263 | Andrew McNabb . 264 | 265 | https://github.com/lilydjwg/pssh 266 | 267 | 268 | .SH SEE ALSO 269 | .BR ssh (1), 270 | .BR ssh_config(5), 271 | .BR pssh (1), 272 | .BR pscp (1), 273 | .BR prsync (1), 274 | .BR pslurp (1), 275 | -------------------------------------------------------------------------------- /man/man1/prsync.1: -------------------------------------------------------------------------------- 1 | .\" Man page for prsync. See "man 7 man" and "man man-pages" for formatting info. 2 | .TH prsync 1 "January 24, 2012" 3 | 4 | .SH NAME 5 | prsync \(em parallel file sync program 6 | 7 | 8 | .SH SYNOPSIS 9 | .B prsync 10 | .RB [ \-vAraz ] 11 | .RB [ \-h 12 | .IR hosts_file ] 13 | .RB [ \-H 14 | .RI [ user @] host [: port ]] 15 | .RB [ \-l 16 | .IR user ] 17 | .RB [ \-p 18 | .IR par ] 19 | .RB [ \-o 20 | .IR outdir ] 21 | .RB [ \-e 22 | .IR errdir ] 23 | .RB [ \-t 24 | .IR timeout ] 25 | .RB [ \-O 26 | .IR options ] 27 | .RB [ \-x 28 | .IR args ] 29 | .RB [ \-X 30 | .IR arg ] 31 | .RB [ \-S 32 | .IR args ] 33 | .I local ... 34 | .I remote 35 | 36 | 37 | .SH DESCRIPTION 38 | .PP 39 | .B prsync 40 | is a program for copying files in parallel to a number of hosts using the 41 | popular 42 | .B rsync 43 | program. It provides features such as passing a password to ssh, 44 | saving output to files, and timing out. 45 | 46 | 47 | .SH OPTIONS 48 | 49 | .TP 50 | .BI \-h " host_file" 51 | .PD 0 52 | .TP 53 | .BI \-\-hosts " host_file" 54 | Read hosts from the given 55 | .IR host_file . 56 | Lines in the host file are of the form 57 | .RI [ user @] host [: port ] 58 | and can include blank lines and comments (lines beginning with "#"). 59 | If multiple host files are given (the 60 | .B \-h 61 | option is used more than once), then prsync behaves as though these files 62 | were concatenated together. 63 | If a host is specified multiple times, then prsync will connect the 64 | given number of times. 65 | 66 | .TP 67 | .B \-H 68 | .RI [ user @] host [: port ] 69 | .PD 0 70 | .TP 71 | .B \-\-host 72 | .RI [ user @] host [: port ] 73 | .PD 0 74 | .TP 75 | .B \-H 76 | .RI \(dq[ user @] host [: port ] 77 | [ 78 | .RI [ user @] host [: port 79 | ] ... ]\(dq 80 | .PD 0 81 | .TP 82 | .B \-\-host 83 | .RI \(dq[ user @] host [: port ] 84 | [ 85 | .RI [ user @] host [: port 86 | ] ... ]\(dq 87 | .PD 0 88 | .IP 89 | Add the given host strings to the list of hosts. This option may be given 90 | multiple times, and may be used in conjunction with the 91 | .B \-h 92 | option. 93 | 94 | .TP 95 | .BI \-l " user" 96 | .PD 0 97 | .TP 98 | .BI \-\-user " user" 99 | Use the given username as the default for any host entries that don't 100 | specifically specify a user. 101 | 102 | .TP 103 | .BI \-p " parallelism" 104 | .PD 0 105 | .TP 106 | .BI \-\-par " parallelism" 107 | Use the given number as the maximum number of concurrent connections. 108 | 109 | .TP 110 | .BI \-t " timeout" 111 | .PD 0 112 | .TP 113 | .BI \-\-timeout " timeout" 114 | Make connections time out after the given number of seconds. With a value 115 | of 0, prsync will not timeout any connections. 116 | 117 | .TP 118 | .BI \-o " outdir" 119 | .PD 0 120 | .TP 121 | .BI \-\-outdir " outdir" 122 | Save standard output to files in the given directory. Filenames are of the 123 | form 124 | .RI [ user @] host [: port ][. num ] 125 | where the user and port are only included for hosts that explicitly 126 | specify them. The number is a counter that is incremented each time for hosts 127 | that are specified more than once. 128 | 129 | .TP 130 | .BI \-e " errdir" 131 | .PD 0 132 | .TP 133 | .BI \-\-errdir " errdir" 134 | Save standard error to files in the given directory. Filenames are of the 135 | same form as with the 136 | .B \-o 137 | option. 138 | 139 | .TP 140 | .BI \-x " args" 141 | .PD 0 142 | .TP 143 | .BI \-\-extra-args " args" 144 | Passes extra rsync command-line arguments (see the 145 | .BR rsync (1) 146 | man page for more information about rsync arguments). 147 | This option may be specified multiple times. 148 | The arguments are processed to split on whitespace, protect text within 149 | quotes, and escape with backslashes. 150 | To pass arguments without such processing, use the 151 | .B \-X 152 | option instead. 153 | 154 | .TP 155 | .BI \-X " arg" 156 | .PD 0 157 | .TP 158 | .BI \-\-extra-arg " arg" 159 | Passes a single rsync command-line argument (see the 160 | .BR rsync (1) 161 | man page for more information about rsync arguments). Unlike the 162 | .B \-x 163 | option, no processing is performed on the argument, including word splitting. 164 | To pass multiple command-line arguments, use the option once for each 165 | argument. 166 | 167 | .TP 168 | .BI \-O " options" 169 | .PD 0 170 | .TP 171 | .BI \-\-options " options" 172 | SSH options in the format used in the SSH configuration file (see the 173 | .BR ssh_config (5) 174 | man page for more information). This option may be specified multiple 175 | times. 176 | 177 | .TP 178 | .B \-A 179 | .PD 0 180 | .TP 181 | .B \-\-askpass 182 | Prompt for a password and pass it to ssh. The password may be used for 183 | either to unlock a key or for password authentication. 184 | The password is transferred in a fairly secure manner (e.g., it will not show 185 | up in argument lists). However, be aware that a root user on your system 186 | could potentially intercept the password. 187 | 188 | .TP 189 | .B \-v 190 | .PD 0 191 | .TP 192 | .B \-\-verbose 193 | Include error messages from rsync with the 194 | .B \-i 195 | and 196 | .B \-e 197 | options. 198 | 199 | .TP 200 | .B \-r 201 | .PD 0 202 | .TP 203 | .B \-\-recursive 204 | Recursively copy directories. 205 | 206 | .TP 207 | .B \-a 208 | .PD 0 209 | .TP 210 | .B \-\-archive 211 | Use rsync archive mode (rsync's \-a option). 212 | 213 | .TP 214 | .B \-z 215 | .PD 0 216 | .TP 217 | .B \-\-compress 218 | Use rsync compression. 219 | 220 | .TP 221 | .BI \-S " args" 222 | .PD 0 223 | .TP 224 | .BI \-\-ssh-args " args" 225 | Passes extra SSH command-line arguments (see the 226 | .BR ssh (1) 227 | man page for more information about SSH arguments). 228 | The given value is appended to the ssh command (rsync's \-e option) without 229 | any processing. 230 | 231 | 232 | .\" .SH EXAMPLES 233 | 234 | .\" .PP 235 | .\" Connect to host1 and host2, and print "hello, world" from each: 236 | .\" .RS 237 | .\" pssh -i -H "host1 host2" echo "hello, world" 238 | .\" .RE 239 | 240 | 241 | .SH TIPS 242 | 243 | .\" .PP 244 | .\" If you have a set of hosts that you connect to frequently with specific 245 | .\" options, it may be helpful to create an alias such as: 246 | .\" .RS 247 | .\" alias pssh_servers="pssh -h /path/to/server_list.txt -l root -A" 248 | .\" .RE 249 | 250 | .PP 251 | The ssh_config file can include an arbitrary number of Host sections. Each 252 | host entry specifies ssh options which apply only to the given host. Host 253 | definitions can even behave like aliases if the HostName option is included. 254 | This ssh feature, in combination with pssh host files, provides a tremendous 255 | amount of flexibility. 256 | 257 | 258 | .SH EXIT STATUS 259 | 260 | .PP 261 | The exit status codes from prsync are as follows: 262 | 263 | .TP 264 | .B 0 265 | Success 266 | 267 | .TP 268 | .B 1 269 | Miscellaneous error 270 | 271 | .TP 272 | .B 2 273 | Syntax or usage error 274 | 275 | .TP 276 | .B 3 277 | At least one process was killed by a signal or timed out. 278 | 279 | .TP 280 | .B 4 281 | All processes completed, but at least one rsync process reported an error 282 | (exit status other than 0). 283 | 284 | 285 | .SH AUTHORS 286 | .PP 287 | Written by 288 | Brent N. Chun and 289 | Andrew McNabb . 290 | 291 | https://github.com/lilydjwg/pssh 292 | 293 | 294 | .SH SEE ALSO 295 | .BR rsync (1), 296 | .BR ssh (1), 297 | .BR ssh_config(5), 298 | .BR pssh (1), 299 | .BR prsync (1), 300 | .BR pslurp (1), 301 | .BR pnuke (1), 302 | -------------------------------------------------------------------------------- /man/man1/pscp.1: -------------------------------------------------------------------------------- 1 | .\" Man page for pscp. See "man 7 man" and "man man-pages" for formatting info. 2 | .TH pscp 1 "January 24, 2012" 3 | 4 | .SH NAME 5 | pscp \(em parallel file upload program 6 | 7 | 8 | .SH SYNOPSIS 9 | .B pscp 10 | .RB [ \-vAr ] 11 | .RB [ \-h 12 | .IR hosts_file ] 13 | .RB [ \-H 14 | .RI [ user @] host [: port ]] 15 | .RB [ \-l 16 | .IR user ] 17 | .RB [ \-p 18 | .IR par ] 19 | .RB [ \-o 20 | .IR outdir ] 21 | .RB [ \-e 22 | .IR errdir ] 23 | .RB [ \-t 24 | .IR timeout ] 25 | .RB [ \-O 26 | .IR options ] 27 | .RB [ \-x 28 | .IR args ] 29 | .RB [ \-X 30 | .IR arg ] 31 | .I source 32 | .I dest 33 | 34 | 35 | .SH DESCRIPTION 36 | .PP 37 | .B pscp 38 | is a program for copying files in parallel to a number of hosts using the 39 | .B scp 40 | program. It provides features such as passing a password to scp, saving output 41 | to files, and timing out. 42 | 43 | 44 | .SH OPTIONS 45 | 46 | .TP 47 | .BI \-h " host_file" 48 | .PD 0 49 | .TP 50 | .BI \-\-hosts " host_file" 51 | Read hosts from the given 52 | .IR host_file . 53 | Lines in the host file are of the form 54 | .RI [ user @] host [: port ] 55 | and can include blank lines and comments (lines beginning with "#"). 56 | If multiple host files are given (the 57 | .B \-h 58 | option is used more than once), then pscp behaves as though these files 59 | were concatenated together. 60 | If a host is specified multiple times, then pscp will connect the 61 | given number of times. 62 | 63 | .TP 64 | .B \-H 65 | .RI [ user @] host [: port ] 66 | .PD 0 67 | .TP 68 | .B \-\-host 69 | .RI [ user @] host [: port ] 70 | .PD 0 71 | .TP 72 | .B \-H 73 | .RI \(dq[ user @] host [: port ] 74 | [ 75 | .RI [ user @] host [: port 76 | ] ... ]\(dq 77 | .PD 0 78 | .TP 79 | .B \-\-host 80 | .RI \(dq[ user @] host [: port ] 81 | [ 82 | .RI [ user @] host [: port 83 | ] ... ]\(dq 84 | .PD 0 85 | .IP 86 | Add the given host strings to the list of hosts. This option may be given 87 | multiple times, and may be used in conjunction with the 88 | .B \-h 89 | option. 90 | 91 | .TP 92 | .BI \-l " user" 93 | .PD 0 94 | .TP 95 | .BI \-\-user " user" 96 | Use the given username as the default for any host entries that don't 97 | specifically specify a user. 98 | 99 | .TP 100 | .BI \-p " parallelism" 101 | .PD 0 102 | .TP 103 | .BI \-\-par " parallelism" 104 | Use the given number as the maximum number of concurrent connections. 105 | 106 | .TP 107 | .BI \-t " timeout" 108 | .PD 0 109 | .TP 110 | .BI \-\-timeout " timeout" 111 | Make connections time out after the given number of seconds. With a value 112 | of 0, pscp will not timeout any connections. 113 | 114 | .TP 115 | .BI \-o " outdir" 116 | .PD 0 117 | .TP 118 | .BI \-\-outdir " outdir" 119 | Save standard output to files in the given directory. Filenames are of the 120 | form 121 | .RI [ user @] host [: port ][. num ] 122 | where the user and port are only included for hosts that explicitly 123 | specify them. The number is a counter that is incremented each time for hosts 124 | that are specified more than once. 125 | 126 | .TP 127 | .BI \-e " errdir" 128 | .PD 0 129 | .TP 130 | .BI \-\-errdir " errdir" 131 | Save standard error to files in the given directory. Filenames are of the 132 | same form as with the 133 | .B \-o 134 | option. 135 | 136 | .TP 137 | .BI \-x " args" 138 | .PD 0 139 | .TP 140 | .BI \-\-extra-args " args" 141 | Passes extra SSH command-line arguments (see the 142 | .BR ssh (1) 143 | man page for more information about SSH arguments). 144 | This option may be specified multiple times. 145 | The arguments are processed to split on whitespace, protect text within 146 | quotes, and escape with backslashes. 147 | To pass arguments without such processing, use the 148 | .B \-X 149 | option instead. 150 | 151 | .TP 152 | .BI \-X " arg" 153 | .PD 0 154 | .TP 155 | .BI \-\-extra-arg " arg" 156 | Passes a single SSH command-line argument (see the 157 | .BR ssh (1) 158 | man page for more information about SSH arguments). Unlike the 159 | .B \-x 160 | option, no processing is performed on the argument, including word splitting. 161 | To pass multiple command-line arguments, use the option once for each 162 | argument. 163 | 164 | .TP 165 | .BI \-O " options" 166 | .PD 0 167 | .TP 168 | .BI \-\-options " options" 169 | SSH options in the format used in the SSH configuration file (see the 170 | .BR ssh_config (5) 171 | man page for more information). This option may be specified multiple 172 | times. 173 | 174 | .TP 175 | .B \-A 176 | .PD 0 177 | .TP 178 | .B \-\-askpass 179 | Prompt for a password and pass it to ssh. The password may be used for 180 | either to unlock a key or for password authentication. 181 | The password is transferred in a fairly secure manner (e.g., it will not show 182 | up in argument lists). However, be aware that a root user on your system 183 | could potentially intercept the password. 184 | 185 | .TP 186 | .B \-v 187 | .PD 0 188 | .TP 189 | .B \-\-verbose 190 | Include error messages from ssh with the 191 | .B \-i 192 | and 193 | .B \-e 194 | options. 195 | 196 | .TP 197 | .B \-r 198 | .PD 0 199 | .TP 200 | .B \-\-recursive 201 | Recursively copy directories. 202 | 203 | 204 | .TP 205 | .B \-\-download 206 | Download from remote instead of upload. The downloaded files will be placed 207 | inside a directory named after host. 208 | 209 | .\" .SH EXAMPLES 210 | 211 | .\" .PP 212 | .\" Connect to host1 and host2, and print "hello, world" from each: 213 | .\" .RS 214 | .\" pssh -i -H "host1 host2" echo "hello, world" 215 | .\" .RE 216 | 217 | 218 | .SH TIPS 219 | 220 | .\" .PP 221 | .\" If you have a set of hosts that you connect to frequently with specific 222 | .\" options, it may be helpful to create an alias such as: 223 | .\" .RS 224 | .\" alias pssh_servers="pssh -h /path/to/server_list.txt -l root -A" 225 | .\" .RE 226 | 227 | .PP 228 | The ssh_config file can include an arbitrary number of Host sections. Each 229 | host entry specifies ssh options which apply only to the given host. Host 230 | definitions can even behave like aliases if the HostName option is included. 231 | This ssh feature, in combination with pssh host files, provides a tremendous 232 | amount of flexibility. 233 | 234 | 235 | .SH EXIT STATUS 236 | 237 | .PP 238 | The exit status codes from pscp are as follows: 239 | 240 | .TP 241 | .B 0 242 | Success 243 | 244 | .TP 245 | .B 1 246 | Miscellaneous error 247 | 248 | .TP 249 | .B 2 250 | Syntax or usage error 251 | 252 | .TP 253 | .B 3 254 | At least one process was killed by a signal or timed out. 255 | 256 | .TP 257 | .B 4 258 | All processes completed, but at least one scp process reported an error 259 | (exit status other than 0). 260 | 261 | 262 | .SH AUTHORS 263 | .PP 264 | Written by 265 | Brent N. Chun and 266 | Andrew McNabb . 267 | 268 | https://github.com/lilydjwg/pssh 269 | 270 | 271 | .SH SEE ALSO 272 | .BR ssh (1), 273 | .BR ssh_config(5), 274 | .BR pssh (1), 275 | .BR prsync (1), 276 | .BR pslurp (1), 277 | .BR pnuke (1), 278 | -------------------------------------------------------------------------------- /man/man1/pslurp.1: -------------------------------------------------------------------------------- 1 | .\" Man page for pslurp. See "man 7 man" and "man man-pages" for formatting info. 2 | .TH pslurp 1 "January 24, 2012" 3 | 4 | .SH NAME 5 | pslurp \(em parallel file download program 6 | 7 | 8 | .SH SYNOPSIS 9 | .B pslurp 10 | .RB [ \-vAr ] 11 | .RB [ \-h 12 | .IR hosts_file ] 13 | .RB [ \-H 14 | .RI [ user @] host [: port ]] 15 | .RB [ \-l 16 | .IR user ] 17 | .RB [ \-p 18 | .IR par ] 19 | .RB [ \-o 20 | .IR outdir ] 21 | .RB [ \-e 22 | .IR errdir ] 23 | .RB [ \-t 24 | .IR timeout ] 25 | .RB [ \-O 26 | .IR options ] 27 | .RB [ \-x 28 | .IR args ] 29 | .RB [ \-X 30 | .IR arg ] 31 | .RB [ \-L 32 | .IR localdir ] 33 | .I remote 34 | .I local 35 | 36 | 37 | .SH DESCRIPTION 38 | .PP 39 | .B pslurp 40 | is a program for copying files in parallel from a number of hosts using the 41 | .B scp 42 | program. It provides features such as passing a password to scp, saving output 43 | to files, and timing out. 44 | 45 | 46 | .SH OPTIONS 47 | 48 | .TP 49 | .BI \-h " host_file" 50 | .PD 0 51 | .TP 52 | .BI \-\-hosts " host_file" 53 | Read hosts from the given 54 | .IR host_file . 55 | Lines in the host file are of the form 56 | .RI [ user @] host [: port ] 57 | and can include blank lines and comments (lines beginning with "#"). 58 | If multiple host files are given (the 59 | .B \-h 60 | option is used more than once), then pslurp behaves as though these files 61 | were concatenated together. 62 | If a host is specified multiple times, then pslurp will connect the 63 | given number of times. 64 | 65 | .TP 66 | .B \-H 67 | .RI [ user @] host [: port ] 68 | .PD 0 69 | .TP 70 | .B \-\-host 71 | .RI [ user @] host [: port ] 72 | .PD 0 73 | .TP 74 | .B \-H 75 | .RI \(dq[ user @] host [: port ] 76 | [ 77 | .RI [ user @] host [: port 78 | ] ... ]\(dq 79 | .PD 0 80 | .TP 81 | .B \-\-host 82 | .RI \(dq[ user @] host [: port ] 83 | [ 84 | .RI [ user @] host [: port 85 | ] ... ]\(dq 86 | .PD 0 87 | .IP 88 | Add the given host strings to the list of hosts. This option may be given 89 | multiple times, and may be used in conjunction with the 90 | .B \-h 91 | option. 92 | 93 | .TP 94 | .BI \-l " user" 95 | .PD 0 96 | .TP 97 | .BI \-\-user " user" 98 | Use the given username as the default for any host entries that don't 99 | specifically specify a user. 100 | 101 | .TP 102 | .BI \-p " parallelism" 103 | .PD 0 104 | .TP 105 | .BI \-\-par " parallelism" 106 | Use the given number as the maximum number of concurrent connections. 107 | 108 | .TP 109 | .BI \-t " timeout" 110 | .PD 0 111 | .TP 112 | .BI \-\-timeout " timeout" 113 | Make connections time out after the given number of seconds. With a value 114 | of 0, pslurp will not timeout any connections. 115 | 116 | .TP 117 | .BI \-o " outdir" 118 | .PD 0 119 | .TP 120 | .BI \-\-outdir " outdir" 121 | Save standard output to files in the given directory. Filenames are of the 122 | form 123 | .RI [ user @] host [: port ][. num ] 124 | where the user and port are only included for hosts that explicitly 125 | specify them. The number is a counter that is incremented each time for hosts 126 | that are specified more than once. 127 | 128 | .TP 129 | .BI \-e " errdir" 130 | .PD 0 131 | .TP 132 | .BI \-\-errdir " errdir" 133 | Save standard error to files in the given directory. Filenames are of the 134 | same form as with the 135 | .B \-o 136 | option. 137 | 138 | .TP 139 | .BI \-x " args" 140 | .PD 0 141 | .TP 142 | .BI \-\-extra-args " args" 143 | Passes extra SSH command-line arguments (see the 144 | .BR ssh (1) 145 | man page for more information about SSH arguments). 146 | This option may be specified multiple times. 147 | The arguments are processed to split on whitespace, protect text within 148 | quotes, and escape with backslashes. 149 | To pass arguments without such processing, use the 150 | .B \-X 151 | option instead. 152 | 153 | .TP 154 | .BI \-X " arg" 155 | .PD 0 156 | .TP 157 | .BI \-\-extra-arg " arg" 158 | Passes a single SSH command-line argument (see the 159 | .BR ssh (1) 160 | man page for more information about SSH arguments). Unlike the 161 | .B \-x 162 | option, no processing is performed on the argument, including word splitting. 163 | To pass multiple command-line arguments, use the option once for each 164 | argument. 165 | 166 | .TP 167 | .BI \-O " options" 168 | .PD 0 169 | .TP 170 | .BI \-\-options " options" 171 | SSH options in the format used in the SSH configuration file (see the 172 | .BR ssh_config (5) 173 | man page for more information). This option may be specified multiple 174 | times. 175 | 176 | .TP 177 | .B \-A 178 | .PD 0 179 | .TP 180 | .B \-\-askpass 181 | Prompt for a password and pass it to ssh. The password may be used for 182 | either to unlock a key or for password authentication. 183 | The password is transferred in a fairly secure manner (e.g., it will not show 184 | up in argument lists). However, be aware that a root user on your system 185 | could potentially intercept the password. 186 | 187 | .TP 188 | .B \-v 189 | .PD 0 190 | .TP 191 | .B \-\-verbose 192 | Include error messages from ssh with the 193 | .B \-i 194 | and 195 | .B \-e 196 | options. 197 | 198 | .TP 199 | .B \-r 200 | .PD 0 201 | .TP 202 | .B \-\-recursive 203 | Recursively copy directories. 204 | 205 | .TP 206 | .BI \-L " localdir" 207 | .PD 0 208 | .TP 209 | .BI \-\-localdir " localdir" 210 | Copy files from the remote host to the given local directory. 211 | 212 | 213 | .\" .SH EXAMPLES 214 | 215 | .\" .PP 216 | .\" Connect to host1 and host2, and print "hello, world" from each: 217 | .\" .RS 218 | .\" pssh -i -H "host1 host2" echo "hello, world" 219 | .\" .RE 220 | 221 | 222 | .SH TIPS 223 | 224 | .\" .PP 225 | .\" If you have a set of hosts that you connect to frequently with specific 226 | .\" options, it may be helpful to create an alias such as: 227 | .\" .RS 228 | .\" alias pssh_servers="pssh -h /path/to/server_list.txt -l root -A" 229 | .\" .RE 230 | 231 | .PP 232 | The ssh_config file can include an arbitrary number of Host sections. Each 233 | host entry specifies ssh options which apply only to the given host. Host 234 | definitions can even behave like aliases if the HostName option is included. 235 | This ssh feature, in combination with pssh host files, provides a tremendous 236 | amount of flexibility. 237 | 238 | 239 | .SH EXIT STATUS 240 | 241 | .PP 242 | The exit status codes from pslurp are as follows: 243 | 244 | .TP 245 | .B 0 246 | Success 247 | 248 | .TP 249 | .B 1 250 | Miscellaneous error 251 | 252 | .TP 253 | .B 2 254 | Syntax or usage error 255 | 256 | .TP 257 | .B 3 258 | At least one process was killed by a signal or timed out. 259 | 260 | .TP 261 | .B 4 262 | All processes completed, but at least one scp process reported an error 263 | (exit status other than 0). 264 | 265 | 266 | .SH AUTHORS 267 | .PP 268 | Written by 269 | Brent N. Chun and 270 | Andrew McNabb . 271 | 272 | https://github.com/lilydjwg/pssh 273 | 274 | 275 | .SH SEE ALSO 276 | .BR ssh (1), 277 | .BR ssh_config(5), 278 | .BR pssh (1), 279 | .BR pscp (1), 280 | .BR prsync (1), 281 | .BR pnuke (1), 282 | -------------------------------------------------------------------------------- /man/man1/pssh.1: -------------------------------------------------------------------------------- 1 | .\" Man page for pssh. See "man 7 man" and "man man-pages" for formatting info. 2 | .TH pssh 1 "January 24, 2012" 3 | 4 | .SH NAME 5 | pssh \(em parallel ssh program 6 | 7 | 8 | .SH SYNOPSIS 9 | .B pssh 10 | .RB [ \-vAiIP ] 11 | .RB [ \-h 12 | .IR hosts_file ] 13 | .RB [ \-H 14 | .RI [ user @] host [: port ]] 15 | .RB [ \-g 16 | .IR pattern ] 17 | .RB [ \-l 18 | .IR user ] 19 | .RB [ \-p 20 | .IR par ] 21 | .RB [ \-o 22 | .IR outdir ] 23 | .RB [ \-e 24 | .IR errdir ] 25 | .RB [ \-t 26 | .IR timeout ] 27 | .RB [ \-O 28 | .IR options ] 29 | .RB [ \-x 30 | .IR args ] 31 | .RB [ \-X 32 | .IR arg ] 33 | .I command ... 34 | 35 | .B pssh \-I 36 | .RB [ \-vAiIP ] 37 | .RB [ \-h 38 | .IR hosts_file ] 39 | .RB [ \-H 40 | .RI [ user @] host [: port ]] 41 | .RB [ \-g 42 | .IR pattern ] 43 | .RB [ \-l 44 | .IR user ] 45 | .RB [ \-p 46 | .IR par ] 47 | .RB [ \-o 48 | .IR outdir ] 49 | .RB [ \-e 50 | .IR errdir ] 51 | .RB [ \-t 52 | .IR timeout ] 53 | .RB [ \-O 54 | .IR options ] 55 | .RB [ \-x 56 | .IR args ] 57 | .RB [ \-X 58 | .IR arg ] 59 | .RI [ command 60 | .IR ... ] 61 | 62 | 63 | .SH DESCRIPTION 64 | .PP 65 | .B pssh 66 | is a program for executing ssh in parallel on a number of hosts. It provides 67 | features such as sending input to all of the processes, passing a password 68 | to ssh, saving output to files, and timing out. 69 | 70 | The PSSH_NODENUM, PSSH_NUMNODES, PSSH_HOST environment variables are sent to 71 | the remote host. The PSSH_NODENUM variable is assigned a unique number for 72 | each ssh connection, starting with 0 and counting up. The PSSH_NUMNODES 73 | variable is assigned the total number of node being used. The PSSH_HOST 74 | variable is assigned the name of the host as specified in the hosts list. Note 75 | that sshd drops environment variables by default, so sshd_config on the remote 76 | host must include the line: 77 | .RS 78 | AcceptEnv PSSH_NODENUM PSSH_NUMNODES PSSH_HOST 79 | .RE 80 | 81 | .SH OPTIONS 82 | 83 | .TP 84 | .BI \-h " host_file" 85 | .PD 0 86 | .TP 87 | .BI \-\-hosts " host_file" 88 | Read hosts from the given 89 | .IR host_file . 90 | Lines in the host file are of the form 91 | .RI [ user @] host [: port ] 92 | and can include blank lines and comments (lines beginning with "#"). 93 | If multiple host files are given (the 94 | .B \-h 95 | option is used more than once), then pssh behaves as though these files 96 | were concatenated together. 97 | If a host is specified multiple times, then pssh will connect the 98 | given number of times. 99 | 100 | .TP 101 | .B \-H 102 | .RI [ user @] host [: port ] 103 | .PD 0 104 | .TP 105 | .B \-\-host 106 | .RI [ user @] host [: port ] 107 | .PD 0 108 | .TP 109 | .B \-H 110 | .RI \(dq[ user @] host [: port ] 111 | [ 112 | .RI [ user @] host [: port 113 | ] ... ]\(dq 114 | .PD 0 115 | .TP 116 | .B \-\-host 117 | .RI \(dq[ user @] host [: port ] 118 | [ 119 | .RI [ user @] host [: port 120 | ] ... ]\(dq 121 | .PD 0 122 | .IP 123 | Add the given host strings to the list of hosts. This option may be given 124 | multiple times, and may be used in conjunction with the 125 | .B \-h 126 | option. 127 | 128 | .TP 129 | .BI \-g " pattern" 130 | .PD 0 131 | .TP 132 | .BI \-\-host\-glob " pattern" 133 | Filter hosts with glob pattern 134 | .IR pattern . 135 | This uses the same syntax as shell globs. Make sure to quote the pattern to 136 | prevent shell from expanding it. Examples are "*web*" and "company_*". 137 | 138 | .TP 139 | .BI \-l " user" 140 | .PD 0 141 | .TP 142 | .BI \-\-user " user" 143 | Use the given username as the default for any host entries that don't 144 | specifically specify a user. 145 | 146 | .TP 147 | .BI \-p " parallelism" 148 | .PD 0 149 | .TP 150 | .BI \-\-par " parallelism" 151 | Use the given number as the maximum number of concurrent connections. 152 | 153 | .TP 154 | .BI \-t " timeout" 155 | .PD 0 156 | .TP 157 | .BI \-\-timeout " timeout" 158 | Make connections time out after the given number of seconds. With a value 159 | of 0, pssh will not timeout any connections. 160 | 161 | .TP 162 | .BI \-o " outdir" 163 | .PD 0 164 | .TP 165 | .BI \-\-outdir " outdir" 166 | Save standard output to files in the given directory. Filenames are of the 167 | form 168 | .RI [ user @] host [: port ][. num ] 169 | where the user and port are only included for hosts that explicitly 170 | specify them. The number is a counter that is incremented each time for hosts 171 | that are specified more than once. 172 | 173 | .TP 174 | .BI \-e " errdir" 175 | .PD 0 176 | .TP 177 | .BI \-\-errdir " errdir" 178 | Save standard error to files in the given directory. Filenames are of the 179 | same form as with the 180 | .B \-o 181 | option. 182 | 183 | .TP 184 | .BI \-x " args" 185 | .PD 0 186 | .TP 187 | .BI \-\-extra-args " args" 188 | Passes extra SSH command-line arguments (see the 189 | .BR ssh (1) 190 | man page for more information about SSH arguments). 191 | This option may be specified multiple times. 192 | The arguments are processed to split on whitespace, protect text within 193 | quotes, and escape with backslashes. 194 | To pass arguments without such processing, use the 195 | .B \-X 196 | option instead. 197 | 198 | .TP 199 | .BI \-X " arg" 200 | .PD 0 201 | .TP 202 | .BI \-\-extra-arg " arg" 203 | Passes a single SSH command-line argument (see the 204 | .BR ssh (1) 205 | man page for more information about SSH arguments). Unlike the 206 | .B \-x 207 | option, no processing is performed on the argument, including word splitting. 208 | To pass multiple command-line arguments, use the option once for each 209 | argument. 210 | 211 | .TP 212 | .BI \-O " options" 213 | .PD 0 214 | .TP 215 | .BI \-\-options " options" 216 | SSH options in the format used in the SSH configuration file (see the 217 | .BR ssh_config (5) 218 | man page for more information). This option may be specified multiple 219 | times. 220 | 221 | .TP 222 | .B \-A 223 | .PD 0 224 | .TP 225 | .B \-\-askpass 226 | Prompt for a password and pass it to ssh. The password may be used for 227 | either to unlock a key or for password authentication. 228 | The password is transferred in a fairly secure manner (e.g., it will not show 229 | up in argument lists). However, be aware that a root user on your system 230 | could potentially intercept the password. 231 | 232 | .TP 233 | .B \-i 234 | .PD 0 235 | .TP 236 | .B \-\-inline 237 | Display standard output and standard error as each host completes. 238 | 239 | .TP 240 | .B \-\-inline\-stdout 241 | Display standard output (but not standard error) as each host completes. 242 | 243 | .TP 244 | .B \-v 245 | .PD 0 246 | .TP 247 | .B \-\-verbose 248 | Include error messages from ssh with the 249 | .B \-i 250 | and 251 | .B \-e 252 | options. 253 | 254 | .TP 255 | .B \-I 256 | .PD 0 257 | .TP 258 | .B \-\-send-input 259 | Read input and send to each ssh process. Since ssh allows a command script to 260 | be sent on standard input, the 261 | .B \-I 262 | option may be used in lieu of the command argument. 263 | 264 | .TP 265 | .B \-P 266 | .PD 0 267 | .TP 268 | .B \-\-print 269 | Display output as it arrives. This option is of limited usefulness because 270 | output from different hosts are interleaved. 271 | 272 | 273 | .SH EXAMPLES 274 | 275 | .PP 276 | Connect to host1 and host2, and print "hello, world" from each: 277 | .RS 278 | pssh -i -H "host1 host2" echo "hello, world" 279 | .RE 280 | 281 | .PP 282 | Print "hello, world" from each host specified in the file hosts.txt: 283 | .RS 284 | pssh -i -h hosts.txt echo "hello, world" 285 | .RE 286 | 287 | .PP 288 | Run a command as root with a prompt for the root password: 289 | .RS 290 | pssh -i -h hosts.txt -A -l root echo hi 291 | .RE 292 | 293 | .PP 294 | Run a long command without timing out: 295 | .RS 296 | pssh -i -h hosts.txt -t 0 sleep 10000 297 | .RE 298 | 299 | .PP 300 | If the file hosts.txt has a large number of entries, say 100, then the 301 | parallelism option may also be set to 100 to ensure that the commands are run 302 | concurrently: 303 | .RS 304 | pssh -i -h hosts.txt -p 100 -t 0 sleep 10000 305 | .RE 306 | 307 | .PP 308 | Run a command without checking or saving host keys: 309 | .RS 310 | pssh -i -H host1 -H host2 -x "-O StrictHostKeyChecking=no -O UserKnownHostsFile=/dev/null -O GlobalKnownHostsFile=/dev/null" echo hi 311 | .RE 312 | 313 | .PP 314 | Print the node number for each connection (this will print 0, 1, and 2): 315 | .RS 316 | pssh -i -H host1 -H host1 -H host2 'echo $PSSH_NODENUM' 317 | .RE 318 | 319 | .SH TIPS 320 | 321 | .PP 322 | If you have a set of hosts that you connect to frequently with specific 323 | options, it may be helpful to create an alias such as: 324 | .RS 325 | alias pssh_servers="pssh -h /path/to/server_list.txt -l root -A" 326 | .RE 327 | 328 | .PP 329 | Note that when an ssh command is terminated, it does not kill remote processes 330 | (OpenSSH bug #396 has been open since 2002). One workaround is to instruct 331 | ssh to allocate a pseudo-terminal, which makes it behave more like a normal 332 | interactive ssh session. To do this, use pssh's "-x" option to pass "-tt" to 333 | ssh. For example: 334 | .RS 335 | pssh -i -x "-tt" -h hosts.txt -t 10 sleep 1000 336 | .RE 337 | will ensure that all of the sleep commands will terminate (with SIGHUP) after 338 | the 10 second timeout. 339 | 340 | .PP 341 | By default, ssh uses full buffering for non-interactive commands. Line 342 | buffering may be preferrable to full buffering if you intend to look at the 343 | files in an output directory as a command is running. To switch ssh to use 344 | line buffering, use its "-tt" option (which allocates a pseudo-terminal) using 345 | the "-x" option in pssh. 346 | 347 | .PP 348 | The ssh_config file can include an arbitrary number of Host sections. Each 349 | host entry specifies ssh options which apply only to the given host. Host 350 | definitions can even behave like aliases if the HostName option is included. 351 | This ssh feature, in combination with pssh host files, provides a tremendous 352 | amount of flexibility. 353 | 354 | 355 | .SH EXIT STATUS 356 | 357 | .PP 358 | The exit status codes from pssh are as follows: 359 | 360 | .TP 361 | .B 0 362 | Success 363 | 364 | .TP 365 | .B 1 366 | Miscellaneous error 367 | 368 | .TP 369 | .B 2 370 | Syntax or usage error 371 | 372 | .TP 373 | .B 3 374 | At least one process was killed by a signal or timed out. 375 | 376 | .TP 377 | .B 4 378 | All processes completed, but at least one ssh process reported an error 379 | (exit status 255). 380 | 381 | .TP 382 | .B 5 383 | There were no ssh errors, but at least one remote command had a non-zero exit 384 | status. 385 | 386 | 387 | .SH AUTHORS 388 | .PP 389 | Written by 390 | Brent N. Chun and 391 | Andrew McNabb . 392 | 393 | https://github.com/lilydjwg/pssh 394 | 395 | 396 | .SH SEE ALSO 397 | .BR ssh (1), 398 | .BR ssh_config (5), 399 | .BR pscp (1), 400 | .BR prsync (1), 401 | .BR pslurp (1), 402 | .BR pnuke (1), 403 | -------------------------------------------------------------------------------- /psshlib/__init__.py: -------------------------------------------------------------------------------- 1 | from .pardo import pardo, prange, penumerate, pproduct 2 | 3 | __all__ = ['pardo', 'prange', 'penumerate', 'pproduct'] 4 | -------------------------------------------------------------------------------- /psshlib/askpass_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- Mode: python -*- 3 | 4 | # Copyright (c) 2009-2012, Andrew McNabb 5 | 6 | """Implementation of SSH_ASKPASS to get a password to ssh from pssh. 7 | 8 | The password is read from the socket specified by the environment variable 9 | PSSH_ASKPASS_SOCKET. The other end of this socket is pssh. 10 | 11 | The ssh man page discusses SSH_ASKPASS as follows: 12 | If ssh needs a passphrase, it will read the passphrase from the current 13 | terminal if it was run from a terminal. If ssh does not have a terminal 14 | associated with it but DISPLAY and SSH_ASKPASS are set, it will execute 15 | the program specified by SSH_ASKPASS and open an X11 window to read the 16 | passphrase. This is particularly useful when calling ssh from a .xsession 17 | or related script. (Note that on some machines it may be necessary to 18 | redirect the input from /dev/null to make this work.) 19 | """ 20 | 21 | import os 22 | import socket 23 | import sys 24 | import textwrap 25 | 26 | bin_dir = os.path.dirname(os.path.abspath(sys.argv[0])) 27 | askpass_bin_path = os.path.join(bin_dir, 'pssh-askpass') 28 | ASKPASS_PATHS = (askpass_bin_path, 29 | '/usr/libexec/pssh/pssh-askpass', 30 | '/usr/local/libexec/pssh/pssh-askpass', 31 | '/usr/lib/pssh/pssh-askpass', 32 | '/usr/local/lib/pssh/pssh-askpass') 33 | 34 | _executable_path = None 35 | 36 | def executable_path(): 37 | """Determines the value to use for SSH_ASKPASS. 38 | 39 | The value is cached since this may be called many times. 40 | """ 41 | global _executable_path 42 | if _executable_path is None: 43 | for path in ASKPASS_PATHS: 44 | if os.access(path, os.X_OK): 45 | _executable_path = path 46 | break 47 | else: 48 | _executable_path = '' 49 | sys.stderr.write(textwrap.fill("Warning: could not find an" 50 | " executable path for askpass because PSSH was not" 51 | " installed correctly. Password prompts will not work.")) 52 | sys.stderr.write('\n') 53 | return _executable_path 54 | 55 | def askpass_main(): 56 | """Connects to pssh over the socket specified at PSSH_ASKPASS_SOCKET.""" 57 | 58 | verbose = os.getenv('PSSH_ASKPASS_VERBOSE') 59 | 60 | # It's not documented anywhere, as far as I can tell, but ssh may prompt 61 | # for a password or ask a yes/no question. The command-line argument 62 | # specifies what is needed. 63 | if len(sys.argv) > 1: 64 | prompt = sys.argv[1] 65 | if verbose: 66 | sys.stderr.write('pssh-askpass received prompt: "%s"\n' % prompt) 67 | if not (prompt.strip().lower().endswith('password:') or 'enter passphrase for key' in prompt.strip().lower()): 68 | sys.stderr.write(prompt) 69 | sys.stderr.write('\n') 70 | sys.exit(1) 71 | else: 72 | sys.stderr.write('Error: pssh-askpass called without a prompt.\n') 73 | sys.exit(1) 74 | 75 | address = os.getenv('PSSH_ASKPASS_SOCKET') 76 | if not address: 77 | sys.stderr.write(textwrap.fill("pssh error: SSH requested a password." 78 | " Please create SSH keys or use the -A option to provide a" 79 | " password.")) 80 | sys.stderr.write('\n') 81 | sys.exit(1) 82 | 83 | sock = socket.socket(socket.AF_UNIX) 84 | try: 85 | sock.connect(address) 86 | except socket.error: 87 | _, e, _ = sys.exc_info() 88 | message = e.args[1] 89 | sys.stderr.write("Couldn't bind to %s: %s.\n" % (address, message)) 90 | sys.exit(2) 91 | 92 | try: 93 | password = sock.makefile().read() 94 | except socket.error: 95 | sys.stderr.write("Socket error.\n") 96 | sys.exit(3) 97 | 98 | print(password) 99 | 100 | 101 | if __name__ == '__main__': 102 | askpass_main() 103 | -------------------------------------------------------------------------------- /psshlib/askpass_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- Mode: python -*- 3 | 4 | # Copyright (c) 2009-2012, Andrew McNabb 5 | 6 | """Sends the password over a socket to askpass. 7 | """ 8 | 9 | import errno 10 | import getpass 11 | import os 12 | import socket 13 | import sys 14 | import tempfile 15 | import textwrap 16 | 17 | class PasswordServer(object): 18 | """Listens on a UNIX domain socket for password requests.""" 19 | def __init__(self): 20 | self.sock = None 21 | self.tempdir = None 22 | self.address = None 23 | self.socketmap = {} 24 | self.buffermap = {} 25 | 26 | def start(self, iomap, backlog): 27 | """Prompts for the password, creates a socket, and starts listening. 28 | 29 | The specified backlog should be the max number of clients connecting 30 | at once. 31 | """ 32 | message = ('Warning: do not enter your password if anyone else has' 33 | ' superuser privileges or access to your account.') 34 | print(textwrap.fill(message)) 35 | 36 | self.password = getpass.getpass() 37 | 38 | # Note that according to the docs for mkdtemp, "The directory is 39 | # readable, writable, and searchable only by the creating user." 40 | self.tempdir = tempfile.mkdtemp(prefix='pssh.') 41 | self.address = os.path.join(self.tempdir, 'pssh_askpass_socket') 42 | self.sock = socket.socket(socket.AF_UNIX) 43 | self.sock.bind(self.address) 44 | self.sock.listen(backlog) 45 | iomap.register_read(self.sock.fileno(), self.handle_listen) 46 | 47 | def handle_listen(self, fd, iomap): 48 | try: 49 | conn = self.sock.accept()[0] 50 | except socket.error: 51 | _, e, _ = sys.exc_info() 52 | number = e.args[0] 53 | if number == errno.EINTR: 54 | return 55 | else: 56 | # TODO: print an error message here? 57 | self.sock.close() 58 | self.sock = None 59 | fd = conn.fileno() 60 | iomap.register_write(fd, self.handle_write) 61 | self.socketmap[fd] = conn 62 | self.buffermap[fd] = self.password 63 | 64 | def handle_write(self, fd, iomap): 65 | buffer = self.buffermap[fd] 66 | conn = self.socketmap[fd] 67 | try: 68 | bytes_written = conn.send(buffer.encode()) 69 | except socket.error: 70 | _, e, _ = sys.exc_info() 71 | number = e.args[0] 72 | if number == errno.EINTR: 73 | return 74 | else: 75 | self.close_socket(fd, iomap) 76 | 77 | buffer = buffer[bytes_written:] 78 | if buffer: 79 | self.buffermap[fd] = buffer 80 | else: 81 | self.close_socket(fd, iomap) 82 | 83 | def close_socket(self, fd, iomap): 84 | iomap.unregister(fd) 85 | self.socketmap[fd].close() 86 | del self.socketmap[fd] 87 | del self.buffermap[fd] 88 | 89 | def __del__(self): 90 | if self.sock: 91 | self.sock.close() 92 | self.sock = None 93 | if self.address: 94 | os.remove(self.address) 95 | if self.tempdir: 96 | os.rmdir(self.tempdir) 97 | 98 | -------------------------------------------------------------------------------- /psshlib/cli.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-2012, Andrew McNabb 2 | # Copyright (c) 2003-2008, Brent N. Chun 3 | 4 | import optparse 5 | import os 6 | import shlex 7 | import sys 8 | import textwrap 9 | 10 | from psshlib import version 11 | 12 | _DEFAULT_PARALLELISM = 32 13 | _DEFAULT_TIMEOUT = 0 # "infinity" by default 14 | 15 | 16 | def common_parser(): 17 | """ 18 | Create a basic OptionParser with arguments common to all pssh programs. 19 | """ 20 | # The "resolve" conflict handler avoids errors from the hosts option 21 | # conflicting with the help option. 22 | parser = optparse.OptionParser(conflict_handler='resolve', 23 | version=version.VERSION) 24 | # Ensure that options appearing after the command are sent to ssh. 25 | parser.disable_interspersed_args() 26 | parser.epilog = "Example: pssh -h nodes.txt -l irb2 -o /tmp/foo uptime" 27 | 28 | parser.add_option('-h', '--hosts', dest='host_files', action='append', 29 | metavar='HOST_FILE', 30 | help='hosts file (each line "[user@]host[:port]")') 31 | parser.add_option('-H', '--host', dest='host_strings', action='append', 32 | metavar='HOST_STRING', 33 | help='additional host entries ("[user@]host[:port]")') 34 | parser.add_option('-l', '--user', dest='user', 35 | help='username (OPTIONAL)') 36 | parser.add_option('-p', '--par', dest='par', type='int', 37 | help='max number of parallel threads (OPTIONAL)') 38 | parser.add_option('-o', '--outdir', dest='outdir', 39 | help='output directory for stdout files (OPTIONAL)') 40 | parser.add_option('-e', '--errdir', dest='errdir', 41 | help='output directory for stderr files (OPTIONAL)') 42 | parser.add_option('--fileappend', dest='fileappend', action='store_true', 43 | help='append to existing output/error files, creates file(s) if missing (OPTIONAL)') 44 | parser.add_option('-t', '--timeout', dest='timeout', type='int', 45 | help='timeout (secs) (0 = no timeout) per host (OPTIONAL)') 46 | parser.add_option('-O', '--option', dest='options', action='append', 47 | metavar='OPTION', help='SSH option (OPTIONAL)') 48 | parser.add_option('-v', '--verbose', dest='verbose', action='store_true', 49 | help='turn on warning and diagnostic messages (OPTIONAL)') 50 | parser.add_option('-A', '--askpass', dest='askpass', action='store_true', 51 | help='Ask for a password (OPTIONAL)') 52 | parser.add_option('-x', '--extra-args', action='callback', type='string', 53 | metavar='ARGS', callback=shlex_append, dest='extra', 54 | help='Extra command-line arguments, with processing for ' 55 | 'spaces, quotes, and backslashes') 56 | parser.add_option('-X', '--extra-arg', dest='extra', action='append', 57 | metavar='ARG', help='Extra command-line argument') 58 | parser.add_option('-g', '--host-glob', dest='host_glob', type='string', 59 | help='Shell-style glob to filter hosts (OPTIONAL)') 60 | 61 | return parser 62 | 63 | 64 | def common_defaults(**kwargs): 65 | defaults = dict(par=_DEFAULT_PARALLELISM, timeout=_DEFAULT_TIMEOUT) 66 | defaults.update(**kwargs) 67 | envvars = [('user', 'PSSH_USER'), 68 | ('par', 'PSSH_PAR'), 69 | ('outdir', 'PSSH_OUTDIR'), 70 | ('errdir', 'PSSH_ERRDIR'), 71 | ('timeout', 'PSSH_TIMEOUT'), 72 | ('verbose', 'PSSH_VERBOSE'), 73 | ('print_out', 'PSSH_PRINT'), 74 | ('askpass', 'PSSH_ASKPASS'), 75 | ('inline', 'PSSH_INLINE'), 76 | ('recursive', 'PSSH_RECURSIVE'), 77 | ('archive', 'PSSH_ARCHIVE'), 78 | ('compress', 'PSSH_COMPRESS'), 79 | ('localdir', 'PSSH_LOCALDIR'), 80 | ] 81 | for option, var, in envvars: 82 | value = os.getenv(var) 83 | if value: 84 | defaults[option] = value 85 | 86 | value = os.getenv('PSSH_OPTIONS') 87 | if value: 88 | defaults['options'] = [value] 89 | 90 | value = os.getenv('PSSH_HOSTS') 91 | if value: 92 | message1 = ('Warning: the PSSH_HOSTS environment variable is ' 93 | 'deprecated. Please use the "-h" option instead, and consider ' 94 | 'creating aliases for convenience. For example:') 95 | message2 = " alias pssh_abc='pssh -h /path/to/hosts_abc'" 96 | sys.stderr.write(textwrap.fill(message1)) 97 | sys.stderr.write('\n') 98 | sys.stderr.write(message2) 99 | sys.stderr.write('\n') 100 | defaults['host_files'] = [value] 101 | 102 | return defaults 103 | 104 | 105 | def shlex_append(option, opt_str, value, parser): 106 | """An optparse callback similar to the append action. 107 | 108 | The given value is processed with shlex, and the resulting list is 109 | concatenated to the option's dest list. 110 | """ 111 | lst = getattr(parser.values, option.dest) 112 | if lst is None: 113 | lst = [] 114 | setattr(parser.values, option.dest, lst) 115 | lst.extend(shlex.split(value)) 116 | -------------------------------------------------------------------------------- /psshlib/color.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-2012, Andrew McNabb 2 | # Copyright (c) 2003-2008, Brent N. Chun 3 | 4 | def with_color(string, fg, bg=49): 5 | '''Given foreground/background ANSI color codes, return a string that, 6 | when printed, will format the supplied string using the supplied colors. 7 | ''' 8 | return "\x1b[%dm\x1b[%dm%s\x1b[39m\x1b[49m" % (fg, bg, string) 9 | 10 | def B(string): 11 | '''Returns a string that, when printed, will display the supplied string 12 | in ANSI bold. 13 | ''' 14 | return "\x1b[1m%s\x1b[22m" % string 15 | 16 | def r(string): return with_color(string, 31) # Red 17 | def g(string): return with_color(string, 32) # Green 18 | def y(string): return with_color(string, 33) # Yellow 19 | def b(string): return with_color(string, 34) # Blue 20 | def m(string): return with_color(string, 35) # Magenta 21 | def c(string): return with_color(string, 36) # Cyan 22 | def w(string): return with_color(string, 37) # White 23 | 24 | #following from Python cookbook, #475186 25 | def has_colors(stream): 26 | '''Returns boolean indicating whether or not the supplied stream supports 27 | ANSI color. 28 | ''' 29 | if not hasattr(stream, "isatty"): 30 | return False 31 | if not stream.isatty(): 32 | return False # auto color only on TTYs 33 | try: 34 | import curses 35 | curses.setupterm() 36 | return curses.tigetnum("colors") > 2 37 | except: 38 | # guess false in case of error 39 | return False 40 | -------------------------------------------------------------------------------- /psshlib/manager.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-2012, Andrew McNabb 2 | 3 | from errno import EINTR 4 | import os 5 | import fcntl 6 | import select 7 | import signal 8 | import sys 9 | import threading 10 | import queue 11 | 12 | from psshlib.askpass_server import PasswordServer 13 | 14 | READ_SIZE = 1 << 16 15 | 16 | 17 | class FatalError(RuntimeError): 18 | """A fatal error in the PSSH Manager.""" 19 | pass 20 | 21 | 22 | class Manager(object): 23 | """Executes tasks concurrently. 24 | 25 | Tasks are added with add_task() and executed in parallel with run(). 26 | Returns a list of the exit statuses of the processes. 27 | 28 | Arguments: 29 | limit: Maximum number of commands running at once. 30 | timeout: Maximum allowed execution time in seconds. 31 | """ 32 | def __init__(self, opts): 33 | self.limit = opts.par 34 | self.timeout = opts.timeout 35 | self.askpass = opts.askpass 36 | self.outdir = opts.outdir 37 | self.errdir = opts.errdir 38 | self.fileappend = opts.fileappend 39 | self.iomap = make_iomap() 40 | 41 | self.next_nodenum = 0 42 | self.numnodes = 0 43 | self.tasks = [] 44 | self.running = [] 45 | self.done = [] 46 | 47 | self.askpass_socket = None 48 | 49 | def run(self): 50 | """Processes tasks previously added with add_task.""" 51 | try: 52 | if self.outdir or self.errdir: 53 | writer = Writer(self.outdir, self.errdir, self.fileappend) 54 | writer.start() 55 | else: 56 | writer = None 57 | 58 | if self.askpass: 59 | pass_server = PasswordServer() 60 | pass_server.start(self.iomap, self.limit) 61 | self.askpass_socket = pass_server.address 62 | 63 | self.set_sigchld_handler() 64 | 65 | try: 66 | self.update_tasks(writer) 67 | wait = None 68 | while self.running or self.tasks: 69 | # Opt for efficiency over subsecond timeout accuracy. 70 | if wait is None or wait < 1: 71 | wait = 1 72 | self.iomap.poll(wait) 73 | self.update_tasks(writer) 74 | wait = self.check_timeout() 75 | except KeyboardInterrupt: 76 | # This exception handler tries to clean things up and prints 77 | # out a nice status message for each interrupted host. 78 | self.interrupted() 79 | 80 | except KeyboardInterrupt: 81 | # This exception handler doesn't print out any fancy status 82 | # information--it just stops. 83 | pass 84 | 85 | if writer: 86 | writer.signal_quit() 87 | writer.join() 88 | 89 | statuses = [task.exitstatus for task in self.done] 90 | return statuses 91 | 92 | def set_sigchld_handler(self): 93 | signal.signal(signal.SIGCHLD, self.handle_sigchld) 94 | # This should keep reads and writes from getting EINTR. 95 | if hasattr(signal, 'siginterrupt'): 96 | signal.siginterrupt(signal.SIGCHLD, False) 97 | 98 | def handle_sigchld(self, number, frame): 99 | """Apparently we need a sigchld handler to make set_wakeup_fd work.""" 100 | for task in self.running: 101 | if task.proc: 102 | task.proc.poll() 103 | # Apparently some UNIX systems automatically reset the SIGCHLD 104 | # handler to SIG_DFL. Reset it just in case. 105 | self.set_sigchld_handler() 106 | 107 | def add_task(self, task): 108 | """Adds a Task to be processed with run().""" 109 | self.tasks.append(task) 110 | self.numnodes += 1 111 | 112 | def update_tasks(self, writer): 113 | """Reaps tasks and starts as many new ones as allowed.""" 114 | while True: 115 | self._start_tasks_once(writer) 116 | if self.reap_tasks() == 0: 117 | break 118 | 119 | def _start_tasks_once(self, writer): 120 | """Starts tasks once.""" 121 | while 0 < len(self.tasks) and len(self.running) < self.limit: 122 | task = self.tasks.pop(0) 123 | self.running.append(task) 124 | task.start(self.next_nodenum, self.numnodes, self.iomap, writer, self.askpass_socket) 125 | self.next_nodenum += 1 126 | 127 | def reap_tasks(self): 128 | """Checks to see if any tasks have terminated. 129 | 130 | After cleaning up, returns the number of tasks that finished. 131 | """ 132 | still_running = [] 133 | finished_count = 0 134 | for task in self.running: 135 | if task.running(): 136 | still_running.append(task) 137 | else: 138 | self.finished(task) 139 | finished_count += 1 140 | self.running = still_running 141 | return finished_count 142 | 143 | def check_timeout(self): 144 | """Kills timed-out processes and returns the lowest time left.""" 145 | if self.timeout <= 0: 146 | return None 147 | 148 | min_timeleft = None 149 | for task in self.running: 150 | timeleft = self.timeout - task.elapsed() 151 | if timeleft <= 0: 152 | task.timedout() 153 | continue 154 | if min_timeleft is None or timeleft < min_timeleft: 155 | min_timeleft = timeleft 156 | 157 | if min_timeleft is None: 158 | return 0 159 | else: 160 | return max(0, min_timeleft) 161 | 162 | def interrupted(self): 163 | """Cleans up after a keyboard interrupt.""" 164 | for task in self.running: 165 | task.interrupted() 166 | self.finished(task) 167 | 168 | for task in self.tasks: 169 | task.cancel() 170 | self.finished(task) 171 | 172 | def finished(self, task): 173 | """Marks a task as complete and reports its status to stdout.""" 174 | self.done.append(task) 175 | n = len(self.done) 176 | task.report(n) 177 | 178 | 179 | class IOMap(object): 180 | """A manager for file descriptors and their associated handlers. 181 | 182 | The poll method dispatches events to the appropriate handlers. 183 | """ 184 | def __init__(self): 185 | self.readmap = {} 186 | self.writemap = {} 187 | 188 | # Setup the wakeup file descriptor to avoid hanging on lost signals. 189 | wakeup_readfd, wakeup_writefd = os.pipe() 190 | fcntl.fcntl(wakeup_writefd, fcntl.F_SETFL, os.O_NONBLOCK) 191 | self.register_read(wakeup_readfd, self.wakeup_handler) 192 | signal.set_wakeup_fd(wakeup_writefd) 193 | 194 | def register_read(self, fd, handler): 195 | """Registers an IO handler for a file descriptor for reading.""" 196 | self.readmap[fd] = handler 197 | 198 | def register_write(self, fd, handler): 199 | """Registers an IO handler for a file descriptor for writing.""" 200 | self.writemap[fd] = handler 201 | 202 | def unregister(self, fd): 203 | """Unregisters the given file descriptor.""" 204 | if fd in self.readmap: 205 | del self.readmap[fd] 206 | if fd in self.writemap: 207 | del self.writemap[fd] 208 | 209 | def poll(self, timeout=None): 210 | """Performs a poll and dispatches the resulting events.""" 211 | if not self.readmap and not self.writemap: 212 | return 213 | rlist = list(self.readmap) 214 | wlist = list(self.writemap) 215 | try: 216 | rlist, wlist, _ = select.select(rlist, wlist, [], timeout) 217 | except select.error: 218 | _, e, _ = sys.exc_info() 219 | errno = e.args[0] 220 | if errno == EINTR: 221 | return 222 | else: 223 | raise 224 | for fd in rlist: 225 | handler = self.readmap[fd] 226 | handler(fd, self) 227 | for fd in wlist: 228 | handler = self.writemap[fd] 229 | handler(fd, self) 230 | 231 | def wakeup_handler(self, fd, iomap): 232 | """Handles read events on the signal wakeup pipe. 233 | 234 | This ensures that SIGCHLD signals aren't lost. 235 | """ 236 | try: 237 | os.read(fd, READ_SIZE) 238 | except (OSError, IOError): 239 | _, e, _ = sys.exc_info() 240 | errno, message = e.args 241 | if errno != EINTR: 242 | sys.stderr.write('Fatal error reading from wakeup pipe: %s\n' 243 | % message) 244 | raise FatalError 245 | 246 | 247 | class PollIOMap(IOMap): 248 | """A manager for file descriptors and their associated handlers. 249 | 250 | The poll method dispatches events to the appropriate handlers. 251 | Note that `select.poll` is not available on all operating systems. 252 | """ 253 | def __init__(self): 254 | self._poller = select.poll() 255 | super(PollIOMap, self).__init__() 256 | 257 | def register_read(self, fd, handler): 258 | """Registers an IO handler for a file descriptor for reading.""" 259 | super(PollIOMap, self).register_read(fd, handler) 260 | self._poller.register(fd, select.POLLIN) 261 | 262 | def register_write(self, fd, handler): 263 | """Registers an IO handler for a file descriptor for writing.""" 264 | super(PollIOMap, self).register_write(fd, handler) 265 | self._poller.register(fd, select.POLLOUT) 266 | 267 | def unregister(self, fd): 268 | """Unregisters the given file descriptor.""" 269 | super(PollIOMap, self).unregister(fd) 270 | self._poller.unregister(fd) 271 | 272 | def poll(self, timeout=None): 273 | """Performs a poll and dispatches the resulting events.""" 274 | if not self.readmap and not self.writemap: 275 | return 276 | try: 277 | event_list = self._poller.poll(timeout) 278 | except select.error: 279 | _, e, _ = sys.exc_info() 280 | errno = e.args[0] 281 | if errno == EINTR: 282 | return 283 | else: 284 | raise 285 | for fd, event in event_list: 286 | if event & (select.POLLIN | select.POLLHUP): 287 | handler = self.readmap[fd] 288 | handler(fd, self) 289 | if event & (select.POLLOUT | select.POLLERR): 290 | handler = self.writemap[fd] 291 | handler(fd, self) 292 | 293 | 294 | def make_iomap(): 295 | """Return a new IOMap or PollIOMap as appropriate. 296 | 297 | Since `select.poll` is not implemented on all platforms, this ensures that 298 | the most appropriate implementation is used. 299 | """ 300 | if hasattr(select, 'poll'): 301 | return PollIOMap() 302 | else: 303 | return IOMap() 304 | 305 | 306 | class Writer(threading.Thread): 307 | """Thread that writes to files by processing requests from a Queue. 308 | 309 | Until AIO becomes widely available, it is impossible to make a nonblocking 310 | write to an ordinary file. The Writer thread processes all writing to 311 | ordinary files so that the main thread can work without blocking. 312 | """ 313 | OPEN = object() 314 | EOF = object() 315 | ABORT = object() 316 | 317 | def __init__(self, outdir, errdir, fileappend): 318 | threading.Thread.__init__(self) 319 | # A daemon thread automatically dies if the program is terminated. 320 | self.daemon = True 321 | self.queue = queue.Queue() 322 | self.outdir = outdir 323 | self.errdir = errdir 324 | 325 | if fileappend: 326 | self.filewritemode = 'ab' 327 | else: 328 | self.filewritemode = 'wb' 329 | 330 | self.host_counts = {} 331 | self.files = {} 332 | 333 | def run(self): 334 | while True: 335 | filename, data = self.queue.get() 336 | if filename == self.ABORT: 337 | return 338 | 339 | if data == self.OPEN: 340 | self.files[filename] = None 341 | else: 342 | dest = self.files[filename] 343 | if data == self.EOF: 344 | if dest is not None: 345 | dest.close() 346 | else: 347 | if dest is None: 348 | dest = self.files[filename] = open( 349 | filename, self.filewritemode, buffering=0) 350 | dest.write(data) 351 | 352 | def open_files(self, host): 353 | """Called from another thread to create files for stdout and stderr. 354 | 355 | Returns a pair of filenames (outfile, errfile). These filenames are 356 | used as handles for future operations. Either or both may be None if 357 | outdir or errdir or not set. 358 | """ 359 | outfile = errfile = None 360 | if self.outdir or self.errdir: 361 | count = self.host_counts.get(host, 0) 362 | self.host_counts[host] = count + 1 363 | if count: 364 | filename = "%s.%s" % (host, count) 365 | else: 366 | filename = host 367 | if self.outdir: 368 | outfile = os.path.join(self.outdir, filename) 369 | self.queue.put((outfile, self.OPEN)) 370 | if self.errdir: 371 | errfile = os.path.join(self.errdir, filename) 372 | self.queue.put((errfile, self.OPEN)) 373 | return outfile, errfile 374 | 375 | def write(self, filename, data): 376 | """Called from another thread to enqueue a write.""" 377 | self.queue.put((filename, data)) 378 | 379 | def close(self, filename): 380 | """Called from another thread to close the given file.""" 381 | self.queue.put((filename, self.EOF)) 382 | 383 | def signal_quit(self): 384 | """Called from another thread to request the Writer to quit.""" 385 | self.queue.put((self.ABORT, None)) 386 | 387 | -------------------------------------------------------------------------------- /psshlib/pardo.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, Jeffrey Lund 2 | 3 | import itertools 4 | import os 5 | 6 | NODE_NUM = int(os.environ.get('PSSH_NODENUM', '0')) 7 | NUM_NODES = int(os.environ.get('PSSH_NUMNODES', '1')) 8 | 9 | 10 | def pardo(xs): 11 | """ 12 | Helper function for splitting up work using pssh. Assuming the 13 | environmental variables PSSH_NODENUM and PSSH_NUMNODES are set, will yield 14 | only the subset of xs which corespond to this node's work. If these 15 | variables are not set, then every value of xs will be yielded. 16 | """ 17 | task_no = -1 18 | for i, x in enumerate(xs): 19 | task_no += 1 20 | if task_no % NUM_NODES != NODE_NUM: 21 | continue 22 | yield x 23 | 24 | 25 | def prange(*args): 26 | """ 27 | A shortcut for pardo(range(*args)). 28 | """ 29 | yield from pardo(range(*args)) 30 | 31 | 32 | def penumerate(iterable, start=0): 33 | """ 34 | A shortchut for pardo(enumerate(iterable, start=start)). 35 | """ 36 | yield from pardo(enumerate(iterable, start=start)) 37 | 38 | 39 | def pproduct(*iterables, repeat=1): 40 | """ 41 | A shortcut for pardo(itertools.product(*iterables, repeat=repeat)). 42 | """ 43 | yield from pardo(itertools.product(*iterables, repeat=repeat)) 44 | -------------------------------------------------------------------------------- /psshlib/psshutil.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-2012, Andrew McNabb 2 | # Copyright (c) 2003-2008, Brent N. Chun 3 | 4 | import re 5 | import sys 6 | import fnmatch 7 | 8 | HOST_FORMAT = 'Host format is [user@]host[:port] [user]' 9 | 10 | 11 | def read_host_files(paths, host_glob, default_user=None, default_port=None): 12 | """Reads the given host files. 13 | 14 | Returns a list of (host, port, user) triples. 15 | """ 16 | hosts = [] 17 | if paths: 18 | for path in paths: 19 | hosts.extend(read_host_file(path, host_glob, default_user=default_user)) 20 | return hosts 21 | 22 | 23 | def read_host_file(path, host_glob, default_user=None, default_port=None): 24 | """Reads the given host file. 25 | 26 | Lines are of the form: host[:port] [login]. 27 | Returns a list of (host, port, user) triples. 28 | """ 29 | lines = [] 30 | f = open(path) 31 | for line in f: 32 | lines.append(line.strip()) 33 | f.close() 34 | 35 | hosts = [] 36 | for line in lines: 37 | # remove trailing comments 38 | line = re.sub('#.*', '', line) 39 | line = line.strip() 40 | # skip blank lines (or lines with only comments) 41 | if not line: 42 | continue 43 | host, port, user = parse_host_entry(line, default_user, default_port) 44 | if host and (not host_glob or fnmatch.fnmatch(host, host_glob)): 45 | hosts.append((host, port, user)) 46 | return hosts 47 | 48 | 49 | # TODO: deprecate the second host field and standardize on the 50 | # [user@]host[:port] format. 51 | def parse_host_entry(line, default_user, default_port): 52 | """Parses a single host entry. 53 | 54 | This may take either the of the form [user@]host[:port] or 55 | host[:port][ user]. 56 | 57 | Returns a (host, port, user) triple. 58 | """ 59 | fields = line.split() 60 | if len(fields) > 2: 61 | sys.stderr.write('Bad line: "%s". Format should be' 62 | ' [user@]host[:port] [user]\n' % line) 63 | return None, None, None 64 | host_field = fields[0] 65 | host, port, user = parse_host(host_field, default_port=default_port) 66 | if len(fields) == 2: 67 | if user is None: 68 | user = fields[1] 69 | else: 70 | sys.stderr.write('User specified twice in line: "%s"\n' % line) 71 | return None, None, None 72 | if user is None: 73 | user = default_user 74 | return host, port, user 75 | 76 | 77 | def parse_host_string(host_string, default_user=None, default_port=None): 78 | """Parses a whitespace-delimited string of "[user@]host[:port]" entries. 79 | 80 | Returns a list of (host, port, user) triples. 81 | """ 82 | hosts = [] 83 | entries = host_string.split() 84 | for entry in entries: 85 | hosts.append(parse_host(entry, default_user, default_port)) 86 | return hosts 87 | 88 | 89 | def parse_host(host, default_user=None, default_port=None): 90 | """Parses host entries of the form "[user@]host[:port]". 91 | 92 | Returns a (host, port, user) triple. 93 | """ 94 | m = re.match('^(?:([^@]+)@)?(.*?)(?::([0-9]+))?$', host) 95 | host = m.group(2) 96 | user = m.group(1) or default_user 97 | port = m.group(3) or default_port 98 | if host.startswith('[') and host.endswith(']'): 99 | host = host[1:-1] 100 | return (host, port, user) 101 | 102 | 103 | -------------------------------------------------------------------------------- /psshlib/task.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, Jeffrey Lund 2 | # Copyright (c) 2009-2012, Andrew McNabb 3 | 4 | from errno import EINTR 5 | from subprocess import Popen, PIPE 6 | import os 7 | import signal 8 | import sys 9 | import time 10 | import traceback 11 | 12 | from psshlib import askpass_client 13 | from psshlib import color 14 | 15 | BUFFER_SIZE = 1 << 16 16 | 17 | try: 18 | bytes 19 | except NameError: 20 | bytes = str 21 | 22 | 23 | class Task(object): 24 | """Starts a process and manages its input and output. 25 | 26 | Upon completion, the `exitstatus` attribute is set to the exit status 27 | of the process. 28 | """ 29 | def __init__(self, host, port, user, cmd, opts, stdin=None): 30 | self.exitstatus = None 31 | 32 | self.host = host 33 | if ':' in host: 34 | self.pretty_host = '[' + host + ']' 35 | else: 36 | self.pretty_host = host 37 | self.port = port 38 | self.cmd = cmd 39 | 40 | if user != opts.user: 41 | self.pretty_host = '@'.join((user, self.pretty_host)) 42 | if port: 43 | self.pretty_host = ':'.join((self.pretty_host, port)) 44 | 45 | self.proc = None 46 | self.writer = None 47 | self.timestamp = None 48 | self.failures = [] 49 | self.killed = False 50 | self.inputbuffer = stdin 51 | self.byteswritten = 0 52 | self.outputbuffer = bytes() 53 | self.errorbuffer = bytes() 54 | 55 | self.stdin = None 56 | self.stdout = None 57 | self.stderr = None 58 | self.outfile = None 59 | self.errfile = None 60 | 61 | # Set options. 62 | self.verbose = opts.verbose 63 | try: 64 | self.print_out = bool(opts.print_out) 65 | except AttributeError: 66 | self.print_out = False 67 | try: 68 | self.inline = bool(opts.inline) 69 | except AttributeError: 70 | self.inline = False 71 | try: 72 | self.inline_stdout = bool(opts.inline_stdout) 73 | except AttributeError: 74 | self.inline_stdout = False 75 | try: 76 | self.noheaders = bool(opts.noheaders) 77 | except AttributeError: 78 | self.noheaders = False 79 | 80 | def start(self, nodenum, numnodes, iomap, writer, askpass_socket=None): 81 | """Starts the process and registers files with the IOMap.""" 82 | self.writer = writer 83 | 84 | if writer: 85 | self.outfile, self.errfile = writer.open_files(self.pretty_host) 86 | 87 | # Set up the environment. 88 | environ = os.environ.copy() 89 | environ['PSSH_NODENUM'] = str(nodenum) 90 | environ['PSSH_NUMNODES'] = str(numnodes) 91 | environ['PSSH_HOST'] = self.host 92 | # Disable the GNOME pop-up password dialog and allow ssh to use 93 | # askpass.py to get a provided password. If the module file is 94 | # askpass.pyc, we replace the extension. 95 | environ['SSH_ASKPASS'] = askpass_client.executable_path() 96 | if askpass_socket: 97 | environ['PSSH_ASKPASS_SOCKET'] = askpass_socket 98 | if self.verbose: 99 | environ['PSSH_ASKPASS_VERBOSE'] = '1' 100 | # Work around a mis-feature in ssh where it won't call SSH_ASKPASS 101 | # if DISPLAY is unset. 102 | if 'DISPLAY' not in environ: 103 | environ['DISPLAY'] = 'pssh-gibberish' 104 | 105 | # Create the subprocess. Since we carefully call set_cloexec() on 106 | # all open files, we specify close_fds=False. 107 | self.proc = Popen(self.cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, 108 | close_fds=False, preexec_fn=os.setsid, env=environ) 109 | self.timestamp = time.time() 110 | if self.inputbuffer: 111 | self.stdin = self.proc.stdin 112 | iomap.register_write(self.stdin.fileno(), self.handle_stdin) 113 | else: 114 | self.proc.stdin.close() 115 | self.stdout = self.proc.stdout 116 | iomap.register_read(self.stdout.fileno(), self.handle_stdout) 117 | self.stderr = self.proc.stderr 118 | iomap.register_read(self.stderr.fileno(), self.handle_stderr) 119 | 120 | def _kill(self): 121 | """Signals the process to terminate.""" 122 | if self.proc: 123 | try: 124 | os.kill(-self.proc.pid, signal.SIGKILL) 125 | except OSError: 126 | # If the kill fails, then just assume the process is dead. 127 | pass 128 | self.killed = True 129 | 130 | def timedout(self): 131 | """Kills the process and registers a timeout error.""" 132 | if not self.killed: 133 | self._kill() 134 | self.failures.append('Timed out') 135 | 136 | def interrupted(self): 137 | """Kills the process and registers an keyboard interrupt error.""" 138 | if not self.killed: 139 | self._kill() 140 | self.failures.append('Interrupted') 141 | 142 | def cancel(self): 143 | """Stops a task that has not started.""" 144 | self.failures.append('Cancelled') 145 | 146 | def elapsed(self): 147 | """Finds the time in seconds since the process was started.""" 148 | return time.time() - self.timestamp 149 | 150 | def running(self): 151 | """Finds if the process has terminated and saves the return code.""" 152 | # don't check self.stderr; it's still open when ControlPersist=yes is 153 | # set for ssh 154 | if self.stdin or self.stdout: 155 | return True 156 | if self.proc: 157 | self.exitstatus = self.proc.poll() 158 | if self.exitstatus is None: 159 | if self.killed: 160 | # Set the exitstatus to what it would be if we waited. 161 | self.exitstatus = -signal.SIGKILL 162 | return False 163 | else: 164 | return True 165 | else: 166 | if self.exitstatus < 0: 167 | message = 'Killed by signal %s' % (-self.exitstatus) 168 | self.failures.append(message) 169 | elif self.exitstatus > 0: 170 | message = 'Exited with error code %s' % self.exitstatus 171 | self.failures.append(message) 172 | self.proc = None 173 | return False 174 | 175 | def handle_stdin(self, fd, iomap): 176 | """Called when the process's standard input is ready for writing.""" 177 | try: 178 | start = self.byteswritten 179 | if start < len(self.inputbuffer): 180 | chunk = self.inputbuffer[start:start+BUFFER_SIZE] 181 | self.byteswritten = start + os.write(fd, chunk) 182 | else: 183 | self.close_stdin(iomap) 184 | except (OSError, IOError): 185 | _, e, _ = sys.exc_info() 186 | if e.errno != EINTR: 187 | self.close_stdin(iomap) 188 | self.log_exception(e) 189 | 190 | def close_stdin(self, iomap): 191 | if self.stdin: 192 | iomap.unregister(self.stdin.fileno()) 193 | self.stdin.close() 194 | self.stdin = None 195 | 196 | def handle_stdout(self, fd, iomap): 197 | """Called when the process's standard output is ready for reading.""" 198 | try: 199 | buf = os.read(fd, BUFFER_SIZE) 200 | if buf: 201 | if self.inline or self.inline_stdout: 202 | self.outputbuffer += buf 203 | if self.outfile: 204 | self.writer.write(self.outfile, buf) 205 | if self.print_out: 206 | text = buf.decode(errors='replace') 207 | sys.stdout.write('%s: %s' % (self.host, text)) 208 | if text[-1] != '\n': 209 | sys.stdout.write('\n') 210 | else: 211 | self.close_stdout(iomap) 212 | except (OSError, IOError): 213 | _, e, _ = sys.exc_info() 214 | if e.errno != EINTR: 215 | self.close_stdout(iomap) 216 | self.log_exception(e) 217 | 218 | def close_stdout(self, iomap): 219 | if self.stdout: 220 | iomap.unregister(self.stdout.fileno()) 221 | self.stdout.close() 222 | self.stdout = None 223 | if self.outfile: 224 | self.writer.close(self.outfile) 225 | self.outfile = None 226 | 227 | def handle_stderr(self, fd, iomap): 228 | """Called when the process's standard error is ready for reading.""" 229 | try: 230 | buf = os.read(fd, BUFFER_SIZE) 231 | if buf: 232 | if self.inline: 233 | self.errorbuffer += buf 234 | if self.errfile: 235 | self.writer.write(self.errfile, buf) 236 | if self.print_out: 237 | text = buf.decode(errors='replace') 238 | sys.stderr.write('%s: %s' % (self.host, text)) 239 | if text[-1] != '\n': 240 | sys.stderr.write('\n') 241 | else: 242 | self.close_stderr(iomap) 243 | except (OSError, IOError): 244 | _, e, _ = sys.exc_info() 245 | if e.errno != EINTR: 246 | self.close_stderr(iomap) 247 | self.log_exception(e) 248 | 249 | def close_stderr(self, iomap): 250 | if self.stderr: 251 | iomap.unregister(self.stderr.fileno()) 252 | self.stderr.close() 253 | self.stderr = None 254 | if self.errfile: 255 | self.writer.close(self.errfile) 256 | self.errfile = None 257 | 258 | def log_exception(self, e): 259 | """Saves a record of the most recent exception for error reporting.""" 260 | if self.verbose: 261 | exc_type, exc_value, exc_traceback = sys.exc_info() 262 | exc = ("Exception: %s, %s, %s" % 263 | (exc_type, exc_value, traceback.format_tb(exc_traceback))) 264 | else: 265 | exc = str(e) 266 | self.failures.append(exc) 267 | 268 | def report(self, n): 269 | """Pretty prints a status report after the Task completes.""" 270 | if not self.noheaders: 271 | error = ', '.join(self.failures) 272 | tstamp = time.asctime().split()[3] # Current time 273 | if color.has_colors(sys.stdout): 274 | progress = color.c("[%s]" % color.B(n)) 275 | success = color.g("[%s]" % color.B("SUCCESS")) 276 | failure = color.r("[%s]" % color.B("FAILURE")) 277 | stderr = color.r("Stderr: ") 278 | error = color.r(color.B(error)) 279 | else: 280 | progress = "[%s]" % n 281 | success = "[SUCCESS]" 282 | failure = "[FAILURE]" 283 | stderr = "Stderr: " 284 | host = self.pretty_host 285 | if self.failures: 286 | print(' '.join((progress, tstamp, failure, host, error))) 287 | else: 288 | print(' '.join((progress, tstamp, success, host))) 289 | else: 290 | stderr = '' 291 | # NOTE: The extra flushes are to ensure that the data is output in 292 | # the correct order with the C implementation of io. 293 | if self.outputbuffer: 294 | sys.stdout.flush() 295 | try: 296 | sys.stdout.buffer.write(self.outputbuffer) 297 | sys.stdout.flush() 298 | except AttributeError: 299 | sys.stdout.write(self.outputbuffer) 300 | if self.errorbuffer: 301 | sys.stdout.write(stderr) 302 | # Flush the TextIOWrapper before writing to the binary buffer. 303 | sys.stdout.flush() 304 | try: 305 | sys.stdout.buffer.write(self.errorbuffer) 306 | except AttributeError: 307 | sys.stdout.write(self.errorbuffer) 308 | 309 | -------------------------------------------------------------------------------- /psshlib/version.py: -------------------------------------------------------------------------------- 1 | VERSION = '2.3.5' 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # The complex upload command: 2 | # rm -rf dist && python -m build --sdist && twine check dist/* && twine upload -s dist/* 3 | 4 | [metadata] 5 | name = pssh 6 | version = attr: psshlib.version.VERSION 7 | author = Andrew McNabb 8 | author_email = amcnabb@mcnabbs.org 9 | description = Parallel version of OpenSSH and related tools 10 | license = BSD 11 | url = https://github.com/lilydjwg/pssh 12 | long_description = PSSH (Parallel SSH) provides parallel versions of OpenSSH and related tools, including pssh, pscp, prsync, pnuke, and pslurp. The project includes psshlib which can be used within custom applications. 13 | long_description_content_type = text/plain 14 | platforms = linux 15 | 16 | classifiers = 17 | Development Status :: 5 - Production/Stable 18 | Intended Audience :: System Administrators 19 | License :: OSI Approved :: BSD License 20 | Operating System :: POSIX 21 | Programming Language :: Python 22 | Programming Language :: Python :: 3 23 | Programming Language :: Python :: 3.5 24 | Programming Language :: Python :: 3.6 25 | Programming Language :: Python :: 3.7 26 | Programming Language :: Python :: 3.8 27 | Programming Language :: Python :: 3.9 28 | Programming Language :: Python :: 3.10 29 | Programming Language :: Python :: 3.11 30 | Topic :: Software Development :: Libraries :: Python Modules 31 | Topic :: System :: Clustering 32 | Topic :: System :: Networking 33 | Topic :: System :: Systems Administration 34 | 35 | [options] 36 | zip_safe = True 37 | 38 | packages = psshlib 39 | install_requires = 40 | setuptools; python_version<"3.8" 41 | scripts = 42 | bin/pssh 43 | bin/pnuke 44 | bin/prsync 45 | bin/pslurp 46 | bin/pscp 47 | bin/pssh-askpass 48 | 49 | -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2009, Andrew McNabb 4 | # Copyright (c) 2003-2008, Brent N. Chun 5 | 6 | import os 7 | import subprocess 8 | import sys 9 | import shutil 10 | import tempfile 11 | import time 12 | import unittest 13 | 14 | basedir, bin = os.path.split(os.path.dirname(os.path.abspath(sys.argv[0]))) 15 | sys.path.append("%s" % basedir) 16 | 17 | if os.getenv("TEST_HOSTS") is None: 18 | raise Exception("Must define TEST_HOSTS") 19 | g_hosts = os.getenv("TEST_HOSTS").split() 20 | 21 | if os.getenv("TEST_USER") is None: 22 | raise Exception("Must define TEST_USER") 23 | g_user = os.getenv("TEST_USER") 24 | 25 | class PsshTest(unittest.TestCase): 26 | def setUp(self): 27 | self.outDir = tempfile.mkdtemp() 28 | self.errDir = tempfile.mkdtemp() 29 | 30 | def tearDown(self): 31 | shutil.rmtree(self.errDir) 32 | shutil.rmtree(self.outDir) 33 | 34 | def testShortOpts(self): 35 | hostsFile = tempfile.NamedTemporaryFile() 36 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 37 | hostsFile.flush() 38 | cmd = "%s/bin/pssh -h %s -l %s -p 64 -o %s -e %s -t 60 -v -P -i uptime < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 39 | rv = subprocess.call(cmd, shell=True) 40 | self.assertEqual(rv, 0) 41 | for host in g_hosts: 42 | stdout = open("%s/%s" % (self.outDir, host)).read() 43 | self.assert_(stdout.find("load average") != -1) 44 | 45 | def testLongOpts(self): 46 | hostsFile = tempfile.NamedTemporaryFile() 47 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 48 | hostsFile.flush() 49 | cmd = "%s/bin/pssh --hosts=%s --user=%s --par=64 --outdir=%s --errdir=%s --timeout=60 --verbose --print --inline uptime < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 50 | rv = subprocess.call(cmd, shell=True) 51 | self.assertEqual(rv, 0) 52 | for host in g_hosts: 53 | stdout = open("%s/%s" % (self.outDir, host)).read() 54 | self.assert_(stdout.find("load average") != -1) 55 | 56 | def testStderr(self): 57 | hostsFile = tempfile.NamedTemporaryFile() 58 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 59 | hostsFile.flush() 60 | cmd = "%s/bin/pssh -h %s -l %s -p 64 -o %s -e %s -t 60 -v -P -i ls /doesnotexist < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 61 | rv = subprocess.call(cmd, shell=True) 62 | self.assertEqual(rv, 0) 63 | for host in g_hosts: 64 | stdout = open("%s/%s" % (self.outDir, host)).read() 65 | self.assertEqual(stdout, "") 66 | stderr = open("%s/%s" % (self.errDir, host)).read() 67 | self.assert_(stderr.find("No such file or directory") != -1) 68 | 69 | class PscpTest(unittest.TestCase): 70 | def setUp(self): 71 | self.outDir = tempfile.mkdtemp() 72 | self.errDir = tempfile.mkdtemp() 73 | 74 | def tearDown(self): 75 | shutil.rmtree(self.errDir) 76 | shutil.rmtree(self.outDir) 77 | try: 78 | os.remove("/tmp/pssh.test") 79 | except OSError: 80 | pass 81 | 82 | def testShortOpts(self): 83 | for host in g_hosts: 84 | cmd = "ssh %s@%s rm -rf /tmp/pssh.test" % (g_user, host) 85 | rv = subprocess.call(cmd, shell=True) 86 | self.assertEqual(rv, 0) 87 | 88 | hostsFile = tempfile.NamedTemporaryFile() 89 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 90 | hostsFile.flush() 91 | cmd = "%s/bin/pscp -h %s -l %s -p 64 -o %s -e %s -t 60 /etc/hosts /tmp/pssh.test < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 92 | rv = subprocess.call(cmd, shell=True) 93 | self.assertEqual(rv, 0) 94 | for host in g_hosts: 95 | cmd = "ssh %s@%s cat /tmp/pssh.test" % (g_user, host) 96 | data = os.popen(cmd).read() 97 | self.assertEqual(data, open("/etc/hosts").read()) 98 | 99 | def testLongOpts(self): 100 | for host in g_hosts: 101 | cmd = "ssh %s@%s rm -rf /tmp/pssh.test" % (g_user, host) 102 | rv = subprocess.call(cmd, shell=True) 103 | self.assertEqual(rv, 0) 104 | 105 | hostsFile = tempfile.NamedTemporaryFile() 106 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 107 | hostsFile.flush() 108 | cmd = "%s/bin/pscp --hosts=%s --user=%s --par=64 --outdir=%s --errdir=%s --timeout=60 /etc/hosts /tmp/pssh.test < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 109 | rv = subprocess.call(cmd, shell=True) 110 | self.assertEqual(rv, 0) 111 | for host in g_hosts: 112 | cmd = "ssh %s@%s cat /tmp/pssh.test" % (g_user, host) 113 | data = os.popen(cmd).read() 114 | self.assertEqual(data, open("/etc/hosts").read()) 115 | 116 | def testRecursive(self): 117 | for host in g_hosts: 118 | cmd = "ssh %s@%s rm -rf /tmp/pssh.test" % (g_user, host) 119 | rv = subprocess.call(cmd, shell=True) 120 | self.assertEqual(rv, 0) 121 | 122 | hostsFile = tempfile.NamedTemporaryFile() 123 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 124 | hostsFile.flush() 125 | cmd = "%s/bin/pscp -r -h %s -l %s -p 64 -o %s -e %s -t 60 /etc/init.d /tmp/pssh.test < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 126 | rv = subprocess.call(cmd, shell=True) 127 | self.assertEqual(rv, 0) 128 | files = os.popen("ls -R /etc/init.d | sed 1d | sort").read().strip() 129 | for host in g_hosts: 130 | cmd = "ssh %s@%s ls -R /tmp/pssh.test | sed 1d | sort" % (g_user, host) 131 | data = os.popen(cmd).read().strip() 132 | self.assertEqual(data, files) 133 | 134 | class PslurpTest(unittest.TestCase): 135 | def setUp(self): 136 | self.outDir = tempfile.mkdtemp() 137 | self.errDir = tempfile.mkdtemp() 138 | 139 | def tearDown(self): 140 | shutil.rmtree(self.errDir) 141 | shutil.rmtree(self.outDir) 142 | 143 | def testShortOpts(self): 144 | if os.path.exists("/tmp/pssh.test"): 145 | try: 146 | os.remove("/tmp/pssh.test") 147 | except OSError: 148 | shutil.rmtree("/tmp/pssh.test") 149 | 150 | hostsFile = tempfile.NamedTemporaryFile() 151 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 152 | hostsFile.flush() 153 | cmd = "%s/bin/pslurp -L /tmp/pssh.test -h %s -l %s -p 64 -o %s -e %s -t 60 /etc/hosts hosts < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 154 | rv = subprocess.call(cmd, shell=True) 155 | self.assertEqual(rv, 0) 156 | 157 | for host in g_hosts: 158 | cmd = "ssh %s@%s cat /etc/hosts" % (g_user, host) 159 | data = os.popen(cmd).read() 160 | self.assertEqual(data, open("/tmp/pssh.test/%s/hosts" % host).read()) 161 | 162 | def testLongOpts(self): 163 | if os.path.exists("/tmp/pssh.test"): 164 | try: 165 | os.remove("/tmp/pssh.test") 166 | except OSError: 167 | shutil.rmtree("/tmp/pssh.test") 168 | 169 | hostsFile = tempfile.NamedTemporaryFile() 170 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 171 | hostsFile.flush() 172 | cmd = "%s/bin/pslurp --localdir=/tmp/pssh.test --hosts=%s --user=%s --par=64 --outdir=%s --errdir=%s --timeout=60 /etc/hosts hosts < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 173 | rv = subprocess.call(cmd, shell=True) 174 | self.assertEqual(rv, 0) 175 | 176 | for host in g_hosts: 177 | cmd = "ssh %s@%s cat /etc/hosts" % (g_user, host) 178 | data = os.popen(cmd).read() 179 | self.assertEqual(data, open("/tmp/pssh.test/%s/hosts" % host).read()) 180 | 181 | def testRecursive(self): 182 | if os.path.exists("/tmp/pssh.test"): 183 | try: 184 | os.remove("/tmp/pssh.test") 185 | except OSError: 186 | shutil.rmtree("/tmp/pssh.test") 187 | 188 | hostsFile = tempfile.NamedTemporaryFile() 189 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 190 | hostsFile.flush() 191 | cmd = "%s/bin/pslurp -r -L /tmp/pssh.test -h %s -l %s -p 64 -o %s -e %s -t 60 /etc/init.d init.d < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 192 | rv = subprocess.call(cmd, shell=True) 193 | self.assertEqual(rv, 0) 194 | 195 | for host in g_hosts: 196 | cmd = "ssh %s@%s ls -R /etc/init.d | sed 1d | sort" % (g_user, host) 197 | data = os.popen(cmd).read() 198 | self.assertEqual(data, os.popen("ls -R /tmp/pssh.test/%s/init.d | sed 1d | sort" % host).read()) 199 | 200 | class PrsyncTest(unittest.TestCase): 201 | def setUp(self): 202 | self.outDir = tempfile.mkdtemp() 203 | self.errDir = tempfile.mkdtemp() 204 | 205 | def tearDown(self): 206 | shutil.rmtree(self.errDir) 207 | shutil.rmtree(self.outDir) 208 | 209 | def testShortOpts(self): 210 | for host in g_hosts: 211 | cmd = "ssh %s@%s rm -rf /tmp/pssh.test" % (g_user, host) 212 | rv = subprocess.call(cmd, shell=True) 213 | self.assertEqual(rv, 0) 214 | 215 | hostsFile = tempfile.NamedTemporaryFile() 216 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 217 | hostsFile.flush() 218 | cmd = "%s/bin/prsync -h %s -l %s -p 64 -o %s -e %s -t 60 -a -z /etc/hosts /tmp/pssh.test < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 219 | rv = subprocess.call(cmd, shell=True) 220 | self.assertEqual(rv, 0) 221 | for host in g_hosts: 222 | cmd = "ssh %s@%s cat /tmp/pssh.test" % (g_user, host) 223 | data = os.popen(cmd).read() 224 | self.assertEqual(data, open("/etc/hosts").read()) 225 | 226 | def testLongOpts(self): 227 | for host in g_hosts: 228 | cmd = "ssh %s@%s rm -rf /tmp/pssh.test" % (g_user, host) 229 | rv = subprocess.call(cmd, shell=True) 230 | self.assertEqual(rv, 0) 231 | 232 | hostsFile = tempfile.NamedTemporaryFile() 233 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 234 | hostsFile.flush() 235 | cmd = "%s/bin/prsync --hosts=%s --user=%s --par=64 --outdir=%s --errdir=%s --timeout=60 --archive --compress /etc/hosts /tmp/pssh.test < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 236 | rv = subprocess.call(cmd, shell=True) 237 | self.assertEqual(rv, 0) 238 | for host in g_hosts: 239 | cmd = "ssh %s@%s cat /tmp/pssh.test" % (g_user, host) 240 | data = os.popen(cmd).read() 241 | self.assertEqual(data, open("/etc/hosts").read()) 242 | 243 | def testRecursive(self): 244 | for host in g_hosts: 245 | cmd = "ssh %s@%s rm -rf /tmp/pssh.test" % (g_user, host) 246 | rv = subprocess.call(cmd, shell=True) 247 | self.assertEqual(rv, 0) 248 | 249 | hostsFile = tempfile.NamedTemporaryFile() 250 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 251 | hostsFile.flush() 252 | cmd = "%s/bin/prsync -r -h %s -l %s -p 64 -o %s -e %s -t 60 -a -z /etc/init.d/ /tmp/pssh.test < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 253 | rv = subprocess.call(cmd, shell=True) 254 | self.assertEqual(rv, 0) 255 | files = os.popen("ls -R /etc/init.d | sed 1d | sort").read().strip() 256 | for host in g_hosts: 257 | cmd = "ssh %s@%s ls -R /tmp/pssh.test | sed 1d | sort" % (g_user, host) 258 | data = os.popen(cmd).read().strip() 259 | self.assertEqual(data, files) 260 | 261 | class PnukeTest(unittest.TestCase): 262 | def setUp(self): 263 | self.outDir = tempfile.mkdtemp() 264 | self.errDir = tempfile.mkdtemp() 265 | 266 | def tearDown(self): 267 | shutil.rmtree(self.errDir) 268 | shutil.rmtree(self.outDir) 269 | 270 | def testShortOpts(self): 271 | hostsFile = tempfile.NamedTemporaryFile() 272 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 273 | hostsFile.flush() 274 | cmd = "%s/bin/pssh -h %s -l %s -p 64 -o %s -e %s -t 60 -v sleep 60 < /dev/null &" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 275 | subprocess.call(cmd, shell=True) 276 | time.sleep(5) 277 | 278 | cmd = "%s/bin/pnuke -h %s -l %s -p 64 -o %s -e %s -t 60 -v sleep < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 279 | print cmd 280 | rv = subprocess.call(cmd, shell=True) 281 | self.assertEqual(rv, 0) 282 | 283 | def testLongOpts(self): 284 | hostsFile = tempfile.NamedTemporaryFile() 285 | hostsFile.write("".join(map(lambda x: "%s\n" % x, g_hosts))) 286 | hostsFile.flush() 287 | cmd = "%s/bin/pssh --hosts=%s --user=%s --par=64 --outdir=%s --errdir=%s --timeout=60 --verbose sleep 60 < /dev/null &" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 288 | subprocess.call(cmd, shell=True) 289 | time.sleep(5) 290 | 291 | cmd = "%s/bin/pnuke --hosts=%s --user=%s --par=64 --outdir=%s --errdir=%s --timeout=60 --verbose sleep < /dev/null" % (basedir, hostsFile.name, g_user, self.outDir, self.errDir) 292 | print cmd 293 | rv = subprocess.call(cmd, shell=True) 294 | self.assertEqual(rv, 0) 295 | 296 | if __name__ == '__main__': 297 | suite = unittest.TestSuite() 298 | suite.addTest(unittest.makeSuite(PsshTest, "test")) 299 | suite.addTest(unittest.makeSuite(PscpTest, "test")) 300 | suite.addTest(unittest.makeSuite(PslurpTest, "test")) 301 | suite.addTest(unittest.makeSuite(PrsyncTest, "test")) 302 | suite.addTest(unittest.makeSuite(PnukeTest, "test")) 303 | unittest.TextTestRunner().run(suite) 304 | --------------------------------------------------------------------------------