summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTiago Gomes <tiago.gomes@codethink.co.uk>2018-07-31 12:34:25 +0100
committerTiago Gomes <tiago.gomes@codethink.co.uk>2018-08-02 12:32:29 +0100
commit70e3bec83484663e264428ae2b62c21b5dc08235 (patch)
treec4c9905424aa5d3dcd9f6a6137a10e2d27813cf0
parent5171cb0ebc9f5d372820e33463b4192fbbc6ec64 (diff)
downloadbuildstream-70e3bec83484663e264428ae2b62c21b5dc08235.tar.gz
plugin: bake API to get and validate a project path
A project path is a path relative to a project directory. A project path can not also refer to the parent directory in the first path component, or point to symbolic links, fifos, sockets and block/character devices.
-rw-r--r--.pylintrc2
-rw-r--r--buildstream/_exceptions.py10
-rw-r--r--buildstream/_yaml.py92
-rw-r--r--buildstream/plugin.py47
4 files changed, 147 insertions, 4 deletions
diff --git a/.pylintrc b/.pylintrc
index 93f9eeadf..153a9ef3e 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -184,7 +184,7 @@ ignore-on-opaque-inference=yes
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
-ignored-classes=optparse.Values,thread._local,_thread._local,contextlib.closing,gi.repository.GLib.GError
+ignored-classes=optparse.Values,thread._local,_thread._local,contextlib.closing,gi.repository.GLib.GError,pathlib.PurePath
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
diff --git a/buildstream/_exceptions.py b/buildstream/_exceptions.py
index c86b6780c..857d9be4c 100644
--- a/buildstream/_exceptions.py
+++ b/buildstream/_exceptions.py
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2016 Codethink Limited
+# Copyright (C) 2018 Codethink Limited
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -16,6 +16,7 @@
#
# Authors:
# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
+# Tiago Gomes <tiago.gomes@codethink.co.uk>
from enum import Enum
@@ -205,6 +206,13 @@ class LoadErrorReason(Enum):
# Try to load a directory not a yaml file
LOADING_DIRECTORY = 18
+ # A project path leads outside of the project directory
+ PROJ_PATH_INVALID = 19
+
+ # A project path points to a file of the not right kind (e.g. a
+ # socket)
+ PROJ_PATH_INVALID_KIND = 20
+
# LoadError
#
diff --git a/buildstream/_yaml.py b/buildstream/_yaml.py
index 0e090e2e7..33ee444aa 100644
--- a/buildstream/_yaml.py
+++ b/buildstream/_yaml.py
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2016 Codethink Limited
+# Copyright (C) 2018 Codethink Limited
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -22,6 +22,7 @@ import collections
import string
from copy import deepcopy
from contextlib import ExitStack
+from pathlib import Path
from ruamel import yaml
from ruamel.yaml.representer import SafeRepresenter, RoundTripRepresenter
@@ -392,6 +393,95 @@ def node_get(node, expected_type, key, indices=None, default_value=_get_sentinel
return value
+# node_get_project_path()
+#
+# Fetches a project path from a dictionary node and validates it
+#
+# Paths are asserted to never lead to a directory outside of the project
+# directory. In addition, paths can not point to symbolic links, fifos,
+# sockets and block/character devices.
+#
+# The `check_is_file` and `check_is_dir` parameters can be used to
+# perform additional validations on the path. Note that an exception
+# will always be raised if both parameters are set to ``True``.
+#
+# Args:
+# node (dict): A dictionary loaded from YAML
+# key (str): The key whose value contains a path to validate
+# project_dir (str): The project directory
+# check_is_file (bool): If ``True`` an error will also be raised
+# if path does not point to a regular file.
+# Defaults to ``False``
+# check_is_dir (bool): If ``True`` an error will be also raised
+# if path does not point to a directory.
+# Defaults to ``False``
+# Returns:
+# (str): The project path
+#
+# Raises:
+# (LoadError): In case that the project path is not valid or does not
+# exist
+#
+def node_get_project_path(node, key, project_dir, *,
+ check_is_file=False, check_is_dir=False):
+ path_str = node_get(node, str, key)
+ path = Path(path_str)
+ project_dir_path = Path(project_dir)
+
+ provenance = node_get_provenance(node, key=key)
+
+ if (project_dir_path / path).is_symlink():
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
+ "{}: Specified path '{}' must not point to "
+ "symbolic links "
+ .format(provenance, path_str))
+
+ if path.parts and path.parts[0] == '..':
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID,
+ "{}: Specified path '{}' first component must "
+ "not be '..'"
+ .format(provenance, path_str))
+
+ try:
+ if sys.version_info[0] == 3 and sys.version_info[1] < 6:
+ full_resolved_path = (project_dir_path / path).resolve()
+ else:
+ full_resolved_path = (project_dir_path / path).resolve(strict=True)
+ except FileNotFoundError:
+ raise LoadError(LoadErrorReason.MISSING_FILE,
+ "{}: Specified path '{}' does not exist"
+ .format(provenance, path_str))
+
+ is_inside = project_dir_path in full_resolved_path.parents or (
+ full_resolved_path == project_dir_path)
+
+ if path.is_absolute() or not is_inside:
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID,
+ "{}: Specified path '{}' must not lead outside of the "
+ "project directory"
+ .format(provenance, path_str))
+
+ if full_resolved_path.is_socket() or (
+ full_resolved_path.is_fifo() or
+ full_resolved_path.is_block_device()):
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
+ "{}: Specified path '{}' points to an unsupported "
+ "file kind"
+ .format(provenance, path_str))
+
+ if check_is_file and not full_resolved_path.is_file():
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
+ "{}: Specified path '{}' is not a regular file"
+ .format(provenance, path_str))
+
+ if check_is_dir and not full_resolved_path.is_dir():
+ raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
+ "{}: Specified path '{}' is not a directory"
+ .format(provenance, path_str))
+
+ return path_str
+
+
# node_items()
#
# A convenience generator for iterating over loaded key/value
diff --git a/buildstream/plugin.py b/buildstream/plugin.py
index 155a9500e..836b60834 100644
--- a/buildstream/plugin.py
+++ b/buildstream/plugin.py
@@ -1,5 +1,5 @@
#
-# Copyright (C) 2017 Codethink Limited
+# Copyright (C) 2018 Codethink Limited
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -335,6 +335,51 @@ class Plugin():
"""
return _yaml.node_get(node, expected_type, member_name, default_value=default)
+ def node_get_project_path(self, node, key, *,
+ check_is_file=False, check_is_dir=False):
+ """Fetches a project path from a dictionary node and validates it
+
+ Paths are asserted to never lead to a directory outside of the
+ project directory. In addition, paths can not point to symbolic
+ links, fifos, sockets and block/character devices.
+
+ The `check_is_file` and `check_is_dir` parameters can be used to
+ perform additional validations on the path. Note that an
+ exception will always be raised if both parameters are set to
+ ``True``.
+
+ Args:
+ node (dict): A dictionary loaded from YAML
+ key (str): The key whose value contains a path to validate
+ check_is_file (bool): If ``True`` an error will also be raised
+ if path does not point to a regular file.
+ Defaults to ``False``
+ check_is_dir (bool): If ``True`` an error will also be raised
+ if path does not point to a directory.
+ Defaults to ``False``
+
+ Returns:
+ (str): The project path
+
+ Raises:
+ :class:`.LoadError`: In the case that the project path is not
+ valid or does not exist
+
+ *Since: 1.2*
+
+ **Example:**
+
+ .. code:: python
+
+ path = self.node_get_project_path(node, 'path')
+
+ """
+
+ return _yaml.node_get_project_path(node, key,
+ self.__project.directory,
+ check_is_file=check_is_file,
+ check_is_dir=check_is_dir)
+
def node_validate(self, node, valid_keys):
"""This should be used in :func:`~buildstream.plugin.Plugin.configure`
implementations to assert that users have only entered