| 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 |