async def rename(self, parent_inode_old: INode, name_old: bytes, parent_inode_new: INode, name_new: bytes, flags: RenameFlags, ctx: RequestContext) -> None: if flags: raise FUSEError(errno.EINVAL) old_path = self.paths.join(parent_inode_old, name_old) new_path = self.paths.join(parent_inode_new, name_new) try: os.rename(src=old_path, dst=new_path) inode = cast(INode, os.lstat(new_path).st_ino) except OSError as exc: raise FUSEError(exc.errno) try: self.paths.replace_path(inode=inode, old_path=old_path, new_path=new_path) except KeyError: self.runtime_errors.unknown_path(inode=inode, path=old_path)
async def rmdir(self, inode_p, name, ctx): name = fsdecode(name) parent = self._inode_to_path(inode_p) path = os.path.join(parent, name) try: inode = os.lstat(path).st_ino os.rmdir(path) except OSError as exc: raise FUSEError(exc.errno) if inode in self._lookup_cnt: self._forget_path(inode, path)
async def mknod(self, parent_inode: INode, name: bytes, mode: FileMode, rdev: int, ctx: RequestContext) -> EntryAttributes: path = self.paths.join(parent_inode, name) try: os.mknod(path=path, mode=(mode & ~ctx.umask), device=rdev) os.chown(path=path, uid=ctx.uid, gid=ctx.gid) except OSError as exc: raise FUSEError(exc.errno) entry_attrs = self._get_entry_attrs(path) self.paths[entry_attrs.st_ino] = path return entry_attrs
async def release(self, fd): LOG.info('release fd %s', fd) # Since we opened all files with its own fd, # we only need to close the fd, and not care about lookup count try: del self._fd2cryptors[fd] except KeyError as exc: LOG.error('Already closed: %s', exc) except Exception as exc: LOG.error('Error closing %d: %s', fd, exc) raise FUSEError(errno.EBADF)
async def readlink( self, inode: InodeT, ctx: "RequestContext" ) -> FileNameT: '''Return target of symbolic link *inode*. *ctx* will be a `RequestContext` instance. ''' raise FUSEError(errno.ENOSYS)
async def statfs(self, ctx): LOG.info('Getting statfs') s = pyfuse3.StatvfsData() try: statfs = os.statvfs(self.rootdir) except OSError as exc: raise FUSEError(exc.errno) for attr in ('f_bsize', 'f_frsize', 'f_blocks', 'f_bfree', 'f_bavail', 'f_files', 'f_ffree', 'f_favail'): setattr(s, attr, getattr(statfs, attr)) #s.f_namemax = statfs.f_namemax - (len(self.rootdir)+1) return s
async def link(self, inode, new_inode_p, new_name, ctx): entry_p = await self.getattr(new_inode_p) if entry_p.st_nlink == 0: log.warn('Attempted to create entry %s with unlinked parent %d', new_name, new_inode_p) raise FUSEError(errno.EINVAL) self.cursor.execute( "INSERT INTO contents (name, inode, parent_inode) VALUES(?,?,?)", (new_name, inode, new_inode_p)) return await self.getattr(inode)
async def unlink(self, parent_inode: INode, name: bytes, ctx: RequestContext) -> None: path = self.paths.join(parent_inode, name) try: inode = cast(INode, os.lstat(path).st_ino) os.unlink(path) except OSError as exc: raise FUSEError(exc.errno) from None try: self.paths.forget_path(inode=inode, path=path) except KeyError: self.runtime_errors.unknown_path(inode=inode, path=path)
async def statfs(self, ctx): root = self._inode_path_map[pyfuse3.ROOT_INODE] stat_ = pyfuse3.StatvfsData() try: statfs = os.statvfs(root) except OSError as exc: raise FUSEError(exc.errno) for attr in ('f_bsize', 'f_frsize', 'f_blocks', 'f_bfree', 'f_bavail', 'f_files', 'f_ffree', 'f_favail'): setattr(stat_, attr, getattr(statfs, attr)) stat_.f_namemax = statfs.f_namemax - (len(root) + 1) return stat_
async def create(self, inode_p, name, mode, flags, ctx): path = os.path.join(self._inode_to_path(inode_p), fsdecode(name)) try: fd = os.open(path, flags | os.O_CREAT | os.O_TRUNC) except OSError as exc: raise FUSEError(exc.errno) attr = self._getattr(fd=fd) self._add_path(attr.st_ino, path) self._inode_fd_map[attr.st_ino] = fd self._fd_inode_map[fd] = attr.st_ino self._fd_open_count[fd] = 1 return (pyfuse3.FileInfo(fh=fd), attr)
async def statfs( self, ctx: "RequestContext" ) -> "StatvfsData": '''Get file system statistics. *ctx* will be a `RequestContext` instance. The method must return an appropriately filled `StatvfsData` instance. ''' raise FUSEError(errno.ENOSYS)
async def fsyncdir( self, fh: FileHandleT, datasync: bool ) -> None: '''Flush buffers for open directory *fh*. If *datasync* is true, only the directory contents should be flushed (in contrast to metadata about the directory itself). ''' raise FUSEError(errno.ENOSYS)
async def readlink(self, id_, ctx): log.debug('started with %d', id_) now_ns = time_ns() inode = self.inodes[id_] if inode.atime_ns < inode.ctime_ns or inode.atime_ns < inode.mtime_ns: inode.atime_ns = now_ns try: return self.db.get_val( "SELECT target FROM symlink_targets WHERE inode=?", (id_, )) except NoSuchRowError: log.warning('Inode does not have symlink target: %d', id_) raise FUSEError(errno.EINVAL)
async def lookup(self, inode_p, name, ctx=None): self.autenticado(ctx) self.lastContextt = ctx if not self.autenticadoB: raise FUSEError(1) name = fsdecode(name) path = os.path.join(self._inode_to_path(inode_p), name) attr = self._getattr(path=path) if name != '.' and name != '..': self._add_path(attr.st_ino, path) return attr
async def releasedir( self, fh: FileHandleT ) -> None: '''Release open directory. This method will be called exactly once for each `opendir` call. After *fh* has been released, no further `readdir` requests will be received for it (until it is opened again with `opendir`). ''' raise FUSEError(errno.ENOSYS)
def _create(self, id_p, name, mode, ctx, rdev=0, size=0): if name == CTRL_NAME: log.warning('Attempted to create s3ql control file at %s', get_path(id_p, self.db, name)) raise FUSEError(errno.EACCES) now_ns = time_ns() inode_p = self.inodes[id_p] if inode_p.locked: raise FUSEError(errno.EPERM) if inode_p.refcount == 0: log.warning('Attempted to create entry %s with unlinked parent %d', name, id_p) raise FUSEError(errno.EINVAL) inode_p.mtime_ns = now_ns inode_p.ctime_ns = now_ns if inode_p.mode & stat.S_ISGID: gid = inode_p.gid if stat.S_ISDIR(mode): mode |= stat.S_ISGID else: gid = ctx.gid inode = self.inodes.create_inode(mtime_ns=now_ns, ctime_ns=now_ns, atime_ns=now_ns, uid=ctx.uid, gid=gid, mode=mode, refcount=1, rdev=rdev, size=size) self.db.execute( "INSERT INTO contents(name_id, inode, parent_inode) VALUES(?,?,?)", (self._add_name(name), inode.id, id_p)) return inode
async def readlink(self, inode, ctx): """Return target of symbolic link *inode*. *ctx* will be a `RequestContext` instance. Currently, this function only works in this FUSE filesystem. """ entry = self.data.get_link_entry(inode, LinkTypes.SYMBOLIC) self.log.debug("Read link of {}".format(entry)) if entry is None: raise FUSEError(errno.ENOENT) self.log.debug("Real path: %s", entry.link_path) return os.fsencode(entry.link_path)
async def setxattr(self, inode, name, value, ctx): if inode != pyfuse3.ROOT_INODE or name != b'command': raise FUSEError(errno.ENOTSUP) if value == b'forget_entry': pyfuse3.invalidate_entry_async(pyfuse3.ROOT_INODE, self.hello_name) # Make sure that the request is pending before we return await trio.sleep(0.1) elif value == b'forget_inode': pyfuse3.invalidate_inode(self.hello_inode) elif value == b'store': pyfuse3.notify_store(self.hello_inode, offset=0, data=self.hello_data) elif value == b'terminate': pyfuse3.terminate() else: raise FUSEError(errno.EINVAL)
async def mknod(self, inode_p, name, mode, rdev, ctx): # create special or ordinary file # mostly used for fifo / pipes but nowadays mkfifo would be better suited for that # mostly rare use cases path = os.path.join(self._inode_to_path(inode_p), fsdecode(name)) try: os.mknod(path, mode=(mode & ~ctx.umask), device=rdev) os.chown(path, ctx.uid, ctx.gid) except OSError as exc: raise FUSEError(exc.errno) attr = FileInfo.getattr(path=path) self._add_path(attr.st_ino, path) return attr
async def create(self, parent_inode: INode, name: bytes, mode: FileMode, flags: int, ctx: RequestContext) -> Tuple[FileInfo, EntryAttributes]: path = self.paths.join(parent_inode, name) try: fd = os.open(path, flags | os.O_CREAT | os.O_TRUNC) except OSError as exc: raise FUSEError(exc.errno) entry_attrs = self._get_entry_attrs(fd) inode = entry_attrs.st_ino self.paths[inode] = path self.descriptors[inode] = fd return FileInfo(fh=fd), entry_attrs
async def release(self, fd): if self._fd_open_count[fd] > 1: self._fd_open_count[fd] -= 1 return del self._fd_open_count[fd] inode = self._fd_inode_map[fd] del self._inode_fd_map[inode] del self._fd_inode_map[fd] try: os.close(fd) except OSError as exc: raise FUSEError(exc.errno)
async def symlink(self, inode_p, name, target, ctx): name = fsdecode(name) target = fsdecode(target) parent = self._inode_to_path(inode_p) path = os.path.join(parent, name) try: os.symlink(target, path) os.chown(path, ctx.uid, ctx.gid, follow_symlinks=False) except OSError as exc: raise FUSEError(exc.errno) stat = os.lstat(path) self._add_path(stat.st_ino, path) return await self.getattr(stat.st_ino)
async def open(self, id_, flags, ctx): log.debug('started with %d', id_) if ((flags & os.O_RDWR or flags & os.O_WRONLY) and (self.failsafe or self.inodes[id_].locked)): raise FUSEError(errno.EPERM) if flags & os.O_TRUNC: if not (flags & os.O_RDWR or flags & os.O_WRONLY): # behaviour is not defined in POSIX, we opt for an error raise FUSEError(errno.EINVAL) attr = await self.getattr(id_, ctx) if stat.S_ISREG(attr.st_mode): attr.st_mtime_ns = time_ns() attr.st_size = 0 await self.setattr(id_, attr, _TruncSetattrFields, None, ctx) elif stat.S_ISFIFO(attr.st_mode) or stat.S_ISBLK(attr.st_mode): # silently ignore O_TRUNC when FIFO or terminal block device pass else: # behaviour is not defined in POSIX, we opt for an error raise FUSEError(errno.EINVAL) return pyfuse3.FileInfo(fh=id_, keep_cache=True)
def _get_entry_attrs( target: Union[str, FileDescriptor]) -> EntryAttributes: try: stat_result = os.lstat(target) if isinstance( target, str) else os.fstat(target) except OSError as exc: raise FUSEError(exc.errno) entry_attrs = EntryAttributes() for attr in dir(entry_attrs): if attr.startswith("st_") and hasattr(stat_result, attr): setattr(entry_attrs, attr, getattr(stat_result, attr)) entry_attrs.attr_timeout = 0 entry_attrs.entry_timeout = 0 return entry_attrs
async def open(self, inode, flags, ctx): if inode in self._inode_fd_map: fd = self._inode_fd_map[inode] self._fd_open_count[fd] += 1 return pyfuse3.FileInfo(fh=fd) assert flags & os.O_CREAT == 0 try: fd = os.open(self._inode_to_path(inode), flags) except OSError as exc: raise FUSEError(exc.errno) self._inode_fd_map[inode] = fd self._fd_inode_map[fd] = inode self._fd_open_count[fd] = 1 return pyfuse3.FileInfo(fh=fd)
async def symlink(self, parent_inode: INode, name: bytes, target: bytes, ctx: RequestContext) -> EntryAttributes: path = self.paths.join(parent_inode, name) try: os.symlink(src=os.fsdecode(target), dst=path) os.chown(path=path, uid=ctx.uid, gid=ctx.gid, follow_symlinks=False) except OSError as exc: raise FUSEError(exc.errno) from None symlink_inode = cast(INode, os.lstat(path).st_ino) self.paths[symlink_inode] = path return await self.getattr(inode=symlink_inode, ctx=ctx)
def _replace(self, id_p_old, name_old, id_p_new, name_new, id_old, id_new): now_ns = time_ns() if self.db.has_val("SELECT 1 FROM contents WHERE parent_inode=?", (id_new, )): log.info("Attempted to overwrite entry with children: %s", get_path(id_p_new, self.db, name_new)) raise FUSEError(errno.EINVAL) # Replace target name_id_new = self.db.get_val('SELECT id FROM names WHERE name=?', (name_new, )) self.db.execute( "UPDATE contents SET inode=? WHERE name_id=? AND parent_inode=?", (id_old, name_id_new, id_p_new)) # Delete old name name_id_old = self._del_name(name_old) self.db.execute( 'DELETE FROM contents WHERE name_id=? AND parent_inode=?', (name_id_old, id_p_old)) inode_new = self.inodes[id_new] inode_new.refcount -= 1 inode_new.ctime_ns = now_ns inode_p_old = self.inodes[id_p_old] inode_p_old.ctime_ns = now_ns inode_p_old.mtime_ns = now_ns inode_p_new = self.inodes[id_p_new] inode_p_new.ctime_ns = now_ns inode_p_new.mtime_ns = now_ns if inode_new.refcount == 0 and id_new not in self.open_inodes: self.cache.remove( id_new, 0, int(math.ceil(inode_new.size / self.max_obj_size))) # Since the inode is not open, it's not possible that new blocks # get created at this point and we can safely delete the inode self.db.execute( 'UPDATE names SET refcount = refcount - 1 WHERE ' 'id IN (SELECT name_id FROM ext_attributes WHERE inode=?)', (id_new, )) self.db.execute('DELETE FROM names WHERE refcount=0') self.db.execute('DELETE FROM ext_attributes WHERE inode=?', (id_new, )) self.db.execute('DELETE FROM symlink_targets WHERE inode=?', (id_new, )) del self.inodes[id_new]
def __init__(self, response): (success, response) = response if not success: if response == 2: raise (FUSEError(errno.ENOENT)) elif response == 6: raise (FUSEError(errno.ENOENT)) elif response == 9: raise (FUSEError(errno.EACCES)) elif response == 22: raise (FUSEError(errno.ENOENT)) elif response == 25: raise (FUSEError(errno.ENOENT)) else: raise (Exception(response)) if 'directory' in response: response = response['directory'] if 'file' in response: response = response['file'] self.response = response
async def getxattr(self, inode, name, ctx): """Return extended attribute *name* of *inode* *ctx* will be a `RequestContext` instance. If the attribute does not exist, the method must raise `FUSEError` with an error code of `ENOATTR`. *name* will be of type `bytes`, but is guaranteed not to contain zero-bytes (``\\0``). """ xattr = self.data.nodes[inode].xattr if name not in xattr: # https://github.com/libfuse/pyfuse3/blob/master/src/xattr.h ENOATTR = ENODATA raise FUSEError(errno.ENODATA) return xattr[name]
async def open(self, inode, flags, ctx): logger.debug(f'open() called for inode {inode}') if inode in self._inode_to_fd: fd = self._inode_to_fd[inode] self._fd_to_open_count[fd] += 1 return pyfuse3.FileInfo(fh=fd) else: asset = self._inode_to_asset[inode] if not asset: raise FUSEError(errno.ENOENT) try: fd = os.open(asset.original_path(self.photo_library.path), flags) except OSError as err: raise FUSEError(err.errno) self._inode_to_fd[inode] = fd self._fd_to_inode[fd] = inode self._fd_to_open_count[fd] = 1 return pyfuse3.FileInfo(fh=fd)