diff options
Diffstat (limited to 'src/buildstream/storage/_filebaseddirectory.py')
-rw-r--r-- | src/buildstream/storage/_filebaseddirectory.py | 81 |
1 files changed, 60 insertions, 21 deletions
diff --git a/src/buildstream/storage/_filebaseddirectory.py b/src/buildstream/storage/_filebaseddirectory.py index 07efa4598..53a87b64c 100644 --- a/src/buildstream/storage/_filebaseddirectory.py +++ b/src/buildstream/storage/_filebaseddirectory.py @@ -44,15 +44,13 @@ from .._exceptions import ImplError class FileBasedDirectory(Directory): - def __init__(self, external_directory=None): + def __init__(self, external_directory=None, *, parent=None): self.external_directory = external_directory + self.parent = parent def descend(self, *paths, create=False, follow_symlinks=False): """ See superclass Directory for arguments """ - if follow_symlinks: - ImplError("FileBasedDirectory.Decend dose not implement follow_symlinks=True") - current_dir = self for path in paths: @@ -60,21 +58,38 @@ class FileBasedDirectory(Directory): if not path: continue + if path == ".": + continue + if path == "..": + if current_dir.parent is not None: + current_dir = current_dir.parent + # In POSIX /.. == / so just stay at the root dir + continue + new_path = os.path.join(current_dir.external_directory, path) try: st = os.lstat(new_path) - if not stat.S_ISDIR(st.st_mode): + if stat.S_ISDIR(st.st_mode): + current_dir = FileBasedDirectory(new_path, parent=current_dir) + elif follow_symlinks and stat.S_ISLNK(st.st_mode): + linklocation = os.readlink(new_path) + newpaths = linklocation.split(os.path.sep) + if os.path.isabs(linklocation): + current_dir = current_dir._find_root().descend(*newpaths, follow_symlinks=True) + else: + current_dir = current_dir.descend(*newpaths, follow_symlinks=True) + else: raise VirtualDirectoryError( - "Cannot descend into '{}': '{}' is not a directory".format(path, new_path) + "Cannot descend into '{}': '{}' is not a directory".format(path, new_path), + reason="not-a-directory", ) except FileNotFoundError: if create: os.mkdir(new_path) + current_dir = FileBasedDirectory(new_path, parent=current_dir) else: raise VirtualDirectoryError("Cannot descend into '{}': '{}' does not exist".format(path, new_path)) - current_dir = FileBasedDirectory(new_path) - return current_dir def import_files( @@ -217,6 +232,35 @@ class FileBasedDirectory(Directory): def get_size(self): return utils._get_dir_size(self.external_directory) + def exists(self, *path, follow_symlinks=False): + try: + subdir = self.descend(*path[:-1], follow_symlinks=follow_symlinks) + newpath = os.path.join(subdir.external_directory, path[-1]) + st = os.lstat(newpath) + if follow_symlinks and stat.S_ISLNK(st.st_mode): + linklocation = os.readlink(newpath) + newpath = linklocation.split(os.path.sep) + if os.path.isabs(linklocation): + return subdir._find_root().exists(*newpath, follow_symlinks=True) + return subdir.exists(*newpath, follow_symlinks=True) + else: + return True + except (VirtualDirectoryError, FileNotFoundError): + return False + + def open_file(self, *path: str, mode: str = "r"): + # Use descend() to avoid following symlinks (potentially escaping the sandbox) + subdir = self.descend(*path[:-1]) + newpath = os.path.join(subdir.external_directory, path[-1]) + + if mode not in ["r", "rb", "w", "wb", "x", "xb"]: + raise ValueError("Unsupported mode: `{}`".format(mode)) + + if "r" in mode: + return open(newpath, mode=mode, encoding="utf-8") + else: + return utils.save_file_atomic(newpath, mode=mode, encoding="utf-8") + def __str__(self): # This returns the whole path (since we don't know where the directory started) # which exposes the sandbox directory; we will have to assume for the time being @@ -228,6 +272,14 @@ class FileBasedDirectory(Directory): object refers to. """ return self.external_directory + def _find_root(self): + """ Finds the root of this directory tree by following 'parent' until there is + no parent. """ + if self.parent: + return self.parent._find_root() + else: + return self + def _get_filetype(self, name=None): path = self.external_directory @@ -334,16 +386,3 @@ class FileBasedDirectory(Directory): assert entry.type == _FileType.SYMLINK os.symlink(entry.target, dest_path) result.files_written.append(relative_pathname) - - def _exists(self, *path, follow_symlinks=False): - """This is very simple but mirrors the cas based storage were it is less trivial""" - if follow_symlinks: - # The lexists is not ideal as it cant spot broken symlinks but this is a long - # standing bug in buildstream as exists follow absolute syslinks to real root - # and incorrectly thinks they are broken the new casbaseddirectory dose not have this bug. - return os.path.lexists(os.path.join(self.external_directory, *path)) - raise ImplError("_exists can only follow symlinks in filebaseddirectory") - - def _create_empty_file(self, name): - with open(os.path.join(self.external_directory, name), "w"): - pass |