Skip to content

Commit 5b11f19

Browse files
[FIX] fs_storage: Resolve rooted_dir sub path is inside path virtually
Before this fix, a FileNotFoundError would ocassionally arise when 'os.getcwd()' is called due to the rooted_dir _join method to check whether the sub path is part of the parent path. This is probably caused by the number and/or combination of workers and threads and the transactions to the FS storage, having race issues. After this commit, the sub path is checked if it is contained inside the parent path virtually via posixpath and pathlib modules avoiding 'os.getcwd()' calls and running into this possible race condition.
1 parent b50f468 commit 5b11f19

File tree

1 file changed

+25
-12
lines changed

1 file changed

+25
-12
lines changed
Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Copyright 2023 ACSONE SA/NV (https://www.acsone.eu).
22
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
33

4-
import os
4+
import posixpath
5+
from pathlib import PurePosixPath
56

67
from fsspec.implementations.dirfs import DirFileSystem
7-
from fsspec.implementations.local import make_path_posix
88
from fsspec.registry import register_implementation
99

1010

@@ -20,18 +20,31 @@ class RootedDirFileSystem(DirFileSystem):
2020
"""
2121

2222
def _join(self, path):
23-
path = super()._join(path)
24-
# Ensure that the path is a subpath of the root path by resolving
25-
# any relative paths.
26-
# Since the path separator is not always the same on all systems,
27-
# we need to normalize the path separator.
28-
path_posix = os.path.normpath(make_path_posix(path))
29-
root_posix = os.path.normpath(make_path_posix(self.path))
30-
if not path_posix.startswith(root_posix):
23+
if path is not None and str(path) != "":
24+
target = PurePosixPath(str(path))
25+
if target.is_absolute() or (":" in str(target).split("/")[0]):
26+
raise PermissionError(
27+
f"Absolute paths are not allowed in rooted_dir; got {path}"
28+
)
29+
30+
joined = super()._join(path)
31+
32+
root = PurePosixPath(self.path).as_posix()
33+
candidate = PurePosixPath(joined or ".").as_posix()
34+
35+
# Anchor candidate under root
36+
candidate = posixpath.join(root, candidate)
37+
38+
# Normalize both paths to check and enforce containment
39+
rnorm = posixpath.normpath(root)
40+
cnorm = posixpath.normpath(candidate)
41+
42+
if not (cnorm == rnorm or cnorm.startswith(rnorm + "/")):
3143
raise PermissionError(
32-
"Path %s is not a subpath of the root path %s" % (path, self.path)
44+
f"Path {path} is not a subpath of the root path {self.path}"
3345
)
34-
return path
46+
47+
return joined
3548

3649

3750
register_implementation("rooted_dir", RootedDirFileSystem)

0 commit comments

Comments
 (0)