1#!/usr/bin/env python
2
3# Copyright 2015 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# Utility functions used to parse a list of DLL entry points.
18# Expected format:
19#
20#   <empty-line>   -> ignored
21#   #<comment>     -> ignored
22#   %<verbatim>    -> verbatim output for header files.
23#   !<prefix>      -> prefix name for header files.
24#   <return-type> <function-name> <signature> ; -> entry point declaration.
25#
26# Anything else is an error.
27
28import argparse
29import re
30import sys
31
32re_func = re.compile(r"""^(.*[\* ])([A-Za-z_][A-Za-z0-9_]*)\((.*)\);$""")
33re_param = re.compile(r"""^(.*[\* ])([A-Za-z_][A-Za-z0-9_]*)$""")
34
35
36class Entry:
37    """Small class used to model a single DLL entry point."""
38
39    def __init__(self, func_name, return_type, parameters):
40        """Initialize Entry instance. |func_name| is the function name,
41           |return_type| its return type, and |parameters| is a list of
42           (type,name) tuples from the entry's signature.
43        """
44        self.func_name = func_name
45        self.return_type = return_type
46        self.parameters = ""
47        self.vartypes = []
48        self.varnames = []
49        self.call = ""
50        comma = ""
51        for param in parameters:
52            self.vartypes.append(param[0])
53            self.varnames.append(param[1])
54            self.parameters += "%s%s %s" % (comma, param[0], param[1])
55            self.call += "%s%s" % (comma, param[1])
56            comma = ", "
57
58
59def banner_command(argv):
60    """Return sanitized command-line description.
61       |argv| must be a list of command-line parameters, e.g. sys.argv.
62       Return a string corresponding to the command, with platform-specific
63       paths removed."""
64
65    # Remove path from first parameter
66    argv = argv[:]
67    argv[0] = "host/commands/gen-entries.py"
68    return " ".join(argv)
69
70
71def parse_entries_file(lines):
72    """Parse an .entries file and return a tuple of:
73        entries: list of Entry instances from the file.
74        prefix_name: prefix name from the file, or None.
75        verbatim: list of verbatim lines from the file.
76        errors: list of errors in the file, prefixed by line number.
77    """
78    entries = []
79    verbatim = []
80    errors = []
81    lineno = 0
82    prefix_name = None
83    for line in lines:
84        lineno += 1
85        line = line.strip()
86        if len(line) == 0:  # Ignore empty lines
87            continue
88        if line[0] == "#":  # Ignore comments
89            continue
90        if line[0] == "!":  # Prefix name
91            prefix_name = line[1:]
92            continue
93        if line[0] == "%":  # Verbatim line copy
94            verbatim.append(line[1:])
95            continue
96        # Must be a function signature.
97        m = re_func.match(line)
98        if not m:
99            errors.append("%d: '%s'" % (lineno, line))
100            continue
101
102        return_type, func_name, parameters = m.groups()
103        return_type = return_type.strip()
104        parameters = parameters.strip()
105        params = []
106        failure = False
107        if parameters != "void":
108            for parameter in parameters.split(','):
109                parameter = parameter.strip()
110                m = re_param.match(parameter)
111                if not m:
112                    errors.append("%d: parameter '%s'" % (lineno, parameter))
113                    failure = True
114                    break
115                else:
116                    param_type, param_name = m.groups()
117                    params.append((param_type.strip(), param_name.strip()))
118
119        if not failure:
120            entries.append(Entry(func_name, return_type, params))
121
122    return (entries, prefix_name, verbatim, errors)
123
124
125def gen_functions_header(entries, prefix_name, verbatim, filename, with_args):
126    """Generate a C header containing a macro listing all entry points.
127       |entries| is a list of Entry instances.
128       |prefix_name| is a prefix-name, it will be converted to upper-case.
129       |verbatim| is a list of verbatim lines that must appear before the
130       macro declaration. Useful to insert #include <> statements.
131       |filename| is the name of the original file.
132    """
133    prefix_name = prefix_name.upper()
134
135    print("// Auto-generated with: " + banner_command(sys.argv))
136    print("// DO NOT EDIT THIS FILE")
137    print("")
138    print(f"#ifndef {prefix_name}_FUNCTIONS_H")
139    print(f"#define {prefix_name}_FUNCTIONS_H")
140    print("")
141    for line in verbatim:
142        print(line)
143
144    print(f"#define LIST_{prefix_name}_FUNCTIONS(X) \\")
145    for entry in entries:
146        if with_args:
147            print(f"  X({entry.return_type}, {entry.func_name}, "
148                  f"({entry.parameters}), ({entry.call})) \\")
149        else:
150            print(f"  X({entry.return_type}, {entry.func_name}, "
151                  f"({entry.parameters})) \\")
152
153    print("")
154    print("")
155    print(f"#endif  // {prefix_name}_FUNCTIONS_H")
156
157
158def gen_dll_wrapper(entries, prefix_name, verbatim, filename):
159    """Generate a C source file that contains functions that act as wrappers
160       for entry points located in another shared library. This allows the
161       code that calls these functions to perform lazy-linking to system
162       libraries.
163       |entries|, |prefix_name|, |verbatim| and |filename| are the same as
164       for gen_functions_header() above.
165    """
166    upper_name = prefix_name.upper()
167
168    ENTRY_PREFIX = "__dll_"
169
170    print("// Auto-generated with: " + banner_command(sys.argv))
171    print("// DO NOT EDIT THIS FILE")
172    print("")
173    print("#include <dlfcn.h>")
174    for line in verbatim:
175        print(line)
176
177    print("")
178    print("///")
179    print("///  W R A P P E R   P O I N T E R S")
180    print("///")
181    print("")
182    for entry in entries:
183        ptr_name = ENTRY_PREFIX + entry.func_name
184        print(f"static {entry.return_type} "
185              f"(*{ptr_name})({entry.parameters}) = 0;")
186
187    print("")
188    print("///")
189    print("///  W R A P P E R   F U N C T I O N S")
190    print("///")
191    print("")
192
193    for entry in entries:
194        print(f"{entry.return_type} {entry.func_name}({entry.parameters}) {{")
195        ptr_name = ENTRY_PREFIX + entry.func_name
196        if entry.return_type != "void":
197            print(f"  return {ptr_name}({entry.call});")
198        else:
199            print("  {ptr_name}({entry.call});")
200        print("}\n")
201
202    print("")
203    print("///")
204    print("///  I N I T I A L I Z A T I O N   F U N C T I O N")
205    print("///")
206    print("")
207
208    print(f"int {prefix_name}_dynlink_init(void* lib) {{")
209    for entry in entries:
210        ptr_name = ENTRY_PREFIX + entry.func_name
211        print(f"  {ptr_name} = ({entry.return_type}(*)({entry.parameters})) "
212              f"dlsym(lib, \"{entry.func_name}\");")
213        print(f"  if (!{ptr_name}) return -1;")
214    print("  return 0;")
215    print("}")
216
217
218def gen_windows_def_file(entries):
219    """Generate a windows DLL .def file. |entries| is a list of Entry instances.
220    """
221    print("EXPORTS")
222    for entry in entries:
223        print("    " + entry.func_name)
224
225
226def gen_unix_sym_file(entries):
227    """Generate an ELF linker version file. |entries| is a list of Entry
228       instances.
229    """
230    print("VERSION {")
231    print("\tglobal:")
232    for entry in entries:
233        print(f"\t\t{entry.func_name};")
234    print("\tlocal:")
235    print("\t\t*;")
236    print("};")
237
238
239def gen_symbols(entries, underscore):
240    """Generate a list of symbols from |entries|, a list of Entry instances.
241       |underscore| is a boolean. If True, then prepend an underscore to each
242       symbol name.
243    """
244    prefix = ""
245    if underscore:
246        prefix = "_"
247    for entry in entries:
248        print(prefix + entry.func_name)
249
250
251def parse_file(filename, lines, mode):
252    """Generate one of possible outputs from |filename|. |lines| must be a list
253       of text lines from the file, and |mode| is one of the --mode option
254       values.
255    """
256    entries, prefix_name, verbatim, errors = parse_entries_file(lines)
257    if errors:
258        for error in errors:
259            print(f"ERROR: {filename}:{error}", file=sys.stderr)
260        sys.exit(1)
261
262    if not prefix_name:
263        prefix_name = "unknown"
264
265    if mode == 'def':
266        gen_windows_def_file(entries)
267    elif mode == 'sym':
268        gen_unix_sym_file(entries)
269    elif mode == 'wrapper':
270        gen_dll_wrapper(entries, prefix_name, verbatim, filename)
271    elif mode == 'symbols':
272        gen_symbols(entries, False)
273    elif mode == '_symbols':
274        gen_symbols(entries, True)
275    elif mode == 'functions':
276        gen_functions_header(entries, prefix_name, verbatim, filename, False)
277    elif mode == 'funcargs':
278        gen_functions_header(entries, prefix_name, verbatim, filename, True)
279
280
281# List of valid --mode option values.
282mode_list = [
283    'def', 'sym', 'wrapper', 'symbols', '_symbols', 'functions', 'funcargs'
284]
285
286# Argument parsing.
287parser = argparse.ArgumentParser(
288    formatter_class=argparse.RawDescriptionHelpFormatter,
289    description="""\
290A script used to parse an .entries input file containing a list of function
291declarations, and generate various output files depending on the value of
292the --mode option, which can be:
293
294  def        Generate a windows DLL .def file.
295  sym        Generate a Unix .so linker script.
296  wrapper    Generate a C source file containing wrapper functions.
297  symbols    Generate a simple list of symbols, one per line.
298  _symbols   Generate a simple list of symbols, prefixed with _.
299  functions  Generate a C header containing a macro listing all functions.
300  funcargs   Like 'functions', but adds function call arguments to listing.
301
302""")
303parser.add_argument("--mode", help="Output mode", choices=mode_list)
304parser.add_argument("--output", help="output file")
305parser.add_argument("file", help=".entries file path")
306
307args = parser.parse_args()
308
309if not args.mode:
310    print("ERROR: Please use --mode=<name>, see --help.", file=sys.stderr)
311    sys.exit(1)
312
313if args.output:
314    sys.stdout = open(args.output, "w+")
315
316if args.file == '--':
317    parse_file("<stdin>", sys.stdin, args.mode)
318else:
319    parse_file(args.file, open(args.file), args.mode)
320