tools/compile_seccomp_policy: Support arch metadata attributes
am: 571e958364

Change-Id: I43ba6868a6189eee3169dab58a84ad8e199125cb
diff --git a/tools/parser.py b/tools/parser.py
index 99b2e99..6f6ea06 100644
--- a/tools/parser.py
+++ b/tools/parser.py
@@ -403,6 +403,57 @@
             filters.append(self._parse_single_filter(tokens))
         return filters
 
+    # key-value-pair = identifier , '=', identifier , [ { ',' , identifier } ]
+    #                ;
+    def _parse_key_value_pair(self, tokens):
+        if not tokens:
+            self._parser_state.error('missing key')
+        key = tokens.pop(0)
+        if key.type != 'IDENTIFIER':
+            self._parser_state.error('invalid key', token=key)
+        if not tokens:
+            self._parser_state.error('missing equal')
+        if tokens[0].type != 'EQUAL':
+            self._parser_state.error('invalid equal', token=tokens[0])
+        tokens.pop(0)
+        value_list = []
+        while tokens:
+            value = tokens.pop(0)
+            if value.type != 'IDENTIFIER':
+                self._parser_state.error('invalid value', token=value)
+            value_list.append(value.value)
+            if not tokens or tokens[0].type != 'COMMA':
+                break
+            tokens.pop(0)
+        else:
+            self._parser_state.error('empty value')
+        return (key.value, value_list)
+
+    # metadata = '[' , key-value-pair , [ { ';' , key-value-pair } ] , ']'
+    #          ;
+    def _parse_metadata(self, tokens):
+        if not tokens:
+            self._parser_state.error('missing opening bracket')
+        opening_bracket = tokens.pop(0)
+        if opening_bracket.type != 'LBRACKET':
+            self._parser_state.error(
+                'invalid opening bracket', token=opening_bracket)
+        metadata = {}
+        while tokens:
+            first_token = tokens[0]
+            key, value = self._parse_key_value_pair(tokens)
+            if key in metadata:
+                self._parser_state.error(
+                    'duplicate metadata key: "%s"' % key, token=first_token)
+            metadata[key] = value
+            if not tokens or tokens[0].type != 'SEMICOLON':
+                break
+            tokens.pop(0)
+        if not tokens or tokens[0].type != 'RBRACKET':
+            self._parser_state.error('unclosed bracket', token=opening_bracket)
+        tokens.pop(0)
+        return metadata
+
     # syscall-descriptor = syscall-name , [ metadata ]
     #                    | libc-function , [ metadata ]
     #                    ;
@@ -413,11 +464,14 @@
         if syscall_descriptor.type != 'IDENTIFIER':
             self._parser_state.error(
                 'invalid syscall descriptor', token=syscall_descriptor)
+        # TODO(lhchavez): Support libc function names.
+        if tokens and tokens[0].type == 'LBRACKET':
+            metadata = self._parse_metadata(tokens)
+            if 'arch' in metadata and self._arch.arch_name not in metadata['arch']:
+                return ()
         if syscall_descriptor.value not in self._arch.syscalls:
             self._parser_state.error(
                 'nonexistent syscall', token=syscall_descriptor)
-        # TODO(lhchavez): Support libc function names.
-        # TODO(lhchavez): Support metadata.
         return (Syscall(syscall_descriptor.value,
                         self._arch.syscalls[syscall_descriptor.value]), )
 
@@ -495,7 +549,12 @@
                         statements.extend(
                             self._parse_include_statement(tokens))
                     else:
-                        statements.append(self.parse_filter_statement(tokens))
+                        statement = self.parse_filter_statement(tokens)
+                        if statement is None:
+                            # If all the syscalls in the statement are for
+                            # another arch, skip the whole statement.
+                            continue
+                        statements.append(statement)
 
                     if tokens:
                         self._parser_state.error(
diff --git a/tools/parser_unittest.py b/tools/parser_unittest.py
index ba0fedd..570f8a2 100755
--- a/tools/parser_unittest.py
+++ b/tools/parser_unittest.py
@@ -399,6 +399,22 @@
                 parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()),
             ]))
 
+    def test_parse_metadata(self):
+        """Accept valid filter statements with metadata."""
+        self.assertEqual(
+            self.parser.parse_filter_statement(
+                self._tokenize('read[arch=test]: arg0 == 0')),
+            parser.ParsedFilterStatement((parser.Syscall('read', 0), ), [
+                parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()),
+            ]))
+        self.assertEqual(
+            self.parser.parse_filter_statement(
+                self._tokenize(
+                    '{read, nonexistent[arch=nonexistent]}: arg0 == 0')),
+            parser.ParsedFilterStatement((parser.Syscall('read', 0), ), [
+                parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()),
+            ]))
+
     def test_parse_unclosed_brace(self):
         """Reject unclosed brace."""
         with self.assertRaisesRegex(parser.ParseException, 'unclosed brace'):