blob: 42c9951861929f412d6742e652dff43e7d38080b [file] [log] [blame]
# Copyright 2017 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Schema elements.
This module provides schema elements that can be used to build up a schema for
validation of the master configuration
"""
from __future__ import print_function
import re
def CheckPhandleTarget(val, target, target_path_match):
"""Check that the target of a phandle matches a pattern
Args:
val: Validator (used for model list, etc.)
target: Target node path (string)
target_path_match: Match string. This is the full path to the node that the
target must point to. Some 'wildcard' nodes are supported in the path:
MODEL - matches any model node
SUBMODEL - matches any submodel when MODEL is earlier in the path
ANY - matches any node
Returns:
True if the target matches, False if not
"""
model = None
parts = target_path_match.split('/')
target_parts = target.path.split('/')
ok = len(parts) == len(target_parts)
if ok:
for i, part in enumerate(parts):
if part == 'MODEL':
if target_parts[i] in val.model_list:
model = target_parts[i]
continue
if part == 'SUBMODEL' and model:
if target_parts[i] in val.submodel_list[model]:
continue
elif part == 'ANY':
continue
if part != target_parts[i]:
ok = False
return ok
class SchemaElement(object):
"""A schema element, either a property or a subnode
Args:
name: Name of schema eleent
prop_type: String describing this property type
required: True if this element is mandatory, False if optional
conditional_props: Properties which control whether this element is present.
Dict:
key: name of controlling property
value: True if the property must be present, False if it must be absent
"""
def __init__(self, name, prop_type, required=False, conditional_props=None):
self.name = name
self.prop_type = prop_type
self.required = required
self.conditional_props = conditional_props
self.parent = None
def Validate(self, val, prop):
"""Validate the schema element against the given property.
This method is overridden by subclasses. It should call val.Fail() if there
is a problem during validation.
Args:
val: CrosConfigValidator object
prop: Prop object of the property
"""
pass
class PropDesc(SchemaElement):
"""A generic property schema element (base class for properties)"""
def __init__(self, name, prop_type, required=False, conditional_props=None):
super(PropDesc, self).__init__(name, prop_type, required, conditional_props)
class PropString(PropDesc):
"""A string-property
Args:
str_pattern: Regex to use to validate the string
"""
def __init__(self, name, required=False, str_pattern='',
conditional_props=None):
super(PropString, self).__init__(name, 'string', required,
conditional_props)
self.str_pattern = str_pattern
def Validate(self, val, prop):
"""Check the string with a regex"""
if not self.str_pattern:
return
pattern = '^' + self.str_pattern + '$'
m = re.match(pattern, prop.value)
if not m:
val.Fail(prop.node.path, "'%s' value '%s' does not match pattern '%s'" %
(prop.name, prop.value, pattern))
class PropFile(PropDesc):
"""A file property
This represents a file to be installed on the filesystem.
Properties:
target_dir: Target directory in the filesystem for files from this
property (e.g. '/etc/cras'). This is used to set the install directory
and keep it consistent across ebuilds (which use cros_config_host) and
init scripts (which use cros_config). The actual file written will be
relative to this.
"""
def __init__(self, name, required=False, str_pattern='',
conditional_props=None, target_dir=None):
super(PropFile, self).__init__(name, 'file', required, conditional_props)
self.str_pattern = str_pattern
self.target_dir = target_dir
def Validate(self, val, prop):
"""Check the filename with a regex"""
if not self.str_pattern:
return
pattern = '^' + self.str_pattern + '$'
m = re.match(pattern, prop.value)
if not m:
val.Fail(prop.node.path, "'%s' value '%s' does not match pattern '%s'" %
(prop.name, prop.value, pattern))
class PropStringList(PropDesc):
"""A string-list property schema element
Note that the list may be empty in which case no validation is performed.
Args:
str_pattern: Regex to use to validate the string
"""
def __init__(self, name, required=False, str_pattern='',
conditional_props=None):
super(PropStringList, self).__init__(name, 'stringlist', required,
conditional_props)
self.str_pattern = str_pattern
def Validate(self, val, prop):
"""Check each item of the list with a regex"""
if not self.str_pattern:
return
pattern = '^' + self.str_pattern + '$'
for item in prop.value:
m = re.match(pattern, item)
if not m:
val.Fail(prop.node.path, "'%s' value '%s' does not match pattern '%s'" %
(prop.name, item, pattern))
class PropPhandleTarget(PropDesc):
"""A phandle-target property schema element
A phandle target can be pointed to by another node using a phandle property.
"""
def __init__(self, required=False, conditional_props=None):
super(PropPhandleTarget, self).__init__('phandle', 'phandle-target',
required, conditional_props)
class PropPhandle(PropDesc):
"""A phandle property schema element
Phandle properties point to other nodes, and allow linking from one node to
another.
Properties:
target_path_match: String to use to validate the target of this phandle.
It is the full path to the node that it must point to. See
CheckPhandleTarget for details.
"""
def __init__(self, name, target_path_match, required=False,
conditional_props=None):
super(PropPhandle, self).__init__(name, 'phandle', required,
conditional_props)
self.target_path_match = target_path_match
def Validate(self, val, prop):
"""Check that this phandle points to the correct place"""
phandle = prop.GetPhandle()
target = prop.fdt.LookupPhandle(phandle)
if not CheckPhandleTarget(val, target, self.target_path_match):
val.Fail(prop.node.path, "Phandle '%s' targets node '%s' which does not "
"match pattern '%s'" % (prop.name, target.path,
self.target_path_match))
class PropCustom(PropDesc):
"""A custom property with its own validator
Properties:
validator: Function to call to validate this property
"""
def __init__(self, name, validator, required=False, conditional_props=None):
super(PropCustom, self).__init__(name, 'custom', required,
conditional_props)
self.validator = validator
def Validate(self, val, prop):
"""Validator for this property
This should be a static method in CrosConfigValidator.
Args:
val: CrosConfigValidator object
prop: Prop object of the property
"""
self.validator(val, prop)
class PropAny(PropDesc):
"""A placeholder for any property name
Properties:
validator: Function to call to validate this property
"""
def __init__(self, validator=None):
super(PropAny, self).__init__('ANY', 'any')
self.validator = validator
def Validate(self, val, prop):
"""Validator for this property
This should be a static method in CrosConfigValidator.
Args:
val: CrosConfigValidator object
prop: Prop object of the property
"""
if self.validator:
self.validator(val, prop)
class NodeDesc(SchemaElement):
"""A generic node schema element (base class for nodes)"""
def __init__(self, name, required=False, elements=None,
conditional_props=None):
super(NodeDesc, self).__init__(name, 'node', required,
conditional_props)
self.elements = elements
for element in elements:
element.parent = self
def GetNodes(self):
"""Get a list of schema elements which are nodes
Returns:
List of objects, each of which has NodeDesc as a base class
"""
return [n for n in self.elements if isinstance(n, NodeDesc)]
class NodeModel(NodeDesc):
"""A generic node schema element (base class for nodes)"""
def __init__(self, elements):
super(NodeModel, self).__init__('MODEL', elements=elements)
class NodeSubmodel(NodeDesc):
"""A generic node schema element (base class for nodes)"""
def __init__(self, elements):
super(NodeSubmodel, self).__init__('SUBMODEL', elements=elements)
class NodeAny(NodeDesc):
"""A generic node schema element (base class for nodes)"""
def __init__(self, name_pattern, elements):
super(NodeAny, self).__init__('ANY', elements=elements)
self.name_pattern = name_pattern
def Validate(self, val, node):
"""Check the name with a regex"""
if not self.name_pattern:
return
pattern = '^' + self.name_pattern + '$'
m = re.match(pattern, node.name)
if not m:
val.Fail(node.path, "Node name '%s' does not match pattern '%s'" %
(node.name, pattern))