storage: add and take use of flag _filesystem_root_folder_is_collision_free

This commit is contained in:
Peter Bieringer
2026-04-14 09:00:04 +02:00
parent f6eb5634cd
commit 7a55e8164b
8 changed files with 18 additions and 11 deletions

View File

@@ -172,6 +172,9 @@ class Storage(
logger.warning("Storage location subfolder: %r does not exist, creating now", self._get_collection_root_folder()) logger.warning("Storage location subfolder: %r does not exist, creating now", self._get_collection_root_folder())
self._makedirs_synced(self._get_collection_root_folder()) self._makedirs_synced(self._get_collection_root_folder())
logger.info("Storage location subfolder permissions: %s", pathutils.path_permissions_as_string(self._get_collection_root_folder())) logger.info("Storage location subfolder permissions: %s", pathutils.path_permissions_as_string(self._get_collection_root_folder()))
logger.info("Storage location subfolder softlink support: %s", pathutils.path_supports_symlink(self._get_collection_root_folder()))
self._filesystem_root_folder_is_collision_free = pathutils.path_is_collision_free(self._get_collection_root_folder())
logger.info("Storage location subfolder is collision free: %s", self._filesystem_root_folder_is_collision_free)
logger.info("Storage cache subfolder usage for 'item': %s", self._use_cache_subfolder_for_item) logger.info("Storage cache subfolder usage for 'item': %s", self._use_cache_subfolder_for_item)
logger.info("Storage cache subfolder usage for 'history': %s", self._use_cache_subfolder_for_history) logger.info("Storage cache subfolder usage for 'history': %s", self._use_cache_subfolder_for_history)
logger.info("Storage cache subfolder usage for 'sync-token': %s", self._use_cache_subfolder_for_synctoken) logger.info("Storage cache subfolder usage for 'sync-token': %s", self._use_cache_subfolder_for_synctoken)

View File

@@ -32,6 +32,7 @@ class CollectionBase(storage.BaseCollection):
_path: str _path: str
_encoding: str _encoding: str
_filesystem_path: str _filesystem_path: str
_filesystem_root_folder_is_collision_free: bool
def __init__(self, storage_: "multifilesystem.Storage", path: str, def __init__(self, storage_: "multifilesystem.Storage", path: str,
filesystem_path: Optional[str] = None) -> None: filesystem_path: Optional[str] = None) -> None:
@@ -42,8 +43,9 @@ class CollectionBase(storage.BaseCollection):
self._path = pathutils.strip_path(path) self._path = pathutils.strip_path(path)
self._encoding = storage_.configuration.get("encoding", "stock") self._encoding = storage_.configuration.get("encoding", "stock")
self._skip_broken_item = storage_.configuration.get("storage", "skip_broken_item") self._skip_broken_item = storage_.configuration.get("storage", "skip_broken_item")
self._filesystem_root_folder_is_collision_free = storage_._filesystem_root_folder_is_collision_free
if filesystem_path is None: if filesystem_path is None:
filesystem_path = pathutils.path_to_filesystem(folder, self.path) filesystem_path = pathutils.path_to_filesystem(folder, self.path, self._filesystem_root_folder_is_collision_free)
self._filesystem_path = filesystem_path self._filesystem_path = filesystem_path
# TODO: better fix for "mypy" # TODO: better fix for "mypy"
@@ -79,6 +81,7 @@ class StorageBase(storage.BaseStorage):
_folder_umask: str _folder_umask: str
_config_umask: int _config_umask: int
_max_resource_size: int _max_resource_size: int
_filesystem_root_folder_is_collision_free: bool = False
def __init__(self, configuration: config.Configuration) -> None: def __init__(self, configuration: config.Configuration) -> None:
super().__init__(configuration) super().__init__(configuration)

View File

@@ -65,7 +65,7 @@ class StoragePartCreateCollection(StorageBase):
# Path should already be sanitized # Path should already be sanitized
sane_path = pathutils.strip_path(href) sane_path = pathutils.strip_path(href)
filesystem_path = pathutils.path_to_filesystem(folder, sane_path) filesystem_path = pathutils.path_to_filesystem(folder, sane_path, self._filesystem_root_folder_is_collision_free)
logger.debug("Create collection: %r" % filesystem_path) logger.debug("Create collection: %r" % filesystem_path)
if not props: if not props:

View File

@@ -46,7 +46,7 @@ class CollectionPartDelete(CollectionPartHistory, CollectionBase):
# Delete an item # Delete an item
if not pathutils.is_safe_filesystem_path_component(href): if not pathutils.is_safe_filesystem_path_component(href):
raise pathutils.UnsafePathError(href) raise pathutils.UnsafePathError(href)
path = pathutils.path_to_filesystem(self._filesystem_path, href) path = pathutils.path_to_filesystem(self._filesystem_path, href, self._filesystem_root_folder_is_collision_free)
if not os.path.isfile(path): if not os.path.isfile(path):
raise storage.ComponentNotFoundError(href) raise storage.ComponentNotFoundError(href)
os.remove(path) os.remove(path)

View File

@@ -52,11 +52,11 @@ class StoragePartDiscover(StorageBase):
# Create the root collection # Create the root collection
self._makedirs_synced(folder) self._makedirs_synced(folder)
try: try:
filesystem_path = pathutils.path_to_filesystem(folder, sane_path) filesystem_path = pathutils.path_to_filesystem(folder, sane_path, self._filesystem_root_folder_is_collision_free)
except ValueError as e: except ValueError as e:
# Path is unsafe # Path is unsafe
logger.debug("Unsafe path %r requested from storage: %s", logger.warning("Unsafe path %r requested from storage: %s",
sane_path, e, exc_info=True) sane_path, e, exc_info=False)
return return
# Check if the path exists and if it leads to a collection or an item # Check if the path exists and if it leads to a collection or an item
@@ -110,7 +110,7 @@ class StoragePartDiscover(StorageBase):
href = base64.b64encode(group.encode('utf-8')).decode('ascii') href = base64.b64encode(group.encode('utf-8')).decode('ascii')
logger.debug(f"searching for group calendar {group} {href}") logger.debug(f"searching for group calendar {group} {href}")
sane_child_path = f"GROUPS/{href}" sane_child_path = f"GROUPS/{href}"
if not os.path.isdir(pathutils.path_to_filesystem(folder, sane_child_path)): if not os.path.isdir(pathutils.path_to_filesystem(folder, sane_child_path, self._filesystem_root_folder_is_collision_free)):
continue continue
child_path = f"/GROUPS/{href}/" child_path = f"/GROUPS/{href}/"
with child_context_manager(sane_child_path, None): with child_context_manager(sane_child_path, None):

View File

@@ -59,7 +59,8 @@ class CollectionPartGet(CollectionPartCache, CollectionPartLock,
if not pathutils.is_safe_filesystem_path_component(href): if not pathutils.is_safe_filesystem_path_component(href):
raise pathutils.UnsafePathError(href) raise pathutils.UnsafePathError(href)
path = pathutils.path_to_filesystem(self._filesystem_path, path = pathutils.path_to_filesystem(self._filesystem_path,
href) href,
self._filesystem_root_folder_is_collision_free)
except ValueError as e: except ValueError as e:
logger.debug( logger.debug(
"Can't translate name %r safely to filesystem in %r: %s", "Can't translate name %r safely to filesystem in %r: %s",

View File

@@ -35,8 +35,8 @@ class StoragePartMove(StorageBase):
assert isinstance(to_collection, multifilesystem.Collection) assert isinstance(to_collection, multifilesystem.Collection)
assert isinstance(item.collection, multifilesystem.Collection) assert isinstance(item.collection, multifilesystem.Collection)
assert item.href assert item.href
move_from = pathutils.path_to_filesystem(item.collection._filesystem_path, item.href) move_from = pathutils.path_to_filesystem(item.collection._filesystem_path, item.href, self._filesystem_root_folder_is_collision_free)
move_to = pathutils.path_to_filesystem(to_collection._filesystem_path, to_href) move_to = pathutils.path_to_filesystem(to_collection._filesystem_path, to_href, self._filesystem_root_folder_is_collision_free)
try: try:
os.replace(move_from, move_to) os.replace(move_from, move_to)
except OSError as e: except OSError as e:

View File

@@ -39,7 +39,7 @@ class CollectionPartUpload(CollectionPartGet, CollectionPartCache,
) -> Tuple[radicale_item.Item, Optional[radicale_item.Item]]: ) -> Tuple[radicale_item.Item, Optional[radicale_item.Item]]:
if not pathutils.is_safe_filesystem_path_component(href): if not pathutils.is_safe_filesystem_path_component(href):
raise pathutils.UnsafePathError(href) raise pathutils.UnsafePathError(href)
path = pathutils.path_to_filesystem(self._filesystem_path, href) path = pathutils.path_to_filesystem(self._filesystem_path, href, self._filesystem_root_folder_is_collision_free)
old_item = self._get(href, verify_href=False) old_item = self._get(href, verify_href=False)
try: try:
with self._atomic_write(path, newline="") as fo: # type: ignore with self._atomic_write(path, newline="") as fo: # type: ignore