OLD | NEW |
| (Empty) |
1 # -*- test-case-name: twisted.conch.test.test_cftp -*- | |
2 # | |
3 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
4 # See LICENSE for details. | |
5 | |
6 # | |
7 # $Id: cftp.py,v 1.65 2004/03/11 00:29:14 z3p Exp $ | |
8 | |
9 #""" Implementation module for the `cftp` command. | |
10 #""" | |
11 from twisted.conch.client import agent, connect, default, options | |
12 from twisted.conch.error import ConchError | |
13 from twisted.conch.ssh import connection, common | |
14 from twisted.conch.ssh import channel, filetransfer | |
15 from twisted.protocols import basic | |
16 from twisted.internet import reactor, stdio, defer, utils | |
17 from twisted.python import log, usage, failure | |
18 | |
19 import os, sys, getpass, struct, tty, fcntl, base64, signal, stat, errno | |
20 import fnmatch, pwd, time, glob | |
21 | |
22 class ClientOptions(options.ConchOptions): | |
23 | |
24 synopsis = """Usage: cftp [options] [user@]host | |
25 cftp [options] [user@]host[:dir[/]] | |
26 cftp [options] [user@]host[:file [localfile]] | |
27 """ | |
28 | |
29 optParameters = [ | |
30 ['buffersize', 'B', 32768, 'Size of the buffer to use for se
nding/receiving.'], | |
31 ['batchfile', 'b', None, 'File to read commands from, or \'-
\' for stdin.'], | |
32 ['requests', 'R', 5, 'Number of requests to make before wait
ing for a reply.'], | |
33 ['subsystem', 's', 'sftp', 'Subsystem/server program to conn
ect to.']] | |
34 zsh_altArgDescr = {"buffersize":"Size of send/receive buffer (default: 32768
)"} | |
35 #zsh_multiUse = ["foo", "bar"] | |
36 #zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")] | |
37 #zsh_actions = {"foo":'_files -g "*.foo"', "bar":"(one two three)"} | |
38 #zsh_actionDescr = {"logfile":"log file name", "random":"random seed"} | |
39 zsh_extras = ['2::localfile:{if [[ $words[1] == *:* ]]; then; _files; fi}'] | |
40 | |
41 def parseArgs(self, host, localPath=None): | |
42 self['remotePath'] = '' | |
43 if ':' in host: | |
44 host, self['remotePath'] = host.split(':', 1) | |
45 self['remotePath'].rstrip('/') | |
46 self['host'] = host | |
47 self['localPath'] = localPath | |
48 | |
49 def run(): | |
50 # import hotshot | |
51 # prof = hotshot.Profile('cftp.prof') | |
52 # prof.start() | |
53 args = sys.argv[1:] | |
54 if '-l' in args: # cvs is an idiot | |
55 i = args.index('-l') | |
56 args = args[i:i+2]+args | |
57 del args[i+2:i+4] | |
58 options = ClientOptions() | |
59 try: | |
60 options.parseOptions(args) | |
61 except usage.UsageError, u: | |
62 print 'ERROR: %s' % u | |
63 sys.exit(1) | |
64 if options['log']: | |
65 realout = sys.stdout | |
66 log.startLogging(sys.stderr) | |
67 sys.stdout = realout | |
68 else: | |
69 log.discardLogs() | |
70 doConnect(options) | |
71 reactor.run() | |
72 # prof.stop() | |
73 # prof.close() | |
74 | |
75 def handleError(): | |
76 from twisted.python import failure | |
77 global exitStatus | |
78 exitStatus = 2 | |
79 try: | |
80 reactor.stop() | |
81 except: pass | |
82 log.err(failure.Failure()) | |
83 raise | |
84 | |
85 def doConnect(options): | |
86 # log.deferr = handleError # HACK | |
87 if '@' in options['host']: | |
88 options['user'], options['host'] = options['host'].split('@',1) | |
89 host = options['host'] | |
90 if not options['user']: | |
91 options['user'] = getpass.getuser() | |
92 if not options['port']: | |
93 options['port'] = 22 | |
94 else: | |
95 options['port'] = int(options['port']) | |
96 host = options['host'] | |
97 port = options['port'] | |
98 conn = SSHConnection() | |
99 conn.options = options | |
100 vhk = default.verifyHostKey | |
101 uao = default.SSHUserAuthClient(options['user'], options, conn) | |
102 connect.connect(host, port, options, vhk, uao).addErrback(_ebExit) | |
103 | |
104 def _ebExit(f): | |
105 #global exitStatus | |
106 if hasattr(f.value, 'value'): | |
107 s = f.value.value | |
108 else: | |
109 s = str(f) | |
110 print s | |
111 #exitStatus = "conch: exiting with error %s" % f | |
112 try: | |
113 reactor.stop() | |
114 except: pass | |
115 | |
116 def _ignore(*args): pass | |
117 | |
118 class FileWrapper: | |
119 | |
120 def __init__(self, f): | |
121 self.f = f | |
122 self.total = 0.0 | |
123 f.seek(0, 2) # seek to the end | |
124 self.size = f.tell() | |
125 | |
126 def __getattr__(self, attr): | |
127 return getattr(self.f, attr) | |
128 | |
129 class StdioClient(basic.LineReceiver): | |
130 | |
131 ps = 'cftp> ' | |
132 delimiter = '\n' | |
133 | |
134 def __init__(self, client, f = None): | |
135 self.client = client | |
136 self.currentDirectory = '' | |
137 self.file = f | |
138 self.useProgressBar = (not f and 1) or 0 | |
139 | |
140 def connectionMade(self): | |
141 self.client.realPath('').addCallback(self._cbSetCurDir) | |
142 | |
143 def _cbSetCurDir(self, path): | |
144 self.currentDirectory = path | |
145 self._newLine() | |
146 | |
147 def lineReceived(self, line): | |
148 if self.client.transport.localClosed: | |
149 return | |
150 log.msg('got line %s' % repr(line)) | |
151 line = line.lstrip() | |
152 if not line: | |
153 self._newLine() | |
154 return | |
155 if self.file and line.startswith('-'): | |
156 self.ignoreErrors = 1 | |
157 line = line[1:] | |
158 else: | |
159 self.ignoreErrors = 0 | |
160 if ' ' in line: | |
161 command, rest = line.split(' ', 1) | |
162 rest = rest.lstrip() | |
163 else: | |
164 command, rest = line, '' | |
165 if command.startswith('!'): # command | |
166 f = self.cmd_EXEC | |
167 rest = (command[1:] + ' ' + rest).strip() | |
168 else: | |
169 command = command.upper() | |
170 log.msg('looking up cmd %s' % command) | |
171 f = getattr(self, 'cmd_%s' % command, None) | |
172 if f is not None: | |
173 d = defer.maybeDeferred(f, rest) | |
174 d.addCallback(self._cbCommand) | |
175 d.addErrback(self._ebCommand) | |
176 else: | |
177 self._ebCommand(failure.Failure(NotImplementedError( | |
178 "No command called `%s'" % command))) | |
179 self._newLine() | |
180 | |
181 def _printFailure(self, f): | |
182 log.msg(f) | |
183 e = f.trap(NotImplementedError, filetransfer.SFTPError, OSError, IOError
) | |
184 if e == NotImplementedError: | |
185 self.transport.write(self.cmd_HELP('')) | |
186 elif e == filetransfer.SFTPError: | |
187 self.transport.write("remote error %i: %s\n" % | |
188 (f.value.code, f.value.message)) | |
189 elif e in (OSError, IOError): | |
190 self.transport.write("local error %i: %s\n" % | |
191 (f.value.errno, f.value.strerror)) | |
192 | |
193 def _newLine(self): | |
194 if self.client.transport.localClosed: | |
195 return | |
196 self.transport.write(self.ps) | |
197 self.ignoreErrors = 0 | |
198 if self.file: | |
199 l = self.file.readline() | |
200 if not l: | |
201 self.client.transport.loseConnection() | |
202 else: | |
203 self.transport.write(l) | |
204 self.lineReceived(l.strip()) | |
205 | |
206 def _cbCommand(self, result): | |
207 if result is not None: | |
208 self.transport.write(result) | |
209 if not result.endswith('\n'): | |
210 self.transport.write('\n') | |
211 self._newLine() | |
212 | |
213 def _ebCommand(self, f): | |
214 self._printFailure(f) | |
215 if self.file and not self.ignoreErrors: | |
216 self.client.transport.loseConnection() | |
217 self._newLine() | |
218 | |
219 def cmd_CD(self, path): | |
220 path, rest = self._getFilename(path) | |
221 if not path.endswith('/'): | |
222 path += '/' | |
223 newPath = path and os.path.join(self.currentDirectory, path) or '' | |
224 d = self.client.openDirectory(newPath) | |
225 d.addCallback(self._cbCd) | |
226 d.addErrback(self._ebCommand) | |
227 return d | |
228 | |
229 def _cbCd(self, directory): | |
230 directory.close() | |
231 d = self.client.realPath(directory.name) | |
232 d.addCallback(self._cbCurDir) | |
233 return d | |
234 | |
235 def _cbCurDir(self, path): | |
236 self.currentDirectory = path | |
237 | |
238 def cmd_CHGRP(self, rest): | |
239 grp, rest = rest.split(None, 1) | |
240 path, rest = self._getFilename(rest) | |
241 grp = int(grp) | |
242 d = self.client.getAttrs(path) | |
243 d.addCallback(self._cbSetUsrGrp, path, grp=grp) | |
244 return d | |
245 | |
246 def cmd_CHMOD(self, rest): | |
247 mod, rest = rest.split(None, 1) | |
248 path, rest = self._getFilename(rest) | |
249 mod = int(mod, 8) | |
250 d = self.client.setAttrs(path, {'permissions':mod}) | |
251 d.addCallback(_ignore) | |
252 return d | |
253 | |
254 def cmd_CHOWN(self, rest): | |
255 usr, rest = rest.split(None, 1) | |
256 path, rest = self._getFilename(rest) | |
257 usr = int(usr) | |
258 d = self.client.getAttrs(path) | |
259 d.addCallback(self._cbSetUsrGrp, path, usr=usr) | |
260 return d | |
261 | |
262 def _cbSetUsrGrp(self, attrs, path, usr=None, grp=None): | |
263 new = {} | |
264 new['uid'] = (usr is not None) and usr or attrs['uid'] | |
265 new['gid'] = (grp is not None) and grp or attrs['gid'] | |
266 d = self.client.setAttrs(path, new) | |
267 d.addCallback(_ignore) | |
268 return d | |
269 | |
270 def cmd_GET(self, rest): | |
271 remote, rest = self._getFilename(rest) | |
272 if '*' in remote or '?' in remote: # wildcard | |
273 if rest: | |
274 local, rest = self._getFilename(rest) | |
275 if not os.path.isdir(local): | |
276 return "Wildcard get with non-directory target." | |
277 else: | |
278 local = '' | |
279 d = self._remoteGlob(remote) | |
280 d.addCallback(self._cbGetMultiple, local) | |
281 return d | |
282 if rest: | |
283 local, rest = self._getFilename(rest) | |
284 else: | |
285 local = os.path.split(remote)[1] | |
286 log.msg((remote, local)) | |
287 lf = file(local, 'w', 0) | |
288 path = os.path.join(self.currentDirectory, remote) | |
289 d = self.client.openFile(path, filetransfer.FXF_READ, {}) | |
290 d.addCallback(self._cbGetOpenFile, lf) | |
291 d.addErrback(self._ebCloseLf, lf) | |
292 return d | |
293 | |
294 def _cbGetMultiple(self, files, local): | |
295 #if self._useProgressBar: # one at a time | |
296 # XXX this can be optimized for times w/o progress bar | |
297 return self._cbGetMultipleNext(None, files, local) | |
298 | |
299 def _cbGetMultipleNext(self, res, files, local): | |
300 if isinstance(res, failure.Failure): | |
301 self._printFailure(res) | |
302 elif res: | |
303 self.transport.write(res) | |
304 if not res.endswith('\n'): | |
305 self.transport.write('\n') | |
306 if not files: | |
307 return | |
308 f = files.pop(0)[0] | |
309 lf = file(os.path.join(local, os.path.split(f)[1]), 'w', 0) | |
310 path = os.path.join(self.currentDirectory, f) | |
311 d = self.client.openFile(path, filetransfer.FXF_READ, {}) | |
312 d.addCallback(self._cbGetOpenFile, lf) | |
313 d.addErrback(self._ebCloseLf, lf) | |
314 d.addBoth(self._cbGetMultipleNext, files, local) | |
315 return d | |
316 | |
317 def _ebCloseLf(self, f, lf): | |
318 lf.close() | |
319 return f | |
320 | |
321 def _cbGetOpenFile(self, rf, lf): | |
322 return rf.getAttrs().addCallback(self._cbGetFileSize, rf, lf) | |
323 | |
324 def _cbGetFileSize(self, attrs, rf, lf): | |
325 if not stat.S_ISREG(attrs['permissions']): | |
326 rf.close() | |
327 lf.close() | |
328 return "Can't get non-regular file: %s" % rf.name | |
329 rf.size = attrs['size'] | |
330 bufferSize = self.client.transport.conn.options['buffersize'] | |
331 numRequests = self.client.transport.conn.options['requests'] | |
332 rf.total = 0.0 | |
333 dList = [] | |
334 chunks = [] | |
335 startTime = time.time() | |
336 for i in range(numRequests): | |
337 d = self._cbGetRead('', rf, lf, chunks, 0, bufferSize, startTime) | |
338 dList.append(d) | |
339 dl = defer.DeferredList(dList, fireOnOneErrback=1) | |
340 dl.addCallback(self._cbGetDone, rf, lf) | |
341 return dl | |
342 | |
343 def _getNextChunk(self, chunks): | |
344 end = 0 | |
345 for chunk in chunks: | |
346 if end == 'eof': | |
347 return # nothing more to get | |
348 if end != chunk[0]: | |
349 i = chunks.index(chunk) | |
350 chunks.insert(i, (end, chunk[0])) | |
351 return (end, chunk[0] - end) | |
352 end = chunk[1] | |
353 bufSize = int(self.client.transport.conn.options['buffersize']) | |
354 chunks.append((end, end + bufSize)) | |
355 return (end, bufSize) | |
356 | |
357 def _cbGetRead(self, data, rf, lf, chunks, start, size, startTime): | |
358 if data and isinstance(data, failure.Failure): | |
359 log.msg('get read err: %s' % data) | |
360 reason = data | |
361 reason.trap(EOFError) | |
362 i = chunks.index((start, start + size)) | |
363 del chunks[i] | |
364 chunks.insert(i, (start, 'eof')) | |
365 elif data: | |
366 log.msg('get read data: %i' % len(data)) | |
367 lf.seek(start) | |
368 lf.write(data) | |
369 if len(data) != size: | |
370 log.msg('got less than we asked for: %i < %i' % | |
371 (len(data), size)) | |
372 i = chunks.index((start, start + size)) | |
373 del chunks[i] | |
374 chunks.insert(i, (start, start + len(data))) | |
375 rf.total += len(data) | |
376 if self.useProgressBar: | |
377 self._printProgessBar(rf, startTime) | |
378 chunk = self._getNextChunk(chunks) | |
379 if not chunk: | |
380 return | |
381 else: | |
382 start, length = chunk | |
383 log.msg('asking for %i -> %i' % (start, start+length)) | |
384 d = rf.readChunk(start, length) | |
385 d.addBoth(self._cbGetRead, rf, lf, chunks, start, length, startTime) | |
386 return d | |
387 | |
388 def _cbGetDone(self, ignored, rf, lf): | |
389 log.msg('get done') | |
390 rf.close() | |
391 lf.close() | |
392 if self.useProgressBar: | |
393 self.transport.write('\n') | |
394 return "Transferred %s to %s" % (rf.name, lf.name) | |
395 | |
396 def cmd_PUT(self, rest): | |
397 local, rest = self._getFilename(rest) | |
398 if '*' in local or '?' in local: # wildcard | |
399 if rest: | |
400 remote, rest = self._getFilename(rest) | |
401 path = os.path.join(self.currentDirectory, remote) | |
402 d = self.client.getAttrs(path) | |
403 d.addCallback(self._cbPutTargetAttrs, remote, local) | |
404 return d | |
405 else: | |
406 remote = '' | |
407 files = glob.glob(local) | |
408 return self._cbPutMultipleNext(None, files, remote) | |
409 if rest: | |
410 remote, rest = self._getFilename(rest) | |
411 else: | |
412 remote = os.path.split(local)[1] | |
413 lf = file(local, 'r') | |
414 path = os.path.join(self.currentDirectory, remote) | |
415 d = self.client.openFile(path, filetransfer.FXF_WRITE|filetransfer.FXF_C
REAT, {}) | |
416 d.addCallback(self._cbPutOpenFile, lf) | |
417 d.addErrback(self._ebCloseLf, lf) | |
418 return d | |
419 | |
420 def _cbPutTargetAttrs(self, attrs, path, local): | |
421 if not stat.S_ISDIR(attrs['permissions']): | |
422 return "Wildcard put with non-directory target." | |
423 return self._cbPutMultipleNext(None, files, path) | |
424 | |
425 def _cbPutMultipleNext(self, res, files, path): | |
426 if isinstance(res, failure.Failure): | |
427 self._printFailure(res) | |
428 elif res: | |
429 self.transport.write(res) | |
430 if not res.endswith('\n'): | |
431 self.transport.write('\n') | |
432 f = None | |
433 while files and not f: | |
434 try: | |
435 f = files.pop(0) | |
436 lf = file(f, 'r') | |
437 except: | |
438 self._printFailure(failure.Failure()) | |
439 f = None | |
440 if not f: | |
441 return | |
442 name = os.path.split(f)[1] | |
443 remote = os.path.join(self.currentDirectory, path, name) | |
444 log.msg((name, remote, path)) | |
445 d = self.client.openFile(remote, filetransfer.FXF_WRITE|filetransfer.FXF
_CREAT, {}) | |
446 d.addCallback(self._cbPutOpenFile, lf) | |
447 d.addErrback(self._ebCloseLf, lf) | |
448 d.addBoth(self._cbPutMultipleNext, files, path) | |
449 return d | |
450 | |
451 def _cbPutOpenFile(self, rf, lf): | |
452 numRequests = self.client.transport.conn.options['requests'] | |
453 if self.useProgressBar: | |
454 lf = FileWrapper(lf) | |
455 dList = [] | |
456 chunks = [] | |
457 startTime = time.time() | |
458 for i in range(numRequests): | |
459 d = self._cbPutWrite(None, rf, lf, chunks, startTime) | |
460 if d: | |
461 dList.append(d) | |
462 dl = defer.DeferredList(dList, fireOnOneErrback=1) | |
463 dl.addCallback(self._cbPutDone, rf, lf) | |
464 return dl | |
465 | |
466 def _cbPutWrite(self, ignored, rf, lf, chunks, startTime): | |
467 chunk = self._getNextChunk(chunks) | |
468 start, size = chunk | |
469 lf.seek(start) | |
470 data = lf.read(size) | |
471 if self.useProgressBar: | |
472 lf.total += len(data) | |
473 self._printProgessBar(lf, startTime) | |
474 if data: | |
475 d = rf.writeChunk(start, data) | |
476 d.addCallback(self._cbPutWrite, rf, lf, chunks, startTime) | |
477 return d | |
478 else: | |
479 return | |
480 | |
481 def _cbPutDone(self, ignored, rf, lf): | |
482 lf.close() | |
483 rf.close() | |
484 if self.useProgressBar: | |
485 self.transport.write('\n') | |
486 return 'Transferred %s to %s' % (lf.name, rf.name) | |
487 | |
488 def cmd_LCD(self, path): | |
489 os.chdir(path) | |
490 | |
491 def cmd_LN(self, rest): | |
492 linkpath, rest = self._getFilename(rest) | |
493 targetpath, rest = self._getFilename(rest) | |
494 linkpath, targetpath = map( | |
495 lambda x: os.path.join(self.currentDirectory, x), | |
496 (linkpath, targetpath)) | |
497 return self.client.makeLink(linkpath, targetpath).addCallback(_ignore) | |
498 | |
499 def cmd_LS(self, rest): | |
500 # possible lines: | |
501 # ls current directory | |
502 # ls name_of_file that file | |
503 # ls name_of_directory that directory | |
504 # ls some_glob_string current directory, globbed for that string | |
505 options = [] | |
506 rest = rest.split() | |
507 while rest and rest[0] and rest[0][0] == '-': | |
508 opts = rest.pop(0)[1:] | |
509 for o in opts: | |
510 if o == 'l': | |
511 options.append('verbose') | |
512 elif o == 'a': | |
513 options.append('all') | |
514 rest = ' '.join(rest) | |
515 path, rest = self._getFilename(rest) | |
516 if not path: | |
517 fullPath = self.currentDirectory + '/' | |
518 else: | |
519 fullPath = os.path.join(self.currentDirectory, path) | |
520 d = self._remoteGlob(fullPath) | |
521 d.addCallback(self._cbDisplayFiles, options) | |
522 return d | |
523 | |
524 def _cbDisplayFiles(self, files, options): | |
525 files.sort() | |
526 if 'all' not in options: | |
527 files = [f for f in files if not f[0].startswith('.')] | |
528 if 'verbose' in options: | |
529 lines = [f[1] for f in files] | |
530 else: | |
531 lines = [f[0] for f in files] | |
532 if not lines: | |
533 return None | |
534 else: | |
535 return '\n'.join(lines) | |
536 | |
537 def cmd_MKDIR(self, path): | |
538 path, rest = self._getFilename(path) | |
539 path = os.path.join(self.currentDirectory, path) | |
540 return self.client.makeDirectory(path, {}).addCallback(_ignore) | |
541 | |
542 def cmd_RMDIR(self, path): | |
543 path, rest = self._getFilename(path) | |
544 path = os.path.join(self.currentDirectory, path) | |
545 return self.client.removeDirectory(path).addCallback(_ignore) | |
546 | |
547 def cmd_LMKDIR(self, path): | |
548 os.system("mkdir %s" % path) | |
549 | |
550 def cmd_RM(self, path): | |
551 path, rest = self._getFilename(path) | |
552 path = os.path.join(self.currentDirectory, path) | |
553 return self.client.removeFile(path).addCallback(_ignore) | |
554 | |
555 def cmd_LLS(self, rest): | |
556 os.system("ls %s" % rest) | |
557 | |
558 def cmd_RENAME(self, rest): | |
559 oldpath, rest = self._getFilename(rest) | |
560 newpath, rest = self._getFilename(rest) | |
561 oldpath, newpath = map ( | |
562 lambda x: os.path.join(self.currentDirectory, x), | |
563 (oldpath, newpath)) | |
564 return self.client.renameFile(oldpath, newpath).addCallback(_ignore) | |
565 | |
566 def cmd_EXIT(self, ignored): | |
567 self.client.transport.loseConnection() | |
568 | |
569 cmd_QUIT = cmd_EXIT | |
570 | |
571 def cmd_VERSION(self, ignored): | |
572 return "SFTP version %i" % self.client.version | |
573 | |
574 def cmd_HELP(self, ignored): | |
575 return """Available commands: | |
576 cd path Change remote directory to 'path'. | |
577 chgrp gid path Change gid of 'path' to 'gid'. | |
578 chmod mode path Change mode of 'path' to 'mode'. | |
579 chown uid path Change uid of 'path' to 'uid'. | |
580 exit Disconnect from the server. | |
581 get remote-path [local-path] Get remote file. | |
582 help Get a list of available commands. | |
583 lcd path Change local directory to 'path'. | |
584 lls [ls-options] [path] Display local directory listing. | |
585 lmkdir path Create local directory. | |
586 ln linkpath targetpath Symlink remote file. | |
587 lpwd Print the local working directory. | |
588 ls [-l] [path] Display remote directory listing. | |
589 mkdir path Create remote directory. | |
590 progress Toggle progress bar. | |
591 put local-path [remote-path] Put local file. | |
592 pwd Print the remote working directory. | |
593 quit Disconnect from the server. | |
594 rename oldpath newpath Rename remote file. | |
595 rmdir path Remove remote directory. | |
596 rm path Remove remote file. | |
597 version Print the SFTP version. | |
598 ? Synonym for 'help'. | |
599 """ | |
600 | |
601 def cmd_PWD(self, ignored): | |
602 return self.currentDirectory | |
603 | |
604 def cmd_LPWD(self, ignored): | |
605 return os.getcwd() | |
606 | |
607 def cmd_PROGRESS(self, ignored): | |
608 self.useProgressBar = not self.useProgressBar | |
609 return "%ssing progess bar." % (self.useProgressBar and "U" or "Not u") | |
610 | |
611 def cmd_EXEC(self, rest): | |
612 shell = pwd.getpwnam(getpass.getuser())[6] | |
613 print repr(rest) | |
614 if rest: | |
615 cmds = ['-c', rest] | |
616 return utils.getProcessOutput(shell, cmds, errortoo=1) | |
617 else: | |
618 os.system(shell) | |
619 | |
620 # accessory functions | |
621 | |
622 def _remoteGlob(self, fullPath): | |
623 log.msg('looking up %s' % fullPath) | |
624 head, tail = os.path.split(fullPath) | |
625 if '*' in tail or '?' in tail: | |
626 glob = 1 | |
627 else: | |
628 glob = 0 | |
629 if tail and not glob: # could be file or directory | |
630 # try directory first | |
631 d = self.client.openDirectory(fullPath) | |
632 d.addCallback(self._cbOpenList, '') | |
633 d.addErrback(self._ebNotADirectory, head, tail) | |
634 else: | |
635 d = self.client.openDirectory(head) | |
636 d.addCallback(self._cbOpenList, tail) | |
637 return d | |
638 | |
639 def _cbOpenList(self, directory, glob): | |
640 files = [] | |
641 d = directory.read() | |
642 d.addBoth(self._cbReadFile, files, directory, glob) | |
643 return d | |
644 | |
645 def _ebNotADirectory(self, reason, path, glob): | |
646 d = self.client.openDirectory(path) | |
647 d.addCallback(self._cbOpenList, glob) | |
648 return d | |
649 | |
650 def _cbReadFile(self, files, l, directory, glob): | |
651 if not isinstance(files, failure.Failure): | |
652 if glob: | |
653 l.extend([f for f in files if fnmatch.fnmatch(f[0], glob)]) | |
654 else: | |
655 l.extend(files) | |
656 d = directory.read() | |
657 d.addBoth(self._cbReadFile, l, directory, glob) | |
658 return d | |
659 else: | |
660 reason = files | |
661 reason.trap(EOFError) | |
662 directory.close() | |
663 return l | |
664 | |
665 def _abbrevSize(self, size): | |
666 # from http://mail.python.org/pipermail/python-list/1999-December/018395
.html | |
667 _abbrevs = [ | |
668 (1<<50L, 'PB'), | |
669 (1<<40L, 'TB'), | |
670 (1<<30L, 'GB'), | |
671 (1<<20L, 'MB'), | |
672 (1<<10L, 'kb'), | |
673 (1, '') | |
674 ] | |
675 | |
676 for factor, suffix in _abbrevs: | |
677 if size > factor: | |
678 break | |
679 return '%.1f' % (size/factor) + suffix | |
680 | |
681 def _abbrevTime(self, t): | |
682 if t > 3600: # 1 hour | |
683 hours = int(t / 3600) | |
684 t -= (3600 * hours) | |
685 mins = int(t / 60) | |
686 t -= (60 * mins) | |
687 return "%i:%02i:%02i" % (hours, mins, t) | |
688 else: | |
689 mins = int(t/60) | |
690 t -= (60 * mins) | |
691 return "%02i:%02i" % (mins, t) | |
692 | |
693 def _printProgessBar(self, f, startTime): | |
694 diff = time.time() - startTime | |
695 total = f.total | |
696 try: | |
697 winSize = struct.unpack('4H', | |
698 fcntl.ioctl(0, tty.TIOCGWINSZ, '12345679')) | |
699 except IOError: | |
700 winSize = [None, 80] | |
701 speed = total/diff | |
702 if speed: | |
703 timeLeft = (f.size - total) / speed | |
704 else: | |
705 timeLeft = 0 | |
706 front = f.name | |
707 back = '%3i%% %s %sps %s ' % ((total/f.size)*100, self._abbrevSize(total
), | |
708 self._abbrevSize(total/diff), self._abbrevTime(timeLeft)) | |
709 spaces = (winSize[1] - (len(front) + len(back) + 1)) * ' ' | |
710 self.transport.write('\r%s%s%s' % (front, spaces, back)) | |
711 | |
712 def _getFilename(self, line): | |
713 line.lstrip() | |
714 if not line: | |
715 return None, '' | |
716 if line[0] in '\'"': | |
717 ret = [] | |
718 line = list(line) | |
719 try: | |
720 for i in range(1,len(line)): | |
721 c = line[i] | |
722 if c == line[0]: | |
723 return ''.join(ret), ''.join(line[i+1:]).lstrip() | |
724 elif c == '\\': # quoted character | |
725 del line[i] | |
726 if line[i] not in '\'"\\': | |
727 raise IndexError, "bad quote: \\%s" % line[i] | |
728 ret.append(line[i]) | |
729 else: | |
730 ret.append(line[i]) | |
731 except IndexError: | |
732 raise IndexError, "unterminated quote" | |
733 ret = line.split(None, 1) | |
734 if len(ret) == 1: | |
735 return ret[0], '' | |
736 else: | |
737 return ret | |
738 | |
739 StdioClient.__dict__['cmd_?'] = StdioClient.cmd_HELP | |
740 | |
741 class SSHConnection(connection.SSHConnection): | |
742 def serviceStarted(self): | |
743 self.openChannel(SSHSession()) | |
744 | |
745 class SSHSession(channel.SSHChannel): | |
746 | |
747 name = 'session' | |
748 | |
749 def channelOpen(self, foo): | |
750 log.msg('session %s open' % self.id) | |
751 if self.conn.options['subsystem'].startswith('/'): | |
752 request = 'exec' | |
753 else: | |
754 request = 'subsystem' | |
755 d = self.conn.sendRequest(self, request, \ | |
756 common.NS(self.conn.options['subsystem']), wantReply=1) | |
757 d.addCallback(self._cbSubsystem) | |
758 d.addErrback(_ebExit) | |
759 | |
760 def _cbSubsystem(self, result): | |
761 self.client = filetransfer.FileTransferClient() | |
762 self.client.makeConnection(self) | |
763 self.dataReceived = self.client.dataReceived | |
764 f = None | |
765 if self.conn.options['batchfile']: | |
766 fn = self.conn.options['batchfile'] | |
767 if fn != '-': | |
768 f = file(fn) | |
769 self.stdio = stdio.StandardIO(StdioClient(self.client, f)) | |
770 | |
771 def extReceived(self, t, data): | |
772 if t==connection.EXTENDED_DATA_STDERR: | |
773 log.msg('got %s stderr data' % len(data)) | |
774 sys.stderr.write(data) | |
775 sys.stderr.flush() | |
776 | |
777 def eofReceived(self): | |
778 log.msg('got eof') | |
779 self.stdio.closeStdin() | |
780 | |
781 def closeReceived(self): | |
782 log.msg('remote side closed %s' % self) | |
783 self.conn.sendClose(self) | |
784 | |
785 def closed(self): | |
786 try: | |
787 reactor.stop() | |
788 except: | |
789 pass | |
790 | |
791 def stopWriting(self): | |
792 self.stdio.pauseProducing() | |
793 | |
794 def startWriting(self): | |
795 self.stdio.resumeProducing() | |
796 | |
797 if __name__ == '__main__': | |
798 run() | |
799 | |
OLD | NEW |