• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright 2021 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
17import argparse
18import os
19import sys
20
21
22META_CHARS = frozenset(['.', '^', '$', '?', '*', '+', '|', '[', '(', '{'])
23ESCAPED_META_CHARS = frozenset([ '\\{}'.format(c) for c in META_CHARS ])
24
25
26def get_stem_len(path):
27    """Returns the length of the stem."""
28    stem_len = 0
29    i = 0
30    while i < len(path):
31        if path[i] == "\\":
32            i += 1
33        elif path[i] in META_CHARS:
34            break
35        stem_len += 1
36        i += 1
37    return stem_len
38
39
40def is_meta(path):
41    """Indicates if a path contains any metacharacter."""
42    meta_char_count = 0
43    escaped_meta_char_count = 0
44    for c in META_CHARS:
45        if c in path:
46            meta_char_count += 1
47    for c in ESCAPED_META_CHARS:
48        if c in path:
49            escaped_meta_char_count += 1
50    return meta_char_count > escaped_meta_char_count
51
52
53class FileContextsNode(object):
54    """An entry in a file_context file."""
55
56    def __init__(self, path, file_type, context, meta, stem_len, str_len, line):
57        self.path = path
58        self.file_type = file_type
59        self.context = context
60        self.meta = meta
61        self.stem_len = stem_len
62        self.str_len = str_len
63        self.type = context.split(":")[2]
64        self.line = line
65
66    @classmethod
67    def create(cls, line):
68        if (len(line) == 0) or (line[0] == '#'):
69            return None
70
71        split = line.split()
72        path = split[0].strip()
73        context = split[-1].strip()
74        file_type = None
75        if len(split) == 3:
76            file_type = split[1].strip()
77        meta = is_meta(path)
78        stem_len = get_stem_len(path)
79        str_len = len(path.replace("\\", ""))
80
81        return cls(path, file_type, context, meta, stem_len, str_len, line)
82
83    # Comparator function based off fc_sort.c
84    def __lt__(self, other):
85        # The regex without metachars is more specific.
86        if self.meta and not other.meta:
87            return True
88        if other.meta and not self.meta:
89            return False
90
91        # The regex with longer stem_len (regex before any meta characters) is
92        # more specific.
93        if self.stem_len < other.stem_len:
94            return True
95        if other.stem_len < self.stem_len:
96            return False
97
98        # The regex with longer string length is more specific
99        if self.str_len < other.str_len:
100            return True
101        if other.str_len < self.str_len:
102            return False
103
104        # A regex with a file_type defined (e.g. file, dir) is more specific.
105        if self.file_type is None and other.file_type is not None:
106            return True
107        if other.file_type is None and self.file_type is not None:
108            return False
109
110        return False
111
112
113def read_file_contexts(file_descriptor):
114    file_contexts = []
115    for line in file_descriptor:
116        node = FileContextsNode.create(line.strip())
117        if node is not None:
118            file_contexts.append(node)
119    return file_contexts
120
121
122def read_multiple_file_contexts(files):
123    file_contexts = []
124    for filename in files:
125        with open(filename) as fd:
126            file_contexts.extend(read_file_contexts(fd))
127    return file_contexts
128
129
130def sort(files):
131    for f in files:
132        if not os.path.exists(f):
133            sys.exit("Error: File_contexts file " + f + " does not exist\n")
134    file_contexts = read_multiple_file_contexts(files)
135    file_contexts.sort()
136    return file_contexts
137
138
139def print_fc(fc, out):
140    if not out:
141        f = sys.stdout
142    else:
143        f = open(out, "w")
144    for node in fc:
145        f.write(node.line + "\n")
146
147
148if __name__ == '__main__':
149    parser = argparse.ArgumentParser(
150            description="SELinux file_contexts sorting tool.")
151    parser.add_argument("-i", dest="input", nargs="*",
152            help="Path to the file_contexts file(s).")
153    parser.add_argument("-o", dest="output", help="Path to the output file.")
154    args = parser.parse_args()
155    if not args.input:
156        parser.error("Must include path to policy")
157
158    print_fc(sort(args.input), args.output)
159