Changeset View
Changeset View
Standalone View
Standalone View
swh/fuse/fuse.py
Show First 20 Lines • Show All 178 Lines • ▼ Show 20 Lines | async def readdir(self, fh: int, offset: int, token: pyfuse3.ReaddirToken) -> None: | ||||
name = os.fsencode(entry.name) | name = os.fsencode(entry.name) | ||||
attrs = await self.get_attrs(entry) | attrs = await self.get_attrs(entry) | ||||
if not pyfuse3.readdir_reply(token, name, attrs, next_id): | if not pyfuse3.readdir_reply(token, name, attrs, next_id): | ||||
break | break | ||||
next_id += 1 | next_id += 1 | ||||
self._inode2entry[attrs.st_ino] = entry | self._inode2entry[attrs.st_ino] = entry | ||||
except Exception as err: | except Exception as err: | ||||
logging.debug(f"Cannot readdir: {err}") | logging.exception(f"Cannot readdir: {err}") | ||||
raise pyfuse3.FUSEError(errno.ENOENT) | raise pyfuse3.FUSEError(errno.ENOENT) | ||||
async def open( | async def open( | ||||
self, inode: int, _flags: int, _ctx: pyfuse3.RequestContext | self, inode: int, _flags: int, _ctx: pyfuse3.RequestContext | ||||
) -> pyfuse3.FileInfo: | ) -> pyfuse3.FileInfo: | ||||
""" Open an inode and return a unique file handle """ | """ Open an inode and return a unique file handle """ | ||||
# Re-use inode as file handle | # Re-use inode as file handle | ||||
return pyfuse3.FileInfo(fh=inode, keep_cache=True) | return pyfuse3.FileInfo(fh=inode, keep_cache=True) | ||||
async def read(self, fh: int, offset: int, length: int) -> bytes: | async def read(self, fh: int, offset: int, length: int) -> bytes: | ||||
""" Read `length` bytes from file handle `fh` at position `offset` """ | """ Read `length` bytes from file handle `fh` at position `offset` """ | ||||
# open() uses inode as file handle | # open() uses inode as file handle | ||||
inode = fh | inode = fh | ||||
entry = self.inode2entry(inode) | entry = self.inode2entry(inode) | ||||
assert isinstance(entry, FuseFileEntry) | assert isinstance(entry, FuseFileEntry) | ||||
try: | try: | ||||
data = await entry.get_content() | data = await entry.get_content() | ||||
return data[offset : offset + length] | return data[offset : offset + length] | ||||
except Exception as err: | except Exception as err: | ||||
logging.debug(f"Cannot read: {err}") | logging.exception(f"Cannot read: {err}") | ||||
raise pyfuse3.FUSEError(errno.ENOENT) | raise pyfuse3.FUSEError(errno.ENOENT) | ||||
async def lookup( | async def lookup( | ||||
self, parent_inode: int, name: str, _ctx: pyfuse3.RequestContext | self, parent_inode: int, name: str, _ctx: pyfuse3.RequestContext | ||||
) -> pyfuse3.EntryAttributes: | ) -> pyfuse3.EntryAttributes: | ||||
""" Look up a directory entry by name and get its attributes """ | """ Look up a directory entry by name and get its attributes """ | ||||
name = os.fsdecode(name) | name = os.fsdecode(name) | ||||
parent_entry = self.inode2entry(parent_inode) | parent_entry = self.inode2entry(parent_inode) | ||||
assert isinstance(parent_entry, FuseDirEntry) | assert isinstance(parent_entry, FuseDirEntry) | ||||
try: | try: | ||||
lookup_entry = await parent_entry.lookup(name) | lookup_entry = await parent_entry.lookup(name) | ||||
if lookup_entry: | if lookup_entry: | ||||
return await self.get_attrs(lookup_entry) | return await self.get_attrs(lookup_entry) | ||||
else: | else: | ||||
raise ValueError(f"unknown name: {name}") | raise ValueError(f"unknown name: {name}") | ||||
except Exception as err: | except Exception as err: | ||||
logging.debug(f"Cannot lookup: {err}") | logging.exception(f"Cannot lookup: {err}") | ||||
raise pyfuse3.FUSEError(errno.ENOENT) | raise pyfuse3.FUSEError(errno.ENOENT) | ||||
async def readlink(self, inode: int, _ctx: pyfuse3.RequestContext) -> bytes: | async def readlink(self, inode: int, _ctx: pyfuse3.RequestContext) -> bytes: | ||||
entry = self.inode2entry(inode) | entry = self.inode2entry(inode) | ||||
assert isinstance(entry, FuseSymlinkEntry) | assert isinstance(entry, FuseSymlinkEntry) | ||||
return os.fsencode(entry.get_target()) | return os.fsencode(entry.get_target()) | ||||
async def main(swhids: List[SWHID], root_path: Path, conf: Dict[str, Any]) -> None: | async def main(swhids: List[SWHID], root_path: Path, conf: Dict[str, Any]) -> None: | ||||
""" swh-fuse CLI entry-point """ | """ swh-fuse CLI entry-point """ | ||||
# Use pyfuse3 asyncio layer to match the rest of Software Heritage codebase | # Use pyfuse3 asyncio layer to match the rest of Software Heritage codebase | ||||
pyfuse3_asyncio.enable() | pyfuse3_asyncio.enable() | ||||
async with FuseCache(conf["cache"]) as cache: | async with FuseCache(conf["cache"]) as cache: | ||||
fs = Fuse(root_path, cache, conf) | fs = Fuse(root_path, cache, conf) | ||||
# Initially populate the cache | # Initially populate the cache | ||||
for swhid in swhids: | for swhid in swhids: | ||||
try: | try: | ||||
await fs.get_metadata(swhid) | await fs.get_metadata(swhid) | ||||
except Exception as err: | except Exception as err: | ||||
logging.error(f"Cannot prefetch object {swhid}: {err}") | logging.exception(f"Cannot prefetch object {swhid}: {err}") | ||||
fuse_options = set(pyfuse3.default_options) | fuse_options = set(pyfuse3.default_options) | ||||
fuse_options.add("fsname=swhfs") | fuse_options.add("fsname=swhfs") | ||||
if logging.root.level <= logging.DEBUG: | if logging.root.level <= logging.DEBUG: | ||||
fuse_options.add("debug") | fuse_options.add("debug") | ||||
try: | try: | ||||
pyfuse3.init(fs, root_path, fuse_options) | pyfuse3.init(fs, root_path, fuse_options) | ||||
await pyfuse3.main() | await pyfuse3.main() | ||||
except Exception as err: | except Exception as err: | ||||
logging.error(f"Error running FUSE: {err}") | logging.error(f"Error running FUSE: {err}") | ||||
finally: | finally: | ||||
fs.shutdown() | fs.shutdown() | ||||
pyfuse3.close(unmount=True) | pyfuse3.close(unmount=True) |