0
# File System Interface
1
2
Cross-platform filesystem abstraction providing virtualized access to local and remote filesystems with security controls. pyftpdlib's filesystem interface enables secure, chrooted access to directories while maintaining compatibility across different operating systems and filesystem types.
3
4
## Capabilities
5
6
### Abstracted File System
7
8
Main filesystem interface providing cross-platform file operations with virtual chroot functionality and security controls.
9
10
```python { .api }
11
class AbstractedFS:
12
def __init__(self, root, cmd_channel):
13
"""
14
Initialize filesystem interface.
15
16
Parameters:
17
- root: root directory path (user's home directory)
18
- cmd_channel: associated FTPHandler instance
19
"""
20
21
# Properties
22
@property
23
def root(self):
24
"""Get root directory path."""
25
26
@property
27
def cwd(self):
28
"""Get current working directory (relative to root)."""
29
30
# Path manipulation methods
31
def ftpnorm(self, ftppath):
32
"""
33
Normalize FTP path by resolving . and .. components.
34
35
Parameters:
36
- ftppath: FTP path string
37
38
Returns:
39
- Normalized FTP path
40
"""
41
42
def ftp2fs(self, ftppath):
43
"""
44
Convert FTP path to filesystem path.
45
46
Parameters:
47
- ftppath: FTP path (relative to user root)
48
49
Returns:
50
- Absolute filesystem path
51
"""
52
53
def fs2ftp(self, fspath):
54
"""
55
Convert filesystem path to FTP path.
56
57
Parameters:
58
- fspath: absolute filesystem path
59
60
Returns:
61
- FTP path relative to user root
62
"""
63
64
def validpath(self, path):
65
"""
66
Check if path is within user's accessible area.
67
68
Parameters:
69
- path: filesystem path to validate
70
71
Returns:
72
- True if path is valid and accessible
73
"""
74
75
# File operations
76
def open(self, filename, mode):
77
"""
78
Open file for reading or writing.
79
80
Parameters:
81
- filename: file path
82
- mode: file open mode ('r', 'w', 'a', 'rb', 'wb', etc.)
83
84
Returns:
85
- File object
86
"""
87
88
def mkstemp(self, suffix='', prefix='', dir=None, mode='wb'):
89
"""
90
Create temporary file.
91
92
Parameters:
93
- suffix: filename suffix
94
- prefix: filename prefix
95
- dir: directory for temp file (None = current directory)
96
- mode: file open mode
97
98
Returns:
99
- (fd, path) tuple of file descriptor and path
100
"""
101
102
def remove(self, path):
103
"""
104
Delete file.
105
106
Parameters:
107
- path: file path to delete
108
"""
109
110
def rename(self, src, dst):
111
"""
112
Rename/move file or directory.
113
114
Parameters:
115
- src: source path
116
- dst: destination path
117
"""
118
119
def chmod(self, path, mode):
120
"""
121
Change file/directory permissions.
122
123
Parameters:
124
- path: file/directory path
125
- mode: permission mode (octal integer)
126
"""
127
128
def stat(self, path):
129
"""
130
Get file/directory status information.
131
132
Parameters:
133
- path: file/directory path
134
135
Returns:
136
- os.stat_result object
137
"""
138
139
def lstat(self, path):
140
"""
141
Get file/directory status (don't follow symlinks).
142
143
Parameters:
144
- path: file/directory path
145
146
Returns:
147
- os.stat_result object
148
"""
149
150
def utime(self, path, timeval):
151
"""
152
Set file access and modification times.
153
154
Parameters:
155
- path: file path
156
- timeval: (atime, mtime) tuple or None for current time
157
"""
158
159
def readlink(self, path):
160
"""
161
Read symbolic link target.
162
163
Parameters:
164
- path: symlink path
165
166
Returns:
167
- Target path string
168
"""
169
170
# Directory operations
171
def chdir(self, path):
172
"""
173
Change current working directory.
174
175
Parameters:
176
- path: directory path (relative to root)
177
"""
178
179
def mkdir(self, path):
180
"""
181
Create directory.
182
183
Parameters:
184
- path: directory path to create
185
"""
186
187
def rmdir(self, path):
188
"""
189
Remove empty directory.
190
191
Parameters:
192
- path: directory path to remove
193
"""
194
195
def listdir(self, path):
196
"""
197
List directory contents.
198
199
Parameters:
200
- path: directory path
201
202
Returns:
203
- List of filenames
204
"""
205
206
def listdirinfo(self, path):
207
"""
208
List directory with file information.
209
210
Parameters:
211
- path: directory path
212
213
Returns:
214
- List of (filename, stat_result) tuples
215
"""
216
217
# File system queries
218
def isfile(self, path):
219
"""Check if path is a regular file."""
220
221
def isdir(self, path):
222
"""Check if path is a directory."""
223
224
def islink(self, path):
225
"""Check if path is a symbolic link."""
226
227
def getsize(self, path):
228
"""Get file size in bytes."""
229
230
def getmtime(self, path):
231
"""Get file modification time as timestamp."""
232
233
def realpath(self, path):
234
"""Get canonical path resolving all symlinks."""
235
236
def lexists(self, path):
237
"""Check if path exists (don't follow symlinks)."""
238
239
# User/group resolution
240
def get_user_by_uid(self, uid):
241
"""
242
Get username from UID.
243
244
Parameters:
245
- uid: user ID number
246
247
Returns:
248
- Username string or UID if not found
249
"""
250
251
def get_group_by_gid(self, gid):
252
"""
253
Get group name from GID.
254
255
Parameters:
256
- gid: group ID number
257
258
Returns:
259
- Group name string or GID if not found
260
"""
261
262
# Directory listing formatters
263
def format_list(self, basedir, listing, ignore_err=True):
264
"""
265
Format directory listing in Unix ls -l style.
266
267
Parameters:
268
- basedir: directory being listed
269
- listing: list of (filename, stat_result) tuples
270
- ignore_err: ignore files that cause stat errors
271
272
Returns:
273
- Iterator of formatted listing lines
274
"""
275
276
def format_mlsx(self, basedir, listing, perms, facts, ignore_err=True):
277
"""
278
Format directory listing in machine-readable MLSD format.
279
280
Parameters:
281
- basedir: directory being listed
282
- listing: list of (filename, stat_result) tuples
283
- perms: permission string for current user
284
- facts: requested fact names
285
- ignore_err: ignore files that cause stat errors
286
287
Returns:
288
- Iterator of formatted MLSD lines
289
"""
290
```
291
292
### Unix File System
293
294
Direct filesystem access without chroot restrictions, suitable for system-level FTP services on POSIX systems.
295
296
```python { .api }
297
class UnixFilesystem(AbstractedFS): # POSIX only
298
def __init__(self, root, cmd_channel):
299
"""
300
Initialize Unix filesystem with direct access.
301
302
Parameters:
303
- root: root directory (not enforced as chroot)
304
- cmd_channel: associated FTPHandler instance
305
"""
306
307
def ftp2fs(self, ftppath):
308
"""
309
Convert FTP path directly to filesystem path.
310
No chroot restrictions applied.
311
"""
312
313
def fs2ftp(self, fspath):
314
"""Convert filesystem path directly to FTP path."""
315
316
def validpath(self, path):
317
"""Always returns True - no path restrictions."""
318
```
319
320
## Exception Classes
321
322
```python { .api }
323
class FilesystemError(Exception):
324
"""Custom exception for filesystem-related errors."""
325
```
326
327
## Utility Functions
328
329
```python { .api }
330
def _memoize(fun):
331
"""
332
Memoization decorator for expensive operations like user/group lookups.
333
Caches function results to improve performance.
334
"""
335
```
336
337
## Usage Examples
338
339
### Basic Virtual Filesystem
340
341
```python
342
from pyftpdlib.filesystems import AbstractedFS
343
344
# Create filesystem rooted at user's home directory
345
fs = AbstractedFS("/home/user", cmd_channel)
346
347
# Path operations
348
print(fs.cwd) # Current directory relative to root
349
fs.chdir("documents") # Change to documents subdirectory
350
real_path = fs.ftp2fs("file.txt") # Convert to actual filesystem path
351
352
# File operations
353
with fs.open("readme.txt", "r") as f:
354
content = f.read()
355
356
fs.mkdir("new_folder")
357
fs.remove("old_file.txt")
358
fs.rename("old_name.txt", "new_name.txt")
359
```
360
361
### Directory Listing
362
363
```python
364
# Get directory contents
365
files = fs.listdir(".")
366
print(files) # ['file1.txt', 'file2.txt', 'subfolder']
367
368
# Get detailed file information
369
detailed = fs.listdirinfo(".")
370
for filename, stat_info in detailed:
371
print(f"{filename}: {stat_info.st_size} bytes")
372
373
# Format as Unix ls -l listing
374
listing = fs.format_list(".", detailed)
375
for line in listing:
376
print(line)
377
# Output: -rw-r--r-- 1 user group 1024 Jan 01 12:00 file1.txt
378
```
379
380
### File Information
381
382
```python
383
# Check file types
384
if fs.isfile("document.pdf"):
385
size = fs.getsize("document.pdf")
386
mtime = fs.getmtime("document.pdf")
387
print(f"File size: {size} bytes, modified: {mtime}")
388
389
if fs.isdir("folder"):
390
print("It's a directory")
391
392
if fs.islink("symlink"):
393
target = fs.readlink("symlink")
394
print(f"Symlink points to: {target}")
395
```
396
397
### Custom Filesystem
398
399
```python
400
class RestrictedFS(AbstractedFS):
401
def validpath(self, path):
402
"""Block access to hidden files and system directories."""
403
if not super().validpath(path):
404
return False
405
406
# Block hidden files
407
if "/.'" in path or path.startswith("."):
408
return False
409
410
# Block system directories
411
restricted = ["/etc", "/proc", "/sys", "/dev"]
412
for restricted_path in restricted:
413
if path.startswith(restricted_path):
414
return False
415
416
return True
417
418
def listdir(self, path):
419
"""Filter out hidden files from listings."""
420
files = super().listdir(path)
421
return [f for f in files if not f.startswith(".")]
422
423
# Use custom filesystem
424
class CustomFTPHandler(FTPHandler):
425
abstracted_fs = RestrictedFS
426
427
handler = CustomFTPHandler
428
handler.authorizer = authorizer
429
```
430
431
### Unix Direct Access
432
433
```python
434
from pyftpdlib.filesystems import UnixFilesystem
435
436
# Create filesystem with direct access (no chroot)
437
class SystemFTPHandler(FTPHandler):
438
abstracted_fs = UnixFilesystem
439
440
# WARNING: This allows access to entire filesystem
441
# Only use with trusted users and proper authorizer restrictions
442
handler = SystemFTPHandler
443
handler.authorizer = unix_authorizer # Should use UnixAuthorizer
444
```
445
446
### Integration with FTP Handler
447
448
```python
449
from pyftpdlib.handlers import FTPHandler
450
451
class CustomFTPHandler(FTPHandler):
452
# Use custom filesystem
453
abstracted_fs = RestrictedFS
454
455
def run_as_current_user(self, function, *args, **kwargs):
456
"""Execute filesystem operations with proper user context."""
457
return function(*args, **kwargs)
458
459
# Configure handler
460
handler = CustomFTPHandler
461
handler.authorizer = authorizer
462
```
463
464
### Permission Management
465
466
```python
467
# Set file permissions (Unix/Linux)
468
fs.chmod("script.sh", 0o755) # rwxr-xr-x
469
470
# Set file modification time
471
import time
472
current_time = time.time()
473
fs.utime("file.txt", (current_time, current_time))
474
475
# User/group lookup
476
uid = 1000
477
username = fs.get_user_by_uid(uid)
478
print(f"UID {uid} belongs to user: {username}")
479
```
480
481
## Security Considerations
482
483
- **AbstractedFS**: Provides chroot-like security by restricting access to files outside the root directory
484
- **UnixFilesystem**: Allows full filesystem access - use only with proper authentication and authorization
485
- **Path Validation**: Always validate paths with `validpath()` before file operations
486
- **Symbolic Links**: Be careful with symlinks that might point outside the chroot area
487
488
## Platform Support
489
490
- **AbstractedFS**: Cross-platform (Windows, Linux, macOS, etc.)
491
- **UnixFilesystem**: POSIX systems only (Linux, macOS, BSD, etc.)
492
- **Symlink Support**: Available on platforms that support symbolic links