@@ -234,12 +234,8 @@ class _SFTPFSProtocol(Protocol):
234
234
"""Protocol for accessing a filesystem via an SFTP server"""
235
235
236
236
@property
237
- def max_read_len (self ) -> int :
238
- """Maximum read length associated with this SFTP session"""
239
-
240
- @property
241
- def max_write_len (self ) -> int :
242
- """Maximum write length associated with this SFTP session"""
237
+ def limits (self ) -> 'SFTPLimits' :
238
+ """SFTP server limits associated with this SFTP session"""
243
239
244
240
@staticmethod
245
241
def basename (path : bytes ) -> bytes :
@@ -2047,7 +2043,20 @@ def decode(cls, packet: SSHPacket, sftp_version: int) -> 'SFTPName':
2047
2043
2048
2044
2049
2045
class SFTPLimits (Record ):
2050
- """SFTP server limits"""
2046
+ """SFTP server limits
2047
+
2048
+ SFTPLimits is a simple record class with the following fields:
2049
+
2050
+ ================= ========================================= ======
2051
+ Field Description Type
2052
+ ================= ========================================= ======
2053
+ max_packet_len Max allowed size of an SFTP packet uint64
2054
+ max_read_len Max allowed size of an SFTP read request uint64
2055
+ max_write_len Max allowed size of an SFTP write request uint64
2056
+ max_open_handles Max allowed number of open file handles uint64
2057
+ ================= ========================================= ======
2058
+
2059
+ """
2051
2060
2052
2061
max_packet_len : int
2053
2062
max_read_len : int
@@ -2296,8 +2305,7 @@ def __init__(self, reader: 'SSHReader[bytes]', writer: 'SSHWriter[bytes]'):
2296
2305
self ._writer : Optional ['SSHWriter[bytes]' ] = writer
2297
2306
self ._logger = reader .logger .get_child ('sftp' )
2298
2307
2299
- self .max_read_len = SAFE_SFTP_READ_LEN
2300
- self .max_write_len = SAFE_SFTP_WRITE_LEN
2308
+ self .limits = SFTPLimits (0 , SAFE_SFTP_READ_LEN , SAFE_SFTP_WRITE_LEN , 0 )
2301
2309
2302
2310
@property
2303
2311
def logger (self ) -> SSHLogger :
@@ -2708,10 +2716,10 @@ async def request_limits(self) -> None:
2708
2716
self ._log_limits (limits )
2709
2717
2710
2718
if limits .max_read_len :
2711
- self .max_read_len = limits .max_read_len
2719
+ self .limits . max_read_len = limits .max_read_len
2712
2720
2713
2721
if limits .max_write_len :
2714
- self .max_write_len = limits .max_write_len
2722
+ self .limits . max_write_len = limits .max_write_len
2715
2723
2716
2724
async def open (self , filename : bytes , pflags : int ,
2717
2725
attrs : SFTPAttrs ) -> bytes :
@@ -3114,9 +3122,9 @@ def __init__(self, handler: SFTPClientHandler, handle: bytes,
3114
3122
self ._offset = None if appending else 0
3115
3123
3116
3124
self .read_len = \
3117
- handler .max_read_len if block_size == - 1 else block_size
3125
+ handler .limits . max_read_len if block_size == - 1 else block_size
3118
3126
self .write_len = \
3119
- handler .max_write_len if block_size == - 1 else block_size
3127
+ handler .limits . max_write_len if block_size == - 1 else block_size
3120
3128
3121
3129
async def __aenter__ (self ) -> Self :
3122
3130
"""Allow SFTPClientFile to be used as an async context manager"""
@@ -3189,8 +3197,8 @@ async def read(self, size: int = -1,
3189
3197
size = (await self ._end ()) - offset
3190
3198
3191
3199
try :
3192
- if self .read_len and size > min ( self . read_len ,
3193
- self ._handler .max_read_len ):
3200
+ if self .read_len and size > \
3201
+ min ( self . read_len , self ._handler . limits .max_read_len ):
3194
3202
data = await _SFTPFileReader (
3195
3203
self .read_len , self ._max_requests , self ._handler ,
3196
3204
self ._handle , offset , size ).run ()
@@ -3610,16 +3618,10 @@ def version(self) -> int:
3610
3618
return self ._handler .version
3611
3619
3612
3620
@property
3613
- def max_read_len (self ) -> int :
3614
- """Maximum read length associated with this SFTP session"""
3615
-
3616
- return self ._handler .max_read_len
3621
+ def limits (self ) -> SFTPLimits :
3622
+ """:class:`SFTPLimits` associated with this SFTP session"""
3617
3623
3618
- @property
3619
- def max_write_len (self ) -> int :
3620
- """Maximum write length associated with this SFTP session"""
3621
-
3622
- return self ._handler .max_write_len
3624
+ return self ._handler .limits
3623
3625
3624
3626
@staticmethod
3625
3627
def basename (path : bytes ) -> bytes :
@@ -3786,7 +3788,8 @@ async def _begin_copy(self, srcfs: _SFTPFSProtocol, dstfs: _SFTPFSProtocol,
3786
3788
"""Begin a new file upload, download, or copy"""
3787
3789
3788
3790
if block_size == - 1 :
3789
- block_size = min (srcfs .max_read_len , dstfs .max_write_len )
3791
+ block_size = min (srcfs .limits .max_read_len ,
3792
+ dstfs .limits .max_write_len )
3790
3793
3791
3794
if isinstance (srcpaths , (bytes , str , PurePath )):
3792
3795
srcpaths = [srcpaths ]
@@ -3880,7 +3883,9 @@ async def get(self, remotepaths: _SFTPPaths,
3880
3883
watch out for links that result in loops.
3881
3884
3882
3885
The block_size argument specifies the size of read and write
3883
- requests issued when downloading the files, defaulting to 16 KB.
3886
+ requests issued when downloading the files, defaulting to
3887
+ the maximum allowed by the server, or 16 KB if the server
3888
+ doesn't advertise limits.
3884
3889
3885
3890
The max_requests argument specifies the maximum number of
3886
3891
parallel read or write requests issued, defaulting to 128.
@@ -3984,7 +3989,9 @@ async def put(self, localpaths: _SFTPPaths,
3984
3989
watch out for links that result in loops.
3985
3990
3986
3991
The block_size argument specifies the size of read and write
3987
- requests issued when uploading the files, defaulting to 16 KB.
3992
+ requests issued when downloading the files, defaulting to
3993
+ the maximum allowed by the server, or 16 KB if the server
3994
+ doesn't advertise limits.
3988
3995
3989
3996
The max_requests argument specifies the maximum number of
3990
3997
parallel read or write requests issued, defaulting to 128.
@@ -4088,7 +4095,9 @@ async def copy(self, srcpaths: _SFTPPaths,
4088
4095
watch out for links that result in loops.
4089
4096
4090
4097
The block_size argument specifies the size of read and write
4091
- requests issued when copying the files, defaulting to 16 KB.
4098
+ requests issued when downloading the files, defaulting to
4099
+ the maximum allowed by the server, or 16 KB if the server
4100
+ doesn't advertise limits.
4092
4101
4093
4102
The max_requests argument specifies the maximum number of
4094
4103
parallel read or write requests issued, defaulting to 128.
@@ -4528,14 +4537,14 @@ async def open(self, path: _SFTPPath,
4528
4537
or write call will become a single request to the SFTP server.
4529
4538
Otherwise, read or write calls larger than this size will be
4530
4539
turned into parallel requests to the server of the requested
4531
- size, defaulting to 16 KB.
4540
+ size, defaulting to the maximum allowed by the server, or 16 KB
4541
+ if the server doesn't advertise limits.
4532
4542
4533
4543
.. note:: The OpenSSH SFTP server will close the connection
4534
- if it receives a message larger than 256 KB, and
4535
- limits read requests to returning no more than
4536
- 64 KB. So, when connecting to an OpenSSH SFTP
4537
- server, it is recommended that the block_size be
4538
- set below these sizes.
4544
+ if it receives a message larger than 256 KB. So,
4545
+ when connecting to an OpenSSH SFTP server, it is
4546
+ recommended that the block_size be left at its
4547
+ default of using the server-advertised limits.
4539
4548
4540
4549
The max_requests argument specifies the maximum number of
4541
4550
parallel read or write requests issued, defaulting to 128.
@@ -6419,8 +6428,10 @@ async def _process_limits(self, packet: SSHPacket) -> SFTPLimits:
6419
6428
6420
6429
packet .check_end ()
6421
6430
6431
+ nfiles = os .sysconf ('SC_OPEN_MAX' ) - 5 if hasattr (os , 'sysconf' ) else 0
6432
+
6422
6433
return SFTPLimits (MAX_SFTP_PACKET_LEN , MAX_SFTP_READ_LEN ,
6423
- MAX_SFTP_WRITE_LEN , 0 )
6434
+ MAX_SFTP_WRITE_LEN , nfiles )
6424
6435
6425
6436
_packet_handlers : Dict [Union [int , bytes ], _SFTPPacketHandler ] = {
6426
6437
FXP_OPEN : _process_open ,
@@ -7529,8 +7540,7 @@ async def close(self) -> None:
7529
7540
class LocalFS :
7530
7541
"""An async wrapper around local filesystem access"""
7531
7542
7532
- max_read_len = MAX_SFTP_READ_LEN
7533
- max_write_len = MAX_SFTP_WRITE_LEN
7543
+ limits = SFTPLimits (0 , MAX_SFTP_READ_LEN , MAX_SFTP_WRITE_LEN , 0 )
7534
7544
7535
7545
@staticmethod
7536
7546
def basename (path : bytes ) -> bytes :
0 commit comments