]> git.ipfire.org Git - thirdparty/glibc.git/blob - scripts/move-symbol-to-libc.py
Update copyright dates with scripts/update-copyrights
[thirdparty/glibc.git] / scripts / move-symbol-to-libc.py
1 #!/usr/bin/python3
2 # Move symbols from other shared objects into libc.so.
3 # Copyright (C) 2020-2021 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
22 This script moves ABI symbols from non-libc abilists in to
23 libc.abilist. Symbol versions are preserved. The script must be
24 called from the top of the glibc source tree.
25
26 """
27
28 import argparse
29 import os.path
30 import sys
31
32 def replace_file(path, new_contents):
33 """Atomically replace PATH with lines from NEW_CONTENTS.
34
35 NEW_CONTENTS must be a sequence of strings.
36
37 """
38 temppath = path + 'T'
39 with open(temppath, 'w') as out:
40 for line in new_contents:
41 out.write(line)
42 os.rename(temppath, path)
43
44 class VersionedSymbol:
45 """A combination of a symbol and its version."""
46
47 def __init__(self, symbol, version):
48 """Construct a new versioned symbol."""
49 assert symbol
50 assert version
51 self.symbol = symbol
52 self.version = version
53
54 def __str__(self):
55 return self.symbol + '@' + self.version
56
57 def __eq__(self, other):
58 return self.symbol == other.symbol and self.version == other.version
59
60 def __hash__(self):
61 return hash(self.symbol) ^ hash(self.version)
62
63 def read_abilist(path):
64 """Read the abilist file at PATH.
65
66 Return a dictionary from VersionedSymbols to their flags (as
67 strings).
68
69 """
70 result = {}
71 with open(path) as inp:
72 for line in inp:
73 version, symbol, flags = line.strip().split(' ', 2)
74 result[VersionedSymbol(symbol, version)] = flags
75 return result
76
77 def abilist_lines(symbols):
78 """Build the abilist file contents (as a list of lines).
79
80 SYMBOLS is a dictionary from VersionedSymbols to their flags.
81
82 """
83 result = []
84 for versym, flags in symbols.items():
85 result.append('{} {} {}\n'.format(
86 versym.version, versym.symbol, flags))
87 result.sort()
88 return result
89
90 def add_to_libc_path(path, new_symbols):
91 """Add SYMBOLS to the abilist file PATH.
92
93 NEW_SYMBOLS is a dictionary from VersionedSymbols to their flags.
94
95 """
96 original_symbols = read_abilist(path)
97 updated_symbols = original_symbols.copy()
98 updated_symbols.update(new_symbols)
99 if updated_symbols != original_symbols:
100 sys.stdout.write('updating libc abilist {}\n'.format(path))
101 replace_file(path, abilist_lines(updated_symbols))
102
103 # The name of the libc.so abilist file.
104 libc_abilist = 'libc.abilist'
105
106 def add_to_libc_fallback(directory, subdirs, symbol_lines):
107 """Add SYMBOL_LINES to the libc.abilist files in SUBDIRS in DIRECTORY.
108
109 All subdirectories must exist. If they do, return True. If not,
110 skip processing and return False.
111
112 """
113 abilists = [os.path.join(directory, subdir, libc_abilist)
114 for subdir in subdirs]
115 for abilist in abilists:
116 if not os.path.exists(abilist):
117 return False
118 for abilist in abilists:
119 add_to_libc_path(abilist, symbol_lines)
120 return True
121
122 def add_to_libc(directory, symbol_lines):
123
124 """Add SYMBOL_LINES (a list of strings) to libc.abilist in DIRECTORY.
125
126 Try specific subdirectories as well if libc.abilist is not found
127 in DIRECTORY.
128
129 """
130 libc_path = os.path.join(directory, libc_abilist)
131 if os.path.exists(libc_path):
132 add_to_libc_path(libc_path, symbol_lines)
133 return
134
135 # Special case for powerpc32 and mips32 variants.
136 if add_to_libc_fallback(directory, ('fpu', 'nofpu'), symbol_lines):
137 return
138
139 # Special case for mips64.
140 if add_to_libc_fallback(directory, ('n32', 'n64'), symbol_lines):
141 return
142
143 raise IOError('No libc.abilist found for: {}'.format(directory))
144
145 def move_symbols_1(path, to_move, moved_symbols):
146 """Move SYMBOLS from the abilist file PATH to MOVED_SYMBOLS.
147
148 TO_MOVE must be a set of strings. MOVED_SYMBOLS is a dictionary.
149
150 """
151 suffix = '.abilist'
152 assert path.endswith('.abilist')
153 library = os.path.basename(path)[:-len(suffix)]
154 placeholder = '__{}_version_placeholder'.format(library)
155
156 new_lines = []
157 changed = False
158
159 old_symbols = read_abilist(path)
160 old_versions = set(versym.version for versym in old_symbols.keys())
161 matching_symbols = dict(e for e in old_symbols.items()
162 if e[0].symbol in to_move)
163 if matching_symbols:
164 sys.stdout.write('updating {} abilist {}\n'.format(library, path))
165 new_symbols = dict(e for e in old_symbols.items()
166 if e[0].symbol not in to_move)
167
168 # Add placeholder symbols to prevent symbol versions from
169 # going away completely.
170 new_versions = set(versym.version for versym in new_symbols.keys())
171 for missing_version in old_versions - new_versions:
172 new_symbols[VersionedSymbol(placeholder, missing_version)] = 'F'
173
174 replace_file(path, abilist_lines(new_symbols))
175
176 moved_symbols.update(matching_symbols)
177
178 def move_symbols(directory, files, symbols):
179 """Move SYMBOLS from FILES (a list of abilist file names) in DIRECTORY.
180
181 SYMBOLS must be a set of strings.
182
183 """
184 moved_symbols = {}
185 for filename in files:
186 move_symbols_1(os.path.join(directory, filename), symbols,
187 moved_symbols)
188 if moved_symbols:
189 add_to_libc(directory, moved_symbols)
190
191 def get_parser():
192 """Return an argument parser for this module."""
193 parser = argparse.ArgumentParser(description=__doc__)
194 parser.add_argument('--only-linux', action='store_true',
195 help='Restrict the operation to Linux abilists')
196 parser.add_argument('symbols', help='name of the symbol to move',
197 nargs='+')
198 return parser
199
200 def main(argv):
201 """The main entry point."""
202 parser = get_parser()
203 opts = parser.parse_args(argv)
204 if opts.only_linux:
205 sysdeps = 'sysdeps/unix/sysv/linux'
206 else:
207 sysdeps = 'sysdeps'
208
209 symbols = frozenset(opts.symbols)
210
211 for directory, dirs, files in os.walk(sysdeps):
212 move_symbols(directory, [name for name in files
213 if name != 'libc.abilist'
214 and name.endswith('.abilist')], symbols)
215
216 if __name__ == '__main__':
217 main(sys.argv[1:])