1#!/usr/bin/env python
2#
3# Copyright (C) 2020 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import parse
19import sys
20
21from abc import ABCMeta
22from abc import abstractmethod
23from ply import lex
24from ply import yacc
25import target_file_utils
26
27
28def repeat_rule(to_repeat, zero_ok=False):
29    '''
30    From a given rule, generates a rule that allows consecutive items
31    of that rule. Instances are collected in a list.
32    '''
33
34    def p_multiple(self, p):
35        if len(p) == 2 and zero_ok:
36            p[0] = []
37        elif len(p) == 2:
38            p[0] = [p[1]]
39        else:
40            p[0] = p[1] + [p[2]]
41
42    func = p_multiple
43    format_tuple = (to_repeat, to_repeat, to_repeat, 'empty'
44                    if zero_ok else to_repeat)
45    func.__doc__ = '%ss : %ss %s \n| %s' % format_tuple
46    return func
47
48
49def literal_token(tok):
50    '''
51    A compact function to specify literal string tokens when.
52    they need to take precedence over a generic string,
53    Among these tokens precedence is decided in alphabetic order.
54    '''
55
56    def t_token(self, t):
57        return t
58
59    func = t_token
60    func.__doc__ = tok
61    return func
62
63
64class KernelProcFileTestBase(object):
65    """
66    An abstract test for the formatting of a procfs file. Individual
67    files can inherit from this class.
68
69    New parsing rules can be defined in the form of p_RULENAME, and
70    similarly new tokens can be defined as t_TOKENNAME.
71
72    Child class should also specify a `start` variable to give the starting rule.
73    """
74
75    __metaclass__ = ABCMeta
76
77    def t_HEX_LITERAL(self, t):
78        r'0x[a-f0-9]+'
79        t.value = int(t.value, 0)
80        return t
81
82    def t_FLOAT(self, t):
83        r'([0-9]+[.][0-9]*|[0-9]*[.][0-9]+)'
84        t.value = float(t.value)
85        return t
86
87    def t_NUMBER(self, t):
88        r'\d+'
89        t.value = int(t.value)
90        return t
91
92    t_PATH = r'/[^\0]+'
93    t_COLON = r':'
94    t_EQUALS = r'='
95    t_COMMA = r','
96    t_PERIOD = r'\.'
97    t_STRING = r'[a-zA-Z\(\)_0-9\-@]+'
98
99    t_TAB = r'\t'
100    t_SPACE = r'[ ]'
101
102    def t_DASH(self, t):
103        r'\-'
104        return t
105
106    def t_NEWLINE(self, t):
107        r'\n'
108        t.lexer.lineno += len(t.value)
109        return t
110
111    t_ignore = ''
112
113    def t_error(self, t):
114        raise SyntaxError("Illegal character '%s' in line %d '%s'" % \
115                (t.value[0], t.lexer.lineno, t.value.split()[0]))
116
117    p_SPACEs = repeat_rule('SPACE', zero_ok=True)
118
119    def p_error(self, p):
120        raise SyntaxError("Parsing error at token %s in line %d" %
121                          (p, p.lexer.lineno))
122
123    def p_empty(self, p):
124        'empty :'
125        pass
126
127    def __init__(self):
128        self.tokens = [
129            t_name[2:] for t_name in dir(self)
130            if len(t_name) > 2 and t_name[:2] == 't_'
131        ]
132        self.tokens.remove('error')
133        self.tokens.remove('ignore')
134        self.lexer = lex.lex(module=self)
135        # (Change logger output stream if debugging)
136        self.parser = yacc.yacc(module=self, write_tables=False, \
137                                errorlog=yacc.PlyLogger(sys.stderr)) #open(os.devnull, 'w')))
138
139    def set_api_level(self, dut):
140        self.api_level = dut.getLaunchApiLevel(strict=False)
141
142    def parse_line(self, rule, line, custom={}):
143        """Parse a line of text with the parse library.
144
145        Args:
146            line: string, a line of text
147            rule: string, a format rule. See parse documentation
148            custom: dict, maps to custom type conversion functions
149
150        Returns:
151            list, information parsed from the line
152
153        Raises:
154            SyntaxError: if the line could not be parsed.
155        """
156        parsed = parse.parse(rule, line, custom)
157        if parsed is None:
158            raise SyntaxError("Failed to parse line %s according to rule %s" %
159                              (line, rule))
160        return list(parsed)
161
162    def parse_contents(self, file_contents):
163        """Using the internal parser, parse the contents.
164
165        Args:
166            file_contents: string, entire contents of a file
167
168        Returns:
169            list, a parsed representation of the file
170
171        Raises:
172            SyntaxError: if the file could not be parsed
173        """
174        return self.parser.parse(file_contents, lexer=self.lexer)
175
176    @abstractmethod
177    def get_path(self):
178        """Returns the full path of this proc file (string)."""
179        pass
180
181    def prepare_test(self, dut):
182        """Performs any actions necessary before testing the proc file.
183
184        Args:
185            shell: shell object, for preparation that requires device access
186
187        Returns:
188            boolean, True if successful.
189        """
190        return True
191
192    def file_optional(self, shell=None, dut=None):
193        """Performs any actions necessary to return if file is allowed to be absent
194
195        Args:
196            shell: shell object, to run commands on the device side
197            dut: AndroidDevice object to access functions and properties of that object
198
199        Returns:
200            boolean, True if file is allowed to be absent.
201        """
202        return False
203
204    def result_correct(self, parse_result):
205        """Returns: True if the parsed result meets the requirements (boolean)."""
206        return True
207
208    def test_format(self):
209        """Returns:
210            boolean, True if the file should be read and its format tested.
211                     False if only the existence and permission should be tested.
212        """
213        return True
214
215    def get_permission_checker(self):
216        """Gets the function handle to use for validating file permissions.
217
218        Return the function that will check if the permissions are correct.
219        By default, return the IsReadOnly function from target_file_utils.
220
221        Returns:
222            function which takes one argument (the unix file permission bits
223            in octal format) and returns True if the permissions are correct,
224            False otherwise.
225        """
226        return target_file_utils.IsReadOnly
227