diff --git a/swh/fuse/fs/artifact.py b/swh/fuse/fs/artifact.py --- a/swh/fuse/fs/artifact.py +++ b/swh/fuse/fs/artifact.py @@ -9,6 +9,7 @@ import logging import os from pathlib import Path +import re from typing import Any, AsyncIterator, Dict, List from swh.fuse.fs.entry import ( @@ -21,6 +22,8 @@ from swh.model.from_disk import DentryPerms from swh.model.identifiers import CONTENT, DIRECTORY, RELEASE, REVISION, SNAPSHOT, SWHID +SWHID_REGEXP = r"swh:1:(cnt|dir|rel|rev|snp):[0-9a-f]{40}" + @dataclass class Content(FuseFileEntry): @@ -257,6 +260,7 @@ is_status_done: bool = field(default=False) DATE_FMT = "{year:04d}/{month:02d}/{day:02d}/" + ENTRIES_REGEXP = re.compile(r"^([0-9]{2,4})|(" + SWHID_REGEXP + ")$") @dataclass class StatusFile(FuseFileEntry): @@ -324,6 +328,7 @@ prefix: str = field(default="") SHARDING_LENGTH = 2 + ENTRIES_REGEXP = re.compile(r"^([a-f0-9]+)|(" + SWHID_REGEXP + ")$") async def compute_entries(self) -> AsyncIterator[FuseEntry]: history = await self.fuse.get_history(self.history_swhid) @@ -362,6 +367,7 @@ PAGE_SIZE = 10_000 PAGE_FMT = "{page_number:03d}" + ENTRIES_REGEXP = re.compile(r"^([0-9]+)|(" + SWHID_REGEXP + ")$") async def compute_entries(self) -> AsyncIterator[FuseEntry]: history = await self.fuse.get_history(self.history_swhid) @@ -532,6 +538,7 @@ node. """ DATE_FMT = "{year:04d}-{month:02d}-{day:02d}" + ENTRIES_REGEXP = re.compile(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}$") async def compute_entries(self) -> AsyncIterator[FuseEntry]: # The origin's name is always its URL (encoded to create a valid UNIX filename) diff --git a/swh/fuse/fs/entry.py b/swh/fuse/fs/entry.py --- a/swh/fuse/fs/entry.py +++ b/swh/fuse/fs/entry.py @@ -8,8 +8,9 @@ from dataclasses import dataclass, field from enum import IntEnum from pathlib import Path +import re from stat import S_IFDIR, S_IFLNK, S_IFREG -from typing import Any, AsyncIterator, Sequence, Union +from typing import Any, AsyncIterator, Optional, Pattern, Sequence, Union # Avoid cycling import Fuse = "Fuse" @@ -60,6 +61,7 @@ return constructor(depth=self.depth + 1, fuse=self.fuse, **kwargs) +@dataclass class FuseFileEntry(FuseEntry): """ FUSE virtual file entry """ @@ -72,12 +74,24 @@ return len(await self.get_content()) +@dataclass class FuseDirEntry(FuseEntry): """ FUSE virtual directory entry """ + ENTRIES_REGEXP: Optional[Pattern] = field(init=False, default=None) + async def size(self) -> int: return 0 + def validate_entry(self, name: str) -> bool: + """ Return true if the name matches the directory entries regular + expression, and false otherwise """ + + if self.ENTRIES_REGEXP: + return re.match(self.ENTRIES_REGEXP, name) + else: + return True + async def compute_entries(self) -> Sequence[FuseEntry]: """ Return the child entries of a directory entry """ diff --git a/swh/fuse/fs/mountpoint.py b/swh/fuse/fs/mountpoint.py --- a/swh/fuse/fs/mountpoint.py +++ b/swh/fuse/fs/mountpoint.py @@ -5,9 +5,10 @@ from dataclasses import dataclass, field import json +import re from typing import AsyncIterator -from swh.fuse.fs.artifact import OBJTYPE_GETTERS, Origin +from swh.fuse.fs.artifact import OBJTYPE_GETTERS, SWHID_REGEXP, Origin from swh.fuse.fs.entry import EntryMode, FuseDirEntry, FuseEntry, FuseFileEntry from swh.model.exceptions import ValidationError from swh.model.identifiers import CONTENT, SWHID, parse_swhid @@ -38,6 +39,7 @@ name: str = field(init=False, default="archive") mode: int = field(init=False, default=int(EntryMode.RDONLY_DIR)) + ENTRIES_REGEXP = re.compile(r"^(" + SWHID_REGEXP + ")(.json)?$") JSON_SUFFIX = ".json" async def compute_entries(self) -> AsyncIterator[FuseEntry]: @@ -100,6 +102,8 @@ name: str = field(init=False, default="origin") mode: int = field(init=False, default=int(EntryMode.RDONLY_DIR)) + ENTRIES_REGEXP = re.compile(r"^.*%3A.*$") # %3A is the encoded version of ':' + def create_child(self, url_encoded: str) -> FuseEntry: return super().create_child( Origin, name=url_encoded, mode=int(EntryMode.RDONLY_DIR), diff --git a/swh/fuse/fuse.py b/swh/fuse/fuse.py --- a/swh/fuse/fuse.py +++ b/swh/fuse/fuse.py @@ -287,9 +287,10 @@ assert isinstance(parent_entry, FuseDirEntry) try: - lookup_entry = await parent_entry.lookup(name) - if lookup_entry: - return await self.get_attrs(lookup_entry) + if parent_entry.validate_entry(name): + lookup_entry = await parent_entry.lookup(name) + if lookup_entry: + return await self.get_attrs(lookup_entry) except Exception as err: self.logger.exception("Cannot lookup: %s", err)