1#!/usr/bin/python3
2# Move symbols from other shared objects into libc.so.
3# Copyright (C) 2020-2022 Free Software Foundation, Inc.
4# This file is part of the GNU C Library.
5#
6# The GNU C Library is free software; you can redistribute it and/or
7# modify it under the terms of the GNU Lesser General Public
8# License as published by the Free Software Foundation; either
9# version 2.1 of the License, or (at your option) any later version.
10#
11# The GNU C Library is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14# Lesser General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public
17# License along with the GNU C Library; if not, see
18# <https://www.gnu.org/licenses/>.
19
20"""Move symbols from other shared objects into libc.so.
21
22This script moves ABI symbols from non-libc abilists in to
23libc.abilist.  Symbol versions are preserved.  The script must be
24called from the top of the glibc source tree.
25
26"""
27
28import argparse
29import os.path
30import sys
31
32# Make available glibc Python modules.
33sys.path.append(os.path.dirname(os.path.realpath(__file__)))
34
35import glibcsymbols
36
37def add_to_libc_path(path, new_symbols):
38    """Add SYMBOLS to the abilist file PATH.
39
40    NEW_SYMBOLS is a dictionary from glibcsymbols.VersionedSymbol
41    objects to their flags.
42
43    """
44    original_symbols = glibcsymbols.read_abilist(path)
45    updated_symbols = original_symbols.copy()
46    updated_symbols.update(new_symbols)
47    if updated_symbols != original_symbols:
48        sys.stdout.write('updating libc abilist {}\n'.format(path))
49        glibcsymbols.replace_file(
50            path, glibcsymbols.abilist_lines(updated_symbols))
51
52# The name of the libc.so abilist file.
53libc_abilist = 'libc.abilist'
54
55def add_to_libc_fallback(directory, subdirs, symbol_lines):
56    """Add SYMBOL_LINES to the libc.abilist files in SUBDIRS in DIRECTORY.
57
58    All subdirectories must exist.  If they do, return True.  If not,
59    skip processing and return False.
60
61    """
62    abilists = [os.path.join(directory, subdir, libc_abilist)
63                for subdir in subdirs]
64    for abilist in abilists:
65        if not os.path.exists(abilist):
66            return False
67    for abilist in abilists:
68            add_to_libc_path(abilist, symbol_lines)
69    return True
70
71def add_to_libc(directory, symbol_lines):
72
73    """Add SYMBOL_LINES (a list of strings) to libc.abilist in DIRECTORY.
74
75    Try specific subdirectories as well if libc.abilist is not found
76    in DIRECTORY.
77
78    """
79    libc_path = os.path.join(directory, libc_abilist)
80    if os.path.exists(libc_path):
81        add_to_libc_path(libc_path, symbol_lines)
82        return
83
84    # Special case for powerpc32 and mips32 variants.
85    if add_to_libc_fallback(directory, ('fpu', 'nofpu'), symbol_lines):
86        return
87
88    # Special case for mips64.
89    if add_to_libc_fallback(directory, ('n32', 'n64'), symbol_lines):
90        return
91
92    raise IOError('No libc.abilist found for: {}'.format(directory))
93
94def move_symbols_1(path, to_move, moved_symbols):
95    """Move SYMBOLS from the abilist file PATH to MOVED_SYMBOLS.
96
97    TO_MOVE must be a set of strings.  MOVED_SYMBOLS is a dictionary.
98
99    """
100    suffix = '.abilist'
101    assert path.endswith('.abilist')
102    library = os.path.basename(path)[:-len(suffix)]
103    placeholder = '__{}_version_placeholder'.format(library)
104
105    new_lines = []
106    changed = False
107
108    old_symbols = glibcsymbols.read_abilist(path)
109    old_versions = set(versym.version for versym in old_symbols.keys())
110    matching_symbols = dict(e for e in old_symbols.items()
111                            if e[0].symbol in to_move)
112    if matching_symbols:
113        sys.stdout.write('updating {} abilist {}\n'.format(library, path))
114        new_symbols = dict(e for e in old_symbols.items()
115                           if e[0].symbol not in to_move)
116
117        # Add placeholder symbols to prevent symbol versions from
118        # going away completely.
119        new_versions = set(versym.version for versym in new_symbols.keys())
120        for missing_version in old_versions - new_versions:
121            new_symbols[glibcsymbols.VersionedSymbol(
122                placeholder, missing_version)] = 'F'
123
124        glibcsymbols.replace_file(
125            path, glibcsymbols.abilist_lines(new_symbols))
126
127        moved_symbols.update(matching_symbols)
128
129def move_symbols(directory, files, symbols):
130    """Move SYMBOLS from FILES (a list of abilist file names) in DIRECTORY.
131
132    SYMBOLS must be a set of strings.
133
134    """
135    moved_symbols = {}
136    for filename in files:
137        move_symbols_1(os.path.join(directory, filename), symbols,
138                       moved_symbols)
139    if moved_symbols:
140        add_to_libc(directory, moved_symbols)
141
142def get_parser():
143    """Return an argument parser for this module."""
144    parser = argparse.ArgumentParser(description=__doc__)
145    parser.add_argument('--only-linux', action='store_true',
146                        help='Restrict the operation to Linux abilists')
147    parser.add_argument('symbols', help='name of the symbol to move',
148                        nargs='+')
149    return parser
150
151def main(argv):
152    """The main entry point."""
153    parser = get_parser()
154    opts = parser.parse_args(argv)
155    if opts.only_linux:
156        sysdeps = 'sysdeps/unix/sysv/linux'
157    else:
158        sysdeps = 'sysdeps'
159
160    symbols = frozenset(opts.symbols)
161
162    for directory, dirs, files in os.walk(sysdeps):
163        move_symbols(directory, [name for name in files
164                                 if name != 'libc.abilist'
165                                 and name.endswith('.abilist')], symbols)
166
167if __name__ == '__main__':
168    main(sys.argv[1:])
169