~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/conch/scripts/cftp.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

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