or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
pypipkg:pypi/pyfuse3@3.4.x

docs

index.md
tile.json

tessl/pypi-pyfuse3

tessl install tessl/pypi-pyfuse3@3.4.0

Python 3 bindings for libfuse 3 with async I/O support

data-structures.mddocs/reference/

Data Structures

Core data structures for representing filesystem metadata, file information, request context, and filesystem statistics.

Capabilities

Entry Attributes

Represents attributes of directory entries, corresponding to the stat struct.

class EntryAttributes:
    """
    Attributes of directory entries.

    Most attributes correspond to stat struct elements.
    Used to return file/directory metadata from various operations.
    Can be pickled for caching/serialization.
    """

    st_ino: InodeT
    generation: int
    entry_timeout: Union[float, int]
    attr_timeout: Union[float, int]
    st_mode: ModeT
    st_nlink: int
    st_uid: int
    st_gid: int
    st_rdev: int
    st_size: int
    st_blksize: int
    st_blocks: int
    st_atime_ns: int
    st_ctime_ns: int
    st_mtime_ns: int
    st_birthtime_ns: int

    def __init__(self) -> None: ...
    def __getstate__(self) -> StatDict: ...
    def __setstate__(self, state: StatDict) -> None: ...

Attributes

  • st_ino: Inode number (InodeT, must be unique within filesystem)
  • generation: Inode generation number (for NFS export support, usually 0)
  • entry_timeout: Validity timeout for name/existence in seconds (float or int)
  • attr_timeout: Validity timeout for attributes in seconds (float or int)
  • st_mode: File mode (type and permissions)
    • File type: S_IFREG, S_IFDIR, S_IFLNK, S_IFCHR, S_IFBLK, S_IFIFO, S_IFSOCK
    • Permissions: 0o000 to 0o777
  • st_nlink: Number of hard links (directories start at 2: . and ..)
  • st_uid: Owner user ID
  • st_gid: Owner group ID
  • st_rdev: Device ID for special files (S_IFCHR, S_IFBLK), otherwise 0
  • st_size: File size in bytes
  • st_blksize: Block size for filesystem I/O (affects buffering, typically 4096)
  • st_blocks: Number of 512-byte blocks allocated
  • st_atime_ns: Last access time in nanoseconds (integer, not float)
  • st_ctime_ns: Inode change time (metadata) in nanoseconds (integer)
  • st_mtime_ns: Last modification time (content) in nanoseconds (integer)
  • st_birthtime_ns: Creation time in nanoseconds (BSD/macOS only, 0 on Linux)

Default Values

When creating a new EntryAttributes instance:

  • entry_timeout: 300 seconds (5 minutes)
  • attr_timeout: 300 seconds (5 minutes)
  • st_mode: S_IFREG | 0o644 (regular file, rw-r--r--)
  • st_blksize: 4096 bytes
  • st_nlink: 1
  • All other numeric fields: 0

Usage example:

import pyfuse3
import stat
import os
import time

entry = pyfuse3.EntryAttributes()
entry.st_ino = 42
entry.st_mode = stat.S_IFDIR | 0o755
entry.st_nlink = 2  # . and ..
entry.st_uid = os.getuid()
entry.st_gid = os.getgid()
entry.st_size = 4096
entry.st_blksize = 4096
entry.st_blocks = 8  # 4096 / 512

# Set timestamps (nanoseconds since epoch)
now_ns = int(time.time() * 1e9)
entry.st_atime_ns = now_ns
entry.st_mtime_ns = now_ns
entry.st_ctime_ns = now_ns
entry.st_birthtime_ns = now_ns  # 0 on Linux

# Set cache timeouts
entry.entry_timeout = 1.0  # Cache entry for 1 second
entry.attr_timeout = 1.0   # Cache attributes for 1 second

Time Handling

Times are in nanoseconds for precision:

import time

# Current time in nanoseconds
now_ns = time.time_ns()  # Python 3.7+
# OR
now_ns = int(time.time() * 1_000_000_000)

# Convert from seconds to nanoseconds
seconds = 1234567890
nanoseconds = seconds * 1_000_000_000

# Convert from nanoseconds to seconds
nanoseconds = 1234567890000000000
seconds = nanoseconds / 1_000_000_000

File Types

Use stat module constants for st_mode:

import stat

# File types (mutually exclusive)
stat.S_IFREG   # Regular file (0o100000)
stat.S_IFDIR   # Directory (0o040000)
stat.S_IFLNK   # Symbolic link (0o120000)
stat.S_IFCHR   # Character device (0o020000)
stat.S_IFBLK   # Block device (0o060000)
stat.S_IFIFO   # Named pipe (FIFO) (0o010000)
stat.S_IFSOCK  # Socket (0o140000)

# Extract file type
file_type = entry.st_mode & stat.S_IFMT

# Check file type
if stat.S_ISREG(entry.st_mode):
    # Regular file
    pass
elif stat.S_ISDIR(entry.st_mode):
    # Directory
    pass
elif stat.S_ISLNK(entry.st_mode):
    # Symbolic link
    pass

# Set file type and permissions
entry.st_mode = stat.S_IFREG | 0o644
entry.st_mode = stat.S_IFDIR | 0o755
entry.st_mode = stat.S_IFLNK | 0o777  # Symlinks always 0o777

Blocks Calculation

Calculate st_blocks from size:

import math

# Simple: assume all bytes allocated
entry.st_size = 10000
entry.st_blocks = math.ceil(entry.st_size / 512)

# More accurate: account for block size
block_size = 4096
blocks_used = math.ceil(entry.st_size / block_size)
entry.st_blocks = blocks_used * (block_size // 512)

# Sparse files: fewer blocks than size would indicate
entry.st_size = 1_000_000_000  # 1 GB
entry.st_blocks = 0  # Sparse file, no blocks allocated

Cache Timeouts

Control kernel caching behavior:

# No caching (always re-query)
entry.entry_timeout = 0
entry.attr_timeout = 0

# Short caching (frequently changing data)
entry.entry_timeout = 1.0  # 1 second
entry.attr_timeout = 1.0

# Long caching (rarely changing data)
entry.entry_timeout = 3600.0  # 1 hour
entry.attr_timeout = 3600.0

# Infinite caching (immutable data)
entry.entry_timeout = float('inf')
entry.attr_timeout = float('inf')

Pickling

EntryAttributes supports pickling:

import pickle

entry = pyfuse3.EntryAttributes()
# ... set attributes ...

# Serialize
data = pickle.dumps(entry)

# Deserialize
restored = pickle.loads(data)

File Information

Stores options and data returned by Operations.open and passed to file operations.

class FileInfo:
    """
    Options and data for open files.

    Returned by open() and create() handlers.
    Controls caching behavior for the open file.
    """

    fh: FileHandleT
    direct_io: bool
    keep_cache: bool
    nonseekable: bool

    def __init__(
        self,
        fh: FileHandleT = 0,
        direct_io: bool = False,
        keep_cache: bool = True,
        nonseekable: bool = False
    ) -> None: ...

Attributes

  • fh: File handle (integer) identifying this open file
    • Must be unique among currently open files
    • Can reuse handles after release()
    • Passed to read(), write(), flush(), fsync(), release()
  • direct_io: Bypass page cache (disable caching)
    • True: Each read/write goes directly to filesystem
    • False: Kernel caches data in page cache (default, faster)
    • Use for files that change outside FUSE
  • keep_cache: Keep page cache (don't invalidate on open)
    • True: Keep existing cache (default, faster)
    • False: Invalidate cache on open
    • Set to False if file may have changed since last open
  • nonseekable: File does not support seeking
    • True: File is non-seekable (pipes, sockets)
    • False: File supports seek/lseek (default)

Default Values

  • fh: 0 (invalid handle, must be set)
  • direct_io: False (caching enabled)
  • keep_cache: True (keep cache)
  • nonseekable: False (seekable)

Usage example:

import pyfuse3

class MyFS(pyfuse3.Operations):
    def __init__(self):
        super().__init__()
        self.next_fh = 1
        self.open_files = {}

    async def open(self, inode, flags, ctx):
        # Allocate file handle
        fh = self.next_fh
        self.next_fh += 1
        self.open_files[fh] = {'inode': inode, 'flags': flags}

        # Return FileInfo with options
        fi = pyfuse3.FileInfo(
            fh=fh,
            direct_io=False,      # Enable caching
            keep_cache=True,      # Keep cache on open
            nonseekable=False     # File supports seeking
        )
        return fi

    async def create(self, parent_inode, name, mode, flags, ctx):
        # ... create inode ...
        
        fh = self.next_fh
        self.next_fh += 1
        self.open_files[fh] = {'inode': new_inode, 'flags': flags}

        fi = pyfuse3.FileInfo(fh=fh)
        entry = await self.getattr(new_inode, ctx)
        return (fi, entry)

Cache Control

Control caching behavior based on file characteristics:

async def open(self, inode, flags, ctx):
    fh = self.allocate_fh(inode, flags)
    
    # Frequently changing file: bypass cache
    if self.is_volatile(inode):
        return pyfuse3.FileInfo(fh=fh, direct_io=True, keep_cache=False)
    
    # File modified externally: invalidate cache
    if self.modified_externally(inode):
        return pyfuse3.FileInfo(fh=fh, keep_cache=False)
    
    # Normal file: use cache
    return pyfuse3.FileInfo(fh=fh, direct_io=False, keep_cache=True)

Non-Seekable Files

For special files that don't support seeking:

async def open(self, inode, flags, ctx):
    fh = self.allocate_fh(inode)
    
    # Check if file is a pipe or socket
    attr = await self.getattr(inode, ctx)
    is_nonseekable = stat.S_ISFIFO(attr.st_mode) or stat.S_ISSOCK(attr.st_mode)
    
    return pyfuse3.FileInfo(
        fh=fh,
        nonseekable=is_nonseekable,
        direct_io=is_nonseekable  # Usually want direct_io too
    )

Request Context

Provides information about the caller of the syscall that initiated a FUSE request.

class RequestContext:
    """
    Information about the request caller.

    Passed to most Operations methods.
    Cannot be pickled.
    All properties are read-only.
    """

    @property
    def uid(self) -> int: ...  # User ID

    @property
    def pid(self) -> int: ...  # Process ID

    @property
    def gid(self) -> int: ...  # Group ID

    @property
    def umask(self) -> int: ...  # File mode creation mask

    def __getstate__(self) -> None: ...  # Raises PicklingError

Properties (read-only)

  • uid: User ID of the calling process
    • Use for ownership checks
    • Compare with st_uid for owner permissions
  • pid: Process ID of the calling process
    • Use with get_sup_groups() for supplementary groups
    • Useful for logging/debugging
  • gid: Group ID of the calling process
    • Use for group permission checks
    • Compare with st_gid for group permissions
  • umask: File mode creation mask of the calling process
    • Apply when creating files: final_mode = mode & ~ctx.umask
    • Typically 0o022 (removes group/other write)

Notes

  • Cannot be pickled (raises PicklingError)
  • All properties are read-only
  • ctx may be None in some internal calls (e.g., getattr from readdir)

Usage example:

import pyfuse3
import stat
import os

async def create(self, parent_inode, name, mode, flags, ctx):
    # Apply umask to mode
    final_mode = mode & ~ctx.umask
    
    entry = pyfuse3.EntryAttributes()
    entry.st_mode = stat.S_IFREG | final_mode
    
    # Use caller's uid/gid for new file
    entry.st_uid = ctx.uid
    entry.st_gid = ctx.gid
    
    # ... create file ...
    return (fi, entry)

Permission Checking

Use ctx for permission checks:

import pyfuse3
import os

async def check_access(self, inode, mode, ctx):
    """Check if ctx has permission for mode on inode."""
    entry = await self.getattr(inode, ctx)
    
    # Owner permissions
    if ctx.uid == entry.st_uid:
        owner_perms = (entry.st_mode >> 6) & 0o7
        if (mode & owner_perms) == mode:
            return True
    
    # Group permissions
    if ctx.gid == entry.st_gid:
        group_perms = (entry.st_mode >> 3) & 0o7
        if (mode & group_perms) == mode:
            return True
    
    # Check supplementary groups
    sup_groups = pyfuse3.get_sup_groups(ctx.pid)
    if entry.st_gid in sup_groups:
        group_perms = (entry.st_mode >> 3) & 0o7
        if (mode & group_perms) == mode:
            return True
    
    # Others permissions
    other_perms = entry.st_mode & 0o7
    return (mode & other_perms) == mode

Umask Application

Always apply umask when creating files:

import stat

async def mknod(self, parent_inode, name, mode, rdev, ctx):
    # Apply umask
    final_mode = mode & ~ctx.umask
    
    entry = pyfuse3.EntryAttributes()
    entry.st_mode = final_mode
    # ... rest of creation ...

async def mkdir(self, parent_inode, name, mode, ctx):
    # Apply umask
    final_mode = (stat.S_IFDIR | mode) & ~ctx.umask
    
    entry = pyfuse3.EntryAttributes()
    entry.st_mode = final_mode
    # ... rest of creation ...

Setattr Fields

Specifies which attributes should be updated in a setattr operation.

class SetattrFields:
    """
    Indicates which attributes to update in setattr.

    Passed to Operations.setattr().
    Cannot be pickled.
    All properties are read-only booleans.
    """

    @property
    def update_atime(self) -> bool: ...

    @property
    def update_mtime(self) -> bool: ...

    @property
    def update_ctime(self) -> bool: ...

    @property
    def update_mode(self) -> bool: ...

    @property
    def update_uid(self) -> bool: ...

    @property
    def update_gid(self) -> bool: ...

    @property
    def update_size(self) -> bool: ...

    def __init__(self) -> None: ...
    def __getstate__(self) -> None: ...  # Raises PicklingError

Properties (read-only)

All properties are boolean and indicate which field to update:

  • update_atime: Update access time (st_atime_ns)
  • update_mtime: Update modification time (st_mtime_ns)
  • update_ctime: Update change time (st_ctime_ns, rarely set directly)
  • update_mode: Update file mode (st_mode, permissions only, not type)
  • update_uid: Update owner user ID (st_uid)
  • update_gid: Update owner group ID (st_gid)
  • update_size: Update file size (st_size, truncate/extend file)

Notes

  • Cannot be pickled (raises PicklingError)
  • All properties are read-only
  • Only update fields where corresponding property is True
  • Always update st_ctime_ns when any metadata changes

Usage example:

import time
import pyfuse3

async def setattr(self, inode, attr, fields, fh, ctx):
    # Get current attributes
    entry = await self.getattr(inode, ctx)

    # Update only specified fields
    if fields.update_mode:
        # Update permissions only (not file type)
        new_mode = attr.st_mode & 0o7777
        entry.st_mode = (entry.st_mode & ~0o7777) | new_mode

    if fields.update_uid:
        entry.st_uid = attr.st_uid

    if fields.update_gid:
        entry.st_gid = attr.st_gid

    if fields.update_size:
        # Truncate or extend file
        old_size = entry.st_size
        new_size = attr.st_size
        if new_size < old_size:
            # Truncate
            self.truncate_file(inode, new_size)
        elif new_size > old_size:
            # Extend (with zeros)
            self.extend_file(inode, new_size)
        entry.st_size = new_size

    if fields.update_atime:
        entry.st_atime_ns = attr.st_atime_ns

    if fields.update_mtime:
        entry.st_mtime_ns = attr.st_mtime_ns

    # Always update ctime when metadata changes
    entry.st_ctime_ns = time.time_ns()

    # Save changes
    self.save_inode(inode, entry)

    return entry

Special Cases

# chmod: updates mode
if fields.update_mode and not any([
    fields.update_size,
    fields.update_uid,
    fields.update_gid,
    fields.update_atime,
    fields.update_mtime
]):
    # Just chmod
    pass

# chown: updates uid/gid
if fields.update_uid or fields.update_gid:
    # chown
    pass

# truncate: updates size
if fields.update_size:
    # truncate/extend
    pass

# touch/utime: updates times
if fields.update_atime or fields.update_mtime:
    # touch/utime
    pass

Filesystem Statistics

Stores filesystem statistics information returned by statfs.

class StatvfsData:
    """
    Filesystem statistics.

    Attributes correspond to statvfs struct elements.
    Returned by Operations.statfs().
    Can be pickled for caching.
    """

    f_bsize: int
    f_frsize: int
    f_blocks: int
    f_bfree: int
    f_bavail: int
    f_files: int
    f_ffree: int
    f_favail: int
    f_namemax: int

    def __init__(self) -> None: ...
    def __getstate__(self) -> StatDict: ...
    def __setstate__(self, state: StatDict) -> None: ...

Attributes

  • f_bsize: Filesystem block size (preferred I/O size)
  • f_frsize: Fragment size (fundamental block size for f_blocks)
  • f_blocks: Total size of filesystem in f_frsize units
  • f_bfree: Total number of free blocks
  • f_bavail: Free blocks available to unprivileged users
  • f_files: Total number of file nodes (inodes)
  • f_ffree: Total number of free file nodes
  • f_favail: Free file nodes available to unprivileged users
  • f_namemax: Maximum filename length (typically 255)

Notes

  • All sizes are in f_frsize units (except f_namemax)
  • f_bavail ≤ f_bfree (root may have reserved space)
  • f_favail ≤ f_ffree (root may have reserved inodes)
  • Set f_namemax to maximum filename length (typically 255)

Usage example:

import pyfuse3

async def statfs(self, ctx):
    stat = pyfuse3.StatvfsData()
    
    # Block size
    stat.f_bsize = 4096
    stat.f_frsize = 4096
    
    # Total space: 1 GB
    stat.f_blocks = 1_000_000_000 // 4096
    
    # Free space: 500 MB
    free_blocks = 500_000_000 // 4096
    stat.f_bfree = free_blocks
    stat.f_bavail = free_blocks  # All free to users
    
    # Inodes
    stat.f_files = 100_000
    stat.f_ffree = 50_000
    stat.f_favail = 50_000
    
    # Maximum filename length
    stat.f_namemax = 255
    
    return stat

Dynamic Statistics

Calculate statistics dynamically:

async def statfs(self, ctx):
    stat = pyfuse3.StatvfsData()
    
    # Block size
    block_size = 4096
    stat.f_bsize = block_size
    stat.f_frsize = block_size
    
    # Calculate total space
    total_bytes = self.calculate_total_space()
    stat.f_blocks = total_bytes // block_size
    
    # Calculate free space
    used_bytes = self.calculate_used_space()
    free_bytes = total_bytes - used_bytes
    free_blocks = free_bytes // block_size
    stat.f_bfree = free_blocks
    
    # Reserve 5% for root
    reserved_blocks = stat.f_blocks // 20
    stat.f_bavail = max(0, free_blocks - reserved_blocks)
    
    # Inode statistics
    total_inodes = len(self.all_inodes)
    free_inodes = self.max_inodes - total_inodes
    stat.f_files = self.max_inodes
    stat.f_ffree = free_inodes
    stat.f_favail = free_inodes
    
    stat.f_namemax = 255
    
    return stat

Readdir Token

Opaque token used for directory listing operations.

class ReaddirToken:
    """
    Token for readdir operations.

    Passed to Operations.readdir() and used with readdir_reply().
    Not directly instantiated by users.
    Treated as opaque by filesystem implementations.
    """
    pass

This token is created by pyfuse3 and passed to the readdir handler. It should be passed directly to readdir_reply() without modification.

Usage example:

async def readdir(self, fh, start_id, token):
    entries = self.get_directory_entries(fh)

    for i, (name, inode) in enumerate(entries):
        if i < start_id:
            continue

        attr = await self.getattr(inode, None)

        # Pass token to readdir_reply
        if not pyfuse3.readdir_reply(token, name, attr, i + 1):
            # Buffer full, stop
            break

Type Aliases

StatDict = Mapping[str, int]  # Stat dictionary for pickling

Used for pickling/unpickling EntryAttributes and StatvfsData objects.

Common Patterns

Inode Management

import pyfuse3
import stat
import time

class InodeManager:
    def __init__(self):
        self.inodes = {}
        self.next_inode = pyfuse3.ROOT_INODE + 1
    
    def create_entry(self, mode, uid, gid):
        """Create new entry attributes."""
        entry = pyfuse3.EntryAttributes()
        entry.st_ino = self.next_inode
        self.next_inode += 1
        
        entry.st_mode = mode
        entry.st_nlink = 1
        entry.st_uid = uid
        entry.st_gid = gid
        entry.st_size = 0
        entry.st_blksize = 4096
        entry.st_blocks = 0
        
        now_ns = time.time_ns()
        entry.st_atime_ns = now_ns
        entry.st_mtime_ns = now_ns
        entry.st_ctime_ns = now_ns
        
        entry.entry_timeout = 1.0
        entry.attr_timeout = 1.0
        
        self.inodes[entry.st_ino] = entry
        return entry

Time Management

import time

def current_time_ns():
    """Get current time in nanoseconds."""
    return time.time_ns()

def update_times(entry, atime=False, mtime=False, ctime=True):
    """Update entry timestamps."""
    now_ns = current_time_ns()
    if atime:
        entry.st_atime_ns = now_ns
    if mtime:
        entry.st_mtime_ns = now_ns
    if ctime:
        entry.st_ctime_ns = now_ns

Size and Blocks

import math

def update_size(entry, new_size):
    """Update size and blocks."""
    entry.st_size = new_size
    
    # Calculate blocks (512-byte units)
    block_size = 4096
    blocks_used = math.ceil(new_size / block_size)
    entry.st_blocks = blocks_used * (block_size // 512)

Performance Considerations

Cache Timeouts

# Fast-changing data: short timeouts
entry.entry_timeout = 0.1
entry.attr_timeout = 0.1

# Stable data: long timeouts
entry.entry_timeout = 300.0
entry.attr_timeout = 300.0

# Immutable data: infinite timeouts
entry.entry_timeout = float('inf')
entry.attr_timeout = float('inf')

File Handle Allocation

class EfficientFileHandles:
    def __init__(self):
        self.next_fh = 1
        self.free_fhs = []
        self.open_files = {}
    
    def allocate_fh(self, inode):
        """Allocate file handle, reusing freed handles."""
        if self.free_fhs:
            fh = self.free_fhs.pop()
        else:
            fh = self.next_fh
            self.next_fh += 1
        
        self.open_files[fh] = inode
        return fh
    
    def release_fh(self, fh):
        """Release file handle for reuse."""
        if fh in self.open_files:
            del self.open_files[fh]
            self.free_fhs.append(fh)

Platform Differences

Linux

  • st_birthtime_ns always 0 (not supported)
  • All fields in EntryAttributes supported
  • StatvfsData fully supported

macOS

  • st_birthtime_ns supported (file creation time)
  • All EntryAttributes fields supported
  • StatvfsData fully supported

BSD

  • Varies by BSD variant
  • Check documentation for specific BSD