1 # -*- test-case-name: twisted.conch.test.test_filetransfer -*-
3 # Copyright (c) Twisted Matrix Laboratories.
4 # See LICENSE for details.
9 from twisted.internet import defer, protocol
10 from twisted.python import failure, log
12 from common import NS, getNS
13 from twisted.conch.interfaces import ISFTPServer, ISFTPFile
15 from zope import interface
19 class FileTransferBase(protocol.Protocol):
27 self.otherVersion = None # this gets set
29 def sendPacket(self, kind, data):
30 self.transport.write(struct.pack('!LB', len(data)+1, kind) + data)
32 def dataReceived(self, data):
34 while len(self.buf) > 5:
35 length, kind = struct.unpack('!LB', self.buf[:5])
36 if len(self.buf) < 4 + length:
38 data, self.buf = self.buf[5:4+length], self.buf[4+length:]
39 packetType = self.packetTypes.get(kind, None)
41 log.msg('no packet type for', kind)
43 f = getattr(self, 'packet_%s' % packetType, None)
45 log.msg('not implemented: %s' % packetType)
46 log.msg(repr(data[4:]))
47 reqId, = struct.unpack('!L', data[:4])
48 self._sendStatus(reqId, FX_OP_UNSUPPORTED,
49 "don't understand %s" % packetType)
57 reqId ,= struct.unpack('!L', data[:4])
58 self._ebStatus(failure.Failure(e), reqId)
60 def _parseAttributes(self, data):
61 flags ,= struct.unpack('!L', data[:4])
64 if flags & FILEXFER_ATTR_SIZE == FILEXFER_ATTR_SIZE:
65 size ,= struct.unpack('!Q', data[:8])
68 if flags & FILEXFER_ATTR_OWNERGROUP == FILEXFER_ATTR_OWNERGROUP:
69 uid, gid = struct.unpack('!2L', data[:8])
73 if flags & FILEXFER_ATTR_PERMISSIONS == FILEXFER_ATTR_PERMISSIONS:
74 perms ,= struct.unpack('!L', data[:4])
75 attrs['permissions'] = perms
77 if flags & FILEXFER_ATTR_ACMODTIME == FILEXFER_ATTR_ACMODTIME:
78 atime, mtime = struct.unpack('!2L', data[:8])
79 attrs['atime'] = atime
80 attrs['mtime'] = mtime
82 if flags & FILEXFER_ATTR_EXTENDED == FILEXFER_ATTR_EXTENDED:
83 extended_count ,= struct.unpack('!L', data[:4])
85 for i in xrange(extended_count):
86 extended_type, data = getNS(data)
87 extended_data, data = getNS(data)
88 attrs['ext_%s' % extended_type] = extended_data
91 def _packAttributes(self, attrs):
95 data += struct.pack('!Q', attrs['size'])
96 flags |= FILEXFER_ATTR_SIZE
97 if 'uid' in attrs and 'gid' in attrs:
98 data += struct.pack('!2L', attrs['uid'], attrs['gid'])
99 flags |= FILEXFER_ATTR_OWNERGROUP
100 if 'permissions' in attrs:
101 data += struct.pack('!L', attrs['permissions'])
102 flags |= FILEXFER_ATTR_PERMISSIONS
103 if 'atime' in attrs and 'mtime' in attrs:
104 data += struct.pack('!2L', attrs['atime'], attrs['mtime'])
105 flags |= FILEXFER_ATTR_ACMODTIME
108 if k.startswith('ext_'):
110 ext_data = NS(attrs[k])
111 extended.append(ext_type+ext_data)
113 data += struct.pack('!L', len(extended))
114 data += ''.join(extended)
115 flags |= FILEXFER_ATTR_EXTENDED
116 return struct.pack('!L', flags) + data
118 class FileTransferServer(FileTransferBase):
120 def __init__(self, data=None, avatar=None):
121 FileTransferBase.__init__(self)
122 self.client = ISFTPServer(avatar) # yay interfaces
126 def packet_INIT(self, data):
127 version ,= struct.unpack('!L', data[:4])
128 self.version = min(list(self.versions) + [version])
132 ext_name, data = getNS(data)
133 ext_data, data = getNS(data)
134 ext[ext_name] = ext_data
135 our_ext = self.client.gotVersion(version, ext)
137 for (k,v) in our_ext.items():
138 our_ext_data += NS(k) + NS(v)
139 self.sendPacket(FXP_VERSION, struct.pack('!L', self.version) + \
142 def packet_OPEN(self, data):
145 filename, data = getNS(data)
146 flags ,= struct.unpack('!L', data[:4])
148 attrs, data = self._parseAttributes(data)
149 assert data == '', 'still have data in OPEN: %s' % repr(data)
150 d = defer.maybeDeferred(self.client.openFile, filename, flags, attrs)
151 d.addCallback(self._cbOpenFile, requestId)
152 d.addErrback(self._ebStatus, requestId, "open failed")
154 def _cbOpenFile(self, fileObj, requestId):
155 fileId = str(hash(fileObj))
156 if fileId in self.openFiles:
157 raise KeyError, 'id already open'
158 self.openFiles[fileId] = fileObj
159 self.sendPacket(FXP_HANDLE, requestId + NS(fileId))
161 def packet_CLOSE(self, data):
164 handle, data = getNS(data)
165 assert data == '', 'still have data in CLOSE: %s' % repr(data)
166 if handle in self.openFiles:
167 fileObj = self.openFiles[handle]
168 d = defer.maybeDeferred(fileObj.close)
169 d.addCallback(self._cbClose, handle, requestId)
170 d.addErrback(self._ebStatus, requestId, "close failed")
171 elif handle in self.openDirs:
172 dirObj = self.openDirs[handle][0]
173 d = defer.maybeDeferred(dirObj.close)
174 d.addCallback(self._cbClose, handle, requestId, 1)
175 d.addErrback(self._ebStatus, requestId, "close failed")
177 self._ebClose(failure.Failure(KeyError()), requestId)
179 def _cbClose(self, result, handle, requestId, isDir = 0):
181 del self.openDirs[handle]
183 del self.openFiles[handle]
184 self._sendStatus(requestId, FX_OK, 'file closed')
186 def packet_READ(self, data):
189 handle, data = getNS(data)
190 (offset, length), data = struct.unpack('!QL', data[:12]), data[12:]
191 assert data == '', 'still have data in READ: %s' % repr(data)
192 if handle not in self.openFiles:
193 self._ebRead(failure.Failure(KeyError()), requestId)
195 fileObj = self.openFiles[handle]
196 d = defer.maybeDeferred(fileObj.readChunk, offset, length)
197 d.addCallback(self._cbRead, requestId)
198 d.addErrback(self._ebStatus, requestId, "read failed")
200 def _cbRead(self, result, requestId):
201 if result == '': # python's read will return this for EOF
203 self.sendPacket(FXP_DATA, requestId + NS(result))
205 def packet_WRITE(self, data):
208 handle, data = getNS(data)
209 offset, = struct.unpack('!Q', data[:8])
211 writeData, data = getNS(data)
212 assert data == '', 'still have data in WRITE: %s' % repr(data)
213 if handle not in self.openFiles:
214 self._ebWrite(failure.Failure(KeyError()), requestId)
216 fileObj = self.openFiles[handle]
217 d = defer.maybeDeferred(fileObj.writeChunk, offset, writeData)
218 d.addCallback(self._cbStatus, requestId, "write succeeded")
219 d.addErrback(self._ebStatus, requestId, "write failed")
221 def packet_REMOVE(self, data):
224 filename, data = getNS(data)
225 assert data == '', 'still have data in REMOVE: %s' % repr(data)
226 d = defer.maybeDeferred(self.client.removeFile, filename)
227 d.addCallback(self._cbStatus, requestId, "remove succeeded")
228 d.addErrback(self._ebStatus, requestId, "remove failed")
230 def packet_RENAME(self, data):
233 oldPath, data = getNS(data)
234 newPath, data = getNS(data)
235 assert data == '', 'still have data in RENAME: %s' % repr(data)
236 d = defer.maybeDeferred(self.client.renameFile, oldPath, newPath)
237 d.addCallback(self._cbStatus, requestId, "rename succeeded")
238 d.addErrback(self._ebStatus, requestId, "rename failed")
240 def packet_MKDIR(self, data):
243 path, data = getNS(data)
244 attrs, data = self._parseAttributes(data)
245 assert data == '', 'still have data in MKDIR: %s' % repr(data)
246 d = defer.maybeDeferred(self.client.makeDirectory, path, attrs)
247 d.addCallback(self._cbStatus, requestId, "mkdir succeeded")
248 d.addErrback(self._ebStatus, requestId, "mkdir failed")
250 def packet_RMDIR(self, data):
253 path, data = getNS(data)
254 assert data == '', 'still have data in RMDIR: %s' % repr(data)
255 d = defer.maybeDeferred(self.client.removeDirectory, path)
256 d.addCallback(self._cbStatus, requestId, "rmdir succeeded")
257 d.addErrback(self._ebStatus, requestId, "rmdir failed")
259 def packet_OPENDIR(self, data):
262 path, data = getNS(data)
263 assert data == '', 'still have data in OPENDIR: %s' % repr(data)
264 d = defer.maybeDeferred(self.client.openDirectory, path)
265 d.addCallback(self._cbOpenDirectory, requestId)
266 d.addErrback(self._ebStatus, requestId, "opendir failed")
268 def _cbOpenDirectory(self, dirObj, requestId):
269 handle = str(hash(dirObj))
270 if handle in self.openDirs:
271 raise KeyError, "already opened this directory"
272 self.openDirs[handle] = [dirObj, iter(dirObj)]
273 self.sendPacket(FXP_HANDLE, requestId + NS(handle))
275 def packet_READDIR(self, data):
278 handle, data = getNS(data)
279 assert data == '', 'still have data in READDIR: %s' % repr(data)
280 if handle not in self.openDirs:
281 self._ebStatus(failure.Failure(KeyError()), requestId)
283 dirObj, dirIter = self.openDirs[handle]
284 d = defer.maybeDeferred(self._scanDirectory, dirIter, [])
285 d.addCallback(self._cbSendDirectory, requestId)
286 d.addErrback(self._ebStatus, requestId, "scan directory failed")
288 def _scanDirectory(self, dirIter, f):
291 info = dirIter.next()
292 except StopIteration:
296 if isinstance(info, defer.Deferred):
297 info.addCallback(self._cbScanDirectory, dirIter, f)
303 def _cbScanDirectory(self, result, dirIter, f):
305 return self._scanDirectory(dirIter, f)
307 def _cbSendDirectory(self, result, requestId):
309 for (filename, longname, attrs) in result:
312 data += self._packAttributes(attrs)
313 self.sendPacket(FXP_NAME, requestId +
314 struct.pack('!L', len(result))+data)
316 def packet_STAT(self, data, followLinks = 1):
319 path, data = getNS(data)
320 assert data == '', 'still have data in STAT/LSTAT: %s' % repr(data)
321 d = defer.maybeDeferred(self.client.getAttrs, path, followLinks)
322 d.addCallback(self._cbStat, requestId)
323 d.addErrback(self._ebStatus, requestId, 'stat/lstat failed')
325 def packet_LSTAT(self, data):
326 self.packet_STAT(data, 0)
328 def packet_FSTAT(self, data):
331 handle, data = getNS(data)
332 assert data == '', 'still have data in FSTAT: %s' % repr(data)
333 if handle not in self.openFiles:
334 self._ebStatus(failure.Failure(KeyError('%s not in self.openFiles'
335 % handle)), requestId)
337 fileObj = self.openFiles[handle]
338 d = defer.maybeDeferred(fileObj.getAttrs)
339 d.addCallback(self._cbStat, requestId)
340 d.addErrback(self._ebStatus, requestId, 'fstat failed')
342 def _cbStat(self, result, requestId):
343 data = requestId + self._packAttributes(result)
344 self.sendPacket(FXP_ATTRS, data)
346 def packet_SETSTAT(self, data):
349 path, data = getNS(data)
350 attrs, data = self._parseAttributes(data)
352 log.msg('WARN: still have data in SETSTAT: %s' % repr(data))
353 d = defer.maybeDeferred(self.client.setAttrs, path, attrs)
354 d.addCallback(self._cbStatus, requestId, 'setstat succeeded')
355 d.addErrback(self._ebStatus, requestId, 'setstat failed')
357 def packet_FSETSTAT(self, data):
360 handle, data = getNS(data)
361 attrs, data = self._parseAttributes(data)
362 assert data == '', 'still have data in FSETSTAT: %s' % repr(data)
363 if handle not in self.openFiles:
364 self._ebStatus(failure.Failure(KeyError()), requestId)
366 fileObj = self.openFiles[handle]
367 d = defer.maybeDeferred(fileObj.setAttrs, attrs)
368 d.addCallback(self._cbStatus, requestId, 'fsetstat succeeded')
369 d.addErrback(self._ebStatus, requestId, 'fsetstat failed')
371 def packet_READLINK(self, data):
374 path, data = getNS(data)
375 assert data == '', 'still have data in READLINK: %s' % repr(data)
376 d = defer.maybeDeferred(self.client.readLink, path)
377 d.addCallback(self._cbReadLink, requestId)
378 d.addErrback(self._ebStatus, requestId, 'readlink failed')
380 def _cbReadLink(self, result, requestId):
381 self._cbSendDirectory([(result, '', {})], requestId)
383 def packet_SYMLINK(self, data):
386 linkPath, data = getNS(data)
387 targetPath, data = getNS(data)
388 d = defer.maybeDeferred(self.client.makeLink, linkPath, targetPath)
389 d.addCallback(self._cbStatus, requestId, 'symlink succeeded')
390 d.addErrback(self._ebStatus, requestId, 'symlink failed')
392 def packet_REALPATH(self, data):
395 path, data = getNS(data)
396 assert data == '', 'still have data in REALPATH: %s' % repr(data)
397 d = defer.maybeDeferred(self.client.realPath, path)
398 d.addCallback(self._cbReadLink, requestId) # same return format
399 d.addErrback(self._ebStatus, requestId, 'realpath failed')
401 def packet_EXTENDED(self, data):
404 extName, extData = getNS(data)
405 d = defer.maybeDeferred(self.client.extendedRequest, extName, extData)
406 d.addCallback(self._cbExtended, requestId)
407 d.addErrback(self._ebStatus, requestId, 'extended %s failed' % extName)
409 def _cbExtended(self, data, requestId):
410 self.sendPacket(FXP_EXTENDED_REPLY, requestId + data)
412 def _cbStatus(self, result, requestId, msg = "request succeeded"):
413 self._sendStatus(requestId, FX_OK, msg)
415 def _ebStatus(self, reason, requestId, msg = "request failed"):
418 if reason.type in (IOError, OSError):
419 if reason.value.errno == errno.ENOENT: # no such file
420 code = FX_NO_SUCH_FILE
421 message = reason.value.strerror
422 elif reason.value.errno == errno.EACCES: # permission denied
423 code = FX_PERMISSION_DENIED
424 message = reason.value.strerror
425 elif reason.value.errno == errno.EEXIST:
426 code = FX_FILE_ALREADY_EXISTS
429 elif reason.type == EOFError: # EOF
431 if reason.value.args:
432 message = reason.value.args[0]
433 elif reason.type == NotImplementedError:
434 code = FX_OP_UNSUPPORTED
435 if reason.value.args:
436 message = reason.value.args[0]
437 elif reason.type == SFTPError:
438 code = reason.value.code
439 message = reason.value.message
442 self._sendStatus(requestId, code, message)
444 def _sendStatus(self, requestId, code, message, lang = ''):
446 Helper method to send a FXP_STATUS message.
448 data = requestId + struct.pack('!L', code)
451 self.sendPacket(FXP_STATUS, data)
454 def connectionLost(self, reason):
456 Clean all opened files and directories.
458 for fileObj in self.openFiles.values():
461 for (dirObj, dirIter) in self.openDirs.values():
467 class FileTransferClient(FileTransferBase):
469 def __init__(self, extData = {}):
471 @param extData: a dict of extended_name : extended_data items
472 to be sent to the server.
474 FileTransferBase.__init__(self)
477 self.openRequests = {} # id -> Deferred
478 self.wasAFile = {} # Deferred -> 1 TERRIBLE HACK
480 def connectionMade(self):
481 data = struct.pack('!L', max(self.versions))
482 for k,v in self.extData.itervalues():
483 data += NS(k) + NS(v)
484 self.sendPacket(FXP_INIT, data)
486 def _sendRequest(self, msg, data):
487 data = struct.pack('!L', self.counter) + data
489 self.openRequests[self.counter] = d
491 self.sendPacket(msg, data)
494 def _parseRequest(self, data):
495 (id,) = struct.unpack('!L', data[:4])
496 d = self.openRequests[id]
497 del self.openRequests[id]
500 def openFile(self, filename, flags, attrs):
504 This method returns a L{Deferred} that is called back with an object
505 that provides the L{ISFTPFile} interface.
507 @param filename: a string representing the file to open.
509 @param flags: a integer of the flags to open the file with, ORed together.
510 The flags and their values are listed at the bottom of this file.
512 @param attrs: a list of attributes to open the file with. It is a
513 dictionary, consisting of 0 or more keys. The possible keys are::
515 size: the size of the file in bytes
516 uid: the user ID of the file as an integer
517 gid: the group ID of the file as an integer
518 permissions: the permissions of the file with as an integer.
519 the bit representation of this field is defined by POSIX.
520 atime: the access time of the file as seconds since the epoch.
521 mtime: the modification time of the file as seconds since the epoch.
522 ext_*: extended attributes. The server is not required to
523 understand this, but it may.
525 NOTE: there is no way to indicate text or binary files. it is up
526 to the SFTP client to deal with this.
528 data = NS(filename) + struct.pack('!L', flags) + self._packAttributes(attrs)
529 d = self._sendRequest(FXP_OPEN, data)
530 self.wasAFile[d] = (1, filename) # HACK
533 def removeFile(self, filename):
535 Remove the given file.
537 This method returns a Deferred that is called back when it succeeds.
539 @param filename: the name of the file as a string.
541 return self._sendRequest(FXP_REMOVE, NS(filename))
543 def renameFile(self, oldpath, newpath):
545 Rename the given file.
547 This method returns a Deferred that is called back when it succeeds.
549 @param oldpath: the current location of the file.
550 @param newpath: the new file name.
552 return self._sendRequest(FXP_RENAME, NS(oldpath)+NS(newpath))
554 def makeDirectory(self, path, attrs):
558 This method returns a Deferred that is called back when it is
561 @param path: the name of the directory to create as a string.
563 @param attrs: a dictionary of attributes to create the directory
564 with. Its meaning is the same as the attrs in the openFile method.
566 return self._sendRequest(FXP_MKDIR, NS(path)+self._packAttributes(attrs))
568 def removeDirectory(self, path):
570 Remove a directory (non-recursively)
572 It is an error to remove a directory that has files or directories in
575 This method returns a Deferred that is called back when it is removed.
577 @param path: the directory to remove.
579 return self._sendRequest(FXP_RMDIR, NS(path))
581 def openDirectory(self, path):
583 Open a directory for scanning.
585 This method returns a Deferred that is called back with an iterable
586 object that has a close() method.
588 The close() method is called when the client is finished reading
589 from the directory. At this point, the iterable will no longer
592 The iterable returns triples of the form (filename, longname, attrs)
593 or a Deferred that returns the same. The sequence must support
594 __getitem__, but otherwise may be any 'sequence-like' object.
596 filename is the name of the file relative to the directory.
597 logname is an expanded format of the filename. The recommended format
599 -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer
600 1234567890 123 12345678 12345678 12345678 123456789012
602 The first line is sample output, the second is the length of the field.
603 The fields are: permissions, link count, user owner, group owner,
604 size in bytes, modification time.
606 attrs is a dictionary in the format of the attrs argument to openFile.
608 @param path: the directory to open.
610 d = self._sendRequest(FXP_OPENDIR, NS(path))
611 self.wasAFile[d] = (0, path)
614 def getAttrs(self, path, followLinks=0):
616 Return the attributes for the given path.
618 This method returns a dictionary in the same format as the attrs
619 argument to openFile or a Deferred that is called back with same.
621 @param path: the path to return attributes for as a string.
622 @param followLinks: a boolean. if it is True, follow symbolic links
623 and return attributes for the real path at the base. if it is False,
624 return attributes for the specified path.
626 if followLinks: m = FXP_STAT
628 return self._sendRequest(m, NS(path))
630 def setAttrs(self, path, attrs):
632 Set the attributes for the path.
634 This method returns when the attributes are set or a Deferred that is
635 called back when they are.
637 @param path: the path to set attributes for as a string.
638 @param attrs: a dictionary in the same format as the attrs argument to
641 data = NS(path) + self._packAttributes(attrs)
642 return self._sendRequest(FXP_SETSTAT, data)
644 def readLink(self, path):
646 Find the root of a set of symbolic links.
648 This method returns the target of the link, or a Deferred that
651 @param path: the path of the symlink to read.
653 d = self._sendRequest(FXP_READLINK, NS(path))
654 return d.addCallback(self._cbRealPath)
656 def makeLink(self, linkPath, targetPath):
658 Create a symbolic link.
660 This method returns when the link is made, or a Deferred that
663 @param linkPath: the pathname of the symlink as a string
664 @param targetPath: the path of the target of the link as a string.
666 return self._sendRequest(FXP_SYMLINK, NS(linkPath)+NS(targetPath))
668 def realPath(self, path):
670 Convert any path to an absolute path.
672 This method returns the absolute path as a string, or a Deferred
673 that returns the same.
675 @param path: the path to convert as a string.
677 d = self._sendRequest(FXP_REALPATH, NS(path))
678 return d.addCallback(self._cbRealPath)
680 def _cbRealPath(self, result):
681 name, longname, attrs = result[0]
684 def extendedRequest(self, request, data):
686 Make an extended request of the server.
688 The method returns a Deferred that is called back with
689 the result of the extended request.
691 @param request: the name of the extended request to make.
692 @param data: any other data that goes along with the request.
694 return self._sendRequest(FXP_EXTENDED, NS(request) + data)
696 def packet_VERSION(self, data):
697 version, = struct.unpack('!L', data[:4])
701 k, data = getNS(data)
702 v, data = getNS(data)
704 self.version = version
705 self.gotServerVersion(version, d)
707 def packet_STATUS(self, data):
708 d, data = self._parseRequest(data)
709 code, = struct.unpack('!L', data[:4])
712 msg, data = getNS(data)
714 lang, data = getNS(data)
721 d.callback((msg, lang))
723 d.errback(EOFError(msg))
724 elif code == FX_OP_UNSUPPORTED:
725 d.errback(NotImplementedError(msg))
727 d.errback(SFTPError(code, msg, lang))
729 def packet_HANDLE(self, data):
730 d, data = self._parseRequest(data)
731 isFile, name = self.wasAFile.pop(d)
733 cb = ClientFile(self, getNS(data)[0])
735 cb = ClientDirectory(self, getNS(data)[0])
739 def packet_DATA(self, data):
740 d, data = self._parseRequest(data)
741 d.callback(getNS(data)[0])
743 def packet_NAME(self, data):
744 d, data = self._parseRequest(data)
745 count, = struct.unpack('!L', data[:4])
748 for i in range(count):
749 filename, data = getNS(data)
750 longname, data = getNS(data)
751 attrs, data = self._parseAttributes(data)
752 files.append((filename, longname, attrs))
755 def packet_ATTRS(self, data):
756 d, data = self._parseRequest(data)
757 d.callback(self._parseAttributes(data)[0])
759 def packet_EXTENDED_REPLY(self, data):
760 d, data = self._parseRequest(data)
763 def gotServerVersion(self, serverVersion, extData):
765 Called when the client sends their version info.
767 @param otherVersion: an integer representing the version of the SFTP
768 protocol they are claiming.
769 @param extData: a dictionary of extended_name : extended_data items.
770 These items are sent by the client to indicate additional features.
775 interface.implements(ISFTPFile)
777 def __init__(self, parent, handle):
779 self.handle = NS(handle)
782 return self.parent._sendRequest(FXP_CLOSE, self.handle)
784 def readChunk(self, offset, length):
785 data = self.handle + struct.pack("!QL", offset, length)
786 return self.parent._sendRequest(FXP_READ, data)
788 def writeChunk(self, offset, chunk):
789 data = self.handle + struct.pack("!Q", offset) + NS(chunk)
790 return self.parent._sendRequest(FXP_WRITE, data)
793 return self.parent._sendRequest(FXP_FSTAT, self.handle)
795 def setAttrs(self, attrs):
796 data = self.handle + self.parent._packAttributes(attrs)
797 return self.parent._sendRequest(FXP_FSTAT, data)
799 class ClientDirectory:
801 def __init__(self, parent, handle):
803 self.handle = NS(handle)
807 d = self.parent._sendRequest(FXP_READDIR, self.handle)
811 return self.parent._sendRequest(FXP_CLOSE, self.handle)
818 return self.filesCache.pop(0)
820 d.addCallback(self._cbReadDir)
821 d.addErrback(self._ebReadDir)
824 def _cbReadDir(self, names):
825 self.filesCache = names[1:]
828 def _ebReadDir(self, reason):
829 reason.trap(EOFError)
836 class SFTPError(Exception):
838 def __init__(self, errorCode, errorMessage, lang = ''):
839 Exception.__init__(self)
840 self.code = errorCode
841 self._message = errorMessage
847 A string received over the network that explains the error to a human.
849 # Python 2.6 deprecates assigning to the 'message' attribute of an
850 # exception. We define this read-only property here in order to
851 # prevent the warning about deprecation while maintaining backwards
852 # compatibility with object clients that rely on the 'message'
853 # attribute being set correctly. See bug #3897.
855 message = property(message)
859 return 'SFTPError %s: %s' % (self.code, self.message)
887 FXP_EXTENDED_REPLY = 201
889 FILEXFER_ATTR_SIZE = 0x00000001
890 FILEXFER_ATTR_UIDGID = 0x00000002
891 FILEXFER_ATTR_OWNERGROUP = FILEXFER_ATTR_UIDGID
892 FILEXFER_ATTR_PERMISSIONS = 0x00000004
893 FILEXFER_ATTR_ACMODTIME = 0x00000008
894 FILEXFER_ATTR_EXTENDED = 0x80000000L
896 FILEXFER_TYPE_REGULAR = 1
897 FILEXFER_TYPE_DIRECTORY = 2
898 FILEXFER_TYPE_SYMLINK = 3
899 FILEXFER_TYPE_SPECIAL = 4
900 FILEXFER_TYPE_UNKNOWN = 5
902 FXF_READ = 0x00000001
903 FXF_WRITE = 0x00000002
904 FXF_APPEND = 0x00000004
905 FXF_CREAT = 0x00000008
906 FXF_TRUNC = 0x00000010
907 FXF_EXCL = 0x00000020
908 FXF_TEXT = 0x00000040
913 FX_PERMISSION_DENIED = 3
917 FX_CONNECTION_LOST = 7
918 FX_OP_UNSUPPORTED = 8
919 FX_FILE_ALREADY_EXISTS = 11
920 # http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/ defines more
921 # useful error codes, but so far OpenSSH doesn't implement them. We use them
922 # internally for clarity, but for now define them all as FX_FAILURE to be
923 # compatible with existing software.
924 FX_NOT_A_DIRECTORY = FX_FAILURE
925 FX_FILE_IS_A_DIRECTORY = FX_FAILURE
928 # initialize FileTransferBase.packetTypes:
930 for name in g.keys():
931 if name.startswith('FXP_'):
933 FileTransferBase.packetTypes[value] = name[4:]