pec2 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,101 +0,0 @@
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
- from psshlib import psshutil
18
-
19
-
20
- class PasswordServer(object):
21
- """Listens on a UNIX domain socket for password requests."""
22
- def __init__(self):
23
- self.sock = None
24
- self.tempdir = None
25
- self.address = None
26
- self.socketmap = {}
27
- self.buffermap = {}
28
-
29
- def start(self, iomap, backlog):
30
- """Prompts for the password, creates a socket, and starts listening.
31
-
32
- The specified backlog should be the max number of clients connecting
33
- at once.
34
- """
35
- message = ('Warning: do not enter your password if anyone else has'
36
- ' superuser privileges or access to your account.')
37
- print(textwrap.fill(message))
38
-
39
- self.password = getpass.getpass()
40
-
41
- # Note that according to the docs for mkdtemp, "The directory is
42
- # readable, writable, and searchable only by the creating user."
43
- self.tempdir = tempfile.mkdtemp(prefix='pssh.')
44
- self.address = os.path.join(self.tempdir, 'pssh_askpass_socket')
45
- self.sock = socket.socket(socket.AF_UNIX)
46
- psshutil.set_cloexec(self.sock)
47
- self.sock.bind(self.address)
48
- self.sock.listen(backlog)
49
- iomap.register_read(self.sock.fileno(), self.handle_listen)
50
-
51
- def handle_listen(self, fd, iomap):
52
- try:
53
- conn = self.sock.accept()[0]
54
- except socket.error:
55
- _, e, _ = sys.exc_info()
56
- number = e.args[0]
57
- if number == errno.EINTR:
58
- return
59
- else:
60
- # TODO: print an error message here?
61
- self.sock.close()
62
- self.sock = None
63
- fd = conn.fileno()
64
- iomap.register_write(fd, self.handle_write)
65
- self.socketmap[fd] = conn
66
- self.buffermap[fd] = self.password
67
-
68
- def handle_write(self, fd, iomap):
69
- buffer = self.buffermap[fd]
70
- conn = self.socketmap[fd]
71
- try:
72
- bytes_written = conn.send(buffer)
73
- except socket.error:
74
- _, e, _ = sys.exc_info()
75
- number = e.args[0]
76
- if number == errno.EINTR:
77
- return
78
- else:
79
- self.close_socket(fd, iomap)
80
-
81
- buffer = buffer[bytes_written:]
82
- if buffer:
83
- self.buffermap[fd] = buffer
84
- else:
85
- self.close_socket(fd, iomap)
86
-
87
- def close_socket(self, fd, iomap):
88
- iomap.unregister(fd)
89
- self.socketmap[fd].close()
90
- del self.socketmap[fd]
91
- del self.buffermap[fd]
92
-
93
- def __del__(self):
94
- if self.sock:
95
- self.sock.close()
96
- self.sock = None
97
- if self.address:
98
- os.remove(self.address)
99
- if self.tempdir:
100
- os.rmdir(self.tempdir)
101
-
data/exe/psshlib/cli.py DELETED
@@ -1,110 +0,0 @@
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
- import version
10
-
11
- _DEFAULT_PARALLELISM = 32
12
- _DEFAULT_TIMEOUT = 0 # "infinity" by default
13
-
14
-
15
- def common_parser():
16
- """
17
- Create a basic OptionParser with arguments common to all pssh programs.
18
- """
19
- # The "resolve" conflict handler avoids errors from the hosts option
20
- # conflicting with the help option.
21
- parser = optparse.OptionParser(conflict_handler='resolve',
22
- version=version.VERSION)
23
- # Ensure that options appearing after the command are sent to ssh.
24
- parser.disable_interspersed_args()
25
- parser.epilog = "Example: pssh -h nodes.txt -l irb2 -o /tmp/foo uptime"
26
-
27
- parser.add_option('-h', '--hosts', dest='host_files', action='append',
28
- metavar='HOST_FILE',
29
- help='hosts file (each line "[user@]host[:port]")')
30
- parser.add_option('-H', '--host', dest='host_strings', action='append',
31
- metavar='HOST_STRING',
32
- help='additional host entries ("[user@]host[:port]")')
33
- parser.add_option('-l', '--user', dest='user',
34
- help='username (OPTIONAL)')
35
- parser.add_option('-p', '--par', dest='par', type='int',
36
- help='max number of parallel threads (OPTIONAL)')
37
- parser.add_option('-o', '--outdir', dest='outdir',
38
- help='output directory for stdout files (OPTIONAL)')
39
- parser.add_option('-e', '--errdir', dest='errdir',
40
- help='output directory for stderr files (OPTIONAL)')
41
- parser.add_option('-t', '--timeout', dest='timeout', type='int',
42
- help='timeout (secs) (0 = no timeout) per host (OPTIONAL)')
43
- parser.add_option('-O', '--option', dest='options', action='append',
44
- metavar='OPTION', help='SSH option (OPTIONAL)')
45
- parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
46
- help='turn on warning and diagnostic messages (OPTIONAL)')
47
- parser.add_option('-A', '--askpass', dest='askpass', action='store_true',
48
- help='Ask for a password (OPTIONAL)')
49
- parser.add_option('-x', '--extra-args', action='callback', type='string',
50
- metavar='ARGS', callback=shlex_append, dest='extra',
51
- help='Extra command-line arguments, with processing for '
52
- 'spaces, quotes, and backslashes')
53
- parser.add_option('-X', '--extra-arg', dest='extra', action='append',
54
- metavar='ARG', help='Extra command-line argument')
55
-
56
- return parser
57
-
58
-
59
- def common_defaults(**kwargs):
60
- defaults = dict(par=_DEFAULT_PARALLELISM, timeout=_DEFAULT_TIMEOUT)
61
- defaults.update(**kwargs)
62
- envvars = [('user', 'PSSH_USER'),
63
- ('par', 'PSSH_PAR'),
64
- ('outdir', 'PSSH_OUTDIR'),
65
- ('errdir', 'PSSH_ERRDIR'),
66
- ('timeout', 'PSSH_TIMEOUT'),
67
- ('verbose', 'PSSH_VERBOSE'),
68
- ('print_out', 'PSSH_PRINT'),
69
- ('askpass', 'PSSH_ASKPASS'),
70
- ('inline', 'PSSH_INLINE'),
71
- ('recursive', 'PSSH_RECURSIVE'),
72
- ('archive', 'PSSH_ARCHIVE'),
73
- ('compress', 'PSSH_COMPRESS'),
74
- ('localdir', 'PSSH_LOCALDIR'),
75
- ]
76
- for option, var, in envvars:
77
- value = os.getenv(var)
78
- if value:
79
- defaults[option] = value
80
-
81
- value = os.getenv('PSSH_OPTIONS')
82
- if value:
83
- defaults['options'] = [value]
84
-
85
- value = os.getenv('PSSH_HOSTS')
86
- if value:
87
- message1 = ('Warning: the PSSH_HOSTS environment variable is '
88
- 'deprecated. Please use the "-h" option instead, and consider '
89
- 'creating aliases for convenience. For example:')
90
- message2 = " alias pssh_abc='pssh -h /path/to/hosts_abc'"
91
- sys.stderr.write(textwrap.fill(message1))
92
- sys.stderr.write('\n')
93
- sys.stderr.write(message2)
94
- sys.stderr.write('\n')
95
- defaults['host_files'] = [value]
96
-
97
- return defaults
98
-
99
-
100
- def shlex_append(option, opt_str, value, parser):
101
- """An optparse callback similar to the append action.
102
-
103
- The given value is processed with shlex, and the resulting list is
104
- concatenated to the option's dest list.
105
- """
106
- lst = getattr(parser.values, option.dest)
107
- if lst is None:
108
- lst = []
109
- setattr(parser.values, option.dest, lst)
110
- lst.extend(shlex.split(value))
data/exe/psshlib/color.py DELETED
@@ -1,39 +0,0 @@
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
@@ -1,345 +0,0 @@
1
- # Copyright (c) 2009-2012, Andrew McNabb
2
-
3
- from errno import EINTR
4
- import os
5
- import select
6
- import signal
7
- import sys
8
- import threading
9
-
10
- try:
11
- import queue
12
- except ImportError:
13
- import Queue as queue
14
-
15
- from psshlib.askpass_server import PasswordServer
16
- from psshlib import psshutil
17
-
18
- READ_SIZE = 1 << 16
19
-
20
-
21
- class FatalError(RuntimeError):
22
- """A fatal error in the PSSH Manager."""
23
- pass
24
-
25
-
26
- class Manager(object):
27
- """Executes tasks concurrently.
28
-
29
- Tasks are added with add_task() and executed in parallel with run().
30
- Returns a list of the exit statuses of the processes.
31
-
32
- Arguments:
33
- limit: Maximum number of commands running at once.
34
- timeout: Maximum allowed execution time in seconds.
35
- """
36
- def __init__(self, opts):
37
- self.limit = opts.par
38
- self.timeout = opts.timeout
39
- self.askpass = opts.askpass
40
- self.outdir = opts.outdir
41
- self.errdir = opts.errdir
42
- self.iomap = IOMap()
43
-
44
- self.taskcount = 0
45
- self.tasks = []
46
- self.running = []
47
- self.done = []
48
-
49
- self.askpass_socket = None
50
-
51
- def run(self):
52
- """Processes tasks previously added with add_task."""
53
- try:
54
- if self.outdir or self.errdir:
55
- writer = Writer(self.outdir, self.errdir)
56
- writer.start()
57
- else:
58
- writer = None
59
-
60
- if self.askpass:
61
- pass_server = PasswordServer()
62
- pass_server.start(self.iomap, self.limit)
63
- self.askpass_socket = pass_server.address
64
-
65
- self.set_sigchld_handler()
66
-
67
- try:
68
- self.update_tasks(writer)
69
- wait = None
70
- while self.running or self.tasks:
71
- # Opt for efficiency over subsecond timeout accuracy.
72
- if wait is None or wait < 1:
73
- wait = 1
74
- self.iomap.poll(wait)
75
- self.update_tasks(writer)
76
- wait = self.check_timeout()
77
- except KeyboardInterrupt:
78
- # This exception handler tries to clean things up and prints
79
- # out a nice status message for each interrupted host.
80
- self.interrupted()
81
-
82
- except KeyboardInterrupt:
83
- # This exception handler doesn't print out any fancy status
84
- # information--it just stops.
85
- pass
86
-
87
- if writer:
88
- writer.signal_quit()
89
- writer.join()
90
-
91
- statuses = [task.exitstatus for task in self.done]
92
- return statuses
93
-
94
- def clear_sigchld_handler(self):
95
- signal.signal(signal.SIGCHLD, signal.SIG_DFL)
96
-
97
- def set_sigchld_handler(self):
98
- # TODO: find out whether set_wakeup_fd still works if the default
99
- # signal handler is used (I'm pretty sure it doesn't work if the
100
- # signal is ignored).
101
- signal.signal(signal.SIGCHLD, self.handle_sigchld)
102
- # This should keep reads and writes from getting EINTR.
103
- if hasattr(signal, 'siginterrupt'):
104
- signal.siginterrupt(signal.SIGCHLD, False)
105
-
106
- def handle_sigchld(self, number, frame):
107
- """Apparently we need a sigchld handler to make set_wakeup_fd work."""
108
- # Write to the signal pipe (only for Python <2.5, where the
109
- # set_wakeup_fd method doesn't exist).
110
- if self.iomap.wakeup_writefd:
111
- os.write(self.iomap.wakeup_writefd, '\0')
112
- for task in self.running:
113
- if task.proc:
114
- task.proc.poll()
115
- # Apparently some UNIX systems automatically resent the SIGCHLD
116
- # handler to SIG_DFL. Reset it just in case.
117
- self.set_sigchld_handler()
118
-
119
- def add_task(self, task):
120
- """Adds a Task to be processed with run()."""
121
- self.tasks.append(task)
122
-
123
- def update_tasks(self, writer):
124
- """Reaps tasks and starts as many new ones as allowed."""
125
- # Mask signals to work around a Python bug:
126
- # http://bugs.python.org/issue1068268
127
- # Since sigprocmask isn't in the stdlib, clear the SIGCHLD handler.
128
- # Since signals are masked, reap_tasks needs to be called once for
129
- # each loop.
130
- keep_running = True
131
- while keep_running:
132
- self.clear_sigchld_handler()
133
- self._start_tasks_once(writer)
134
- self.set_sigchld_handler()
135
- keep_running = self.reap_tasks()
136
-
137
- def _start_tasks_once(self, writer):
138
- """Starts tasks once.
139
-
140
- Due to http://bugs.python.org/issue1068268, signals must be masked
141
- when this method is called.
142
- """
143
- while 0 < len(self.tasks) and len(self.running) < self.limit:
144
- task = self.tasks.pop(0)
145
- self.running.append(task)
146
- task.start(self.taskcount, self.iomap, writer, self.askpass_socket)
147
- self.taskcount += 1
148
-
149
- def reap_tasks(self):
150
- """Checks to see if any tasks have terminated.
151
-
152
- After cleaning up, returns the number of tasks that finished.
153
- """
154
- still_running = []
155
- finished_count = 0
156
- for task in self.running:
157
- if task.running():
158
- still_running.append(task)
159
- else:
160
- self.finished(task)
161
- finished_count += 1
162
- self.running = still_running
163
- return finished_count
164
-
165
- def check_timeout(self):
166
- """Kills timed-out processes and returns the lowest time left."""
167
- if self.timeout <= 0:
168
- return None
169
-
170
- min_timeleft = None
171
- for task in self.running:
172
- timeleft = self.timeout - task.elapsed()
173
- if timeleft <= 0:
174
- task.timedout()
175
- continue
176
- if min_timeleft is None or timeleft < min_timeleft:
177
- min_timeleft = timeleft
178
-
179
- if min_timeleft is None:
180
- return 0
181
- else:
182
- return max(0, min_timeleft)
183
-
184
- def interrupted(self):
185
- """Cleans up after a keyboard interrupt."""
186
- for task in self.running:
187
- task.interrupted()
188
- self.finished(task)
189
-
190
- for task in self.tasks:
191
- task.cancel()
192
- self.finished(task)
193
-
194
- def finished(self, task):
195
- """Marks a task as complete and reports its status to stdout."""
196
- self.done.append(task)
197
- n = len(self.done)
198
- task.report(n)
199
-
200
-
201
- class IOMap(object):
202
- """A manager for file descriptors and their associated handlers.
203
-
204
- The poll method dispatches events to the appropriate handlers.
205
- """
206
- def __init__(self):
207
- self.readmap = {}
208
- self.writemap = {}
209
-
210
- # Setup the wakeup file descriptor to avoid hanging on lost signals.
211
- wakeup_readfd, wakeup_writefd = os.pipe()
212
- self.register_read(wakeup_readfd, self.wakeup_handler)
213
- # TODO: remove test when we stop supporting Python <2.5
214
- if hasattr(signal, 'set_wakeup_fd'):
215
- signal.set_wakeup_fd(wakeup_writefd)
216
- self.wakeup_writefd = None
217
- else:
218
- self.wakeup_writefd = wakeup_writefd
219
-
220
- def register_read(self, fd, handler):
221
- """Registers an IO handler for a file descriptor for reading."""
222
- self.readmap[fd] = handler
223
-
224
- def register_write(self, fd, handler):
225
- """Registers an IO handler for a file descriptor for writing."""
226
- self.writemap[fd] = handler
227
-
228
- def unregister(self, fd):
229
- """Unregisters the given file descriptor."""
230
- if fd in self.readmap:
231
- del self.readmap[fd]
232
- if fd in self.writemap:
233
- del self.writemap[fd]
234
-
235
- def poll(self, timeout=None):
236
- """Performs a poll and dispatches the resulting events."""
237
- if not self.readmap and not self.writemap:
238
- return
239
- rlist = list(self.readmap)
240
- wlist = list(self.writemap)
241
- try:
242
- rlist, wlist, _ = select.select(rlist, wlist, [], timeout)
243
- except select.error:
244
- _, e, _ = sys.exc_info()
245
- errno = e.args[0]
246
- if errno == EINTR:
247
- return
248
- else:
249
- raise
250
- for fd in rlist:
251
- handler = self.readmap[fd]
252
- handler(fd, self)
253
- for fd in wlist:
254
- handler = self.writemap[fd]
255
- handler(fd, self)
256
-
257
- def wakeup_handler(self, fd, iomap):
258
- """Handles read events on the signal wakeup pipe.
259
-
260
- This ensures that SIGCHLD signals aren't lost.
261
- """
262
- try:
263
- os.read(fd, READ_SIZE)
264
- except (OSError, IOError):
265
- _, e, _ = sys.exc_info()
266
- errno, message = e.args
267
- if errno != EINTR:
268
- sys.stderr.write('Fatal error reading from wakeup pipe: %s\n'
269
- % message)
270
- raise FatalError
271
-
272
-
273
- class Writer(threading.Thread):
274
- """Thread that writes to files by processing requests from a Queue.
275
-
276
- Until AIO becomes widely available, it is impossible to make a nonblocking
277
- write to an ordinary file. The Writer thread processes all writing to
278
- ordinary files so that the main thread can work without blocking.
279
- """
280
- OPEN = object()
281
- EOF = object()
282
- ABORT = object()
283
-
284
- def __init__(self, outdir, errdir):
285
- threading.Thread.__init__(self)
286
- # A daemon thread automatically dies if the program is terminated.
287
- self.setDaemon(True)
288
- self.queue = queue.Queue()
289
- self.outdir = outdir
290
- self.errdir = errdir
291
-
292
- self.host_counts = {}
293
- self.files = {}
294
-
295
- def run(self):
296
- while True:
297
- filename, data = self.queue.get()
298
- if filename == self.ABORT:
299
- return
300
-
301
- if data == self.OPEN:
302
- self.files[filename] = open(filename, 'wb', buffering=1)
303
- psshutil.set_cloexec(self.files[filename])
304
- else:
305
- dest = self.files[filename]
306
- if data == self.EOF:
307
- dest.close()
308
- else:
309
- dest.write(data)
310
-
311
- def open_files(self, host):
312
- """Called from another thread to create files for stdout and stderr.
313
-
314
- Returns a pair of filenames (outfile, errfile). These filenames are
315
- used as handles for future operations. Either or both may be None if
316
- outdir or errdir or not set.
317
- """
318
- outfile = errfile = None
319
- if self.outdir or self.errdir:
320
- count = self.host_counts.get(host, 0)
321
- self.host_counts[host] = count + 1
322
- if count:
323
- filename = "%s.%s" % (host, count)
324
- else:
325
- filename = host
326
- if self.outdir:
327
- outfile = os.path.join(self.outdir, filename)
328
- self.queue.put((outfile, self.OPEN))
329
- if self.errdir:
330
- errfile = os.path.join(self.errdir, filename)
331
- self.queue.put((errfile, self.OPEN))
332
- return outfile, errfile
333
-
334
- def write(self, filename, data):
335
- """Called from another thread to enqueue a write."""
336
- self.queue.put((filename, data))
337
-
338
- def close(self, filename):
339
- """Called from another thread to close the given file."""
340
- self.queue.put((filename, self.EOF))
341
-
342
- def signal_quit(self):
343
- """Called from another thread to request the Writer to quit."""
344
- self.queue.put((self.ABORT, None))
345
-