xref: /freebsd/sys/tools/syscalls/config.lua (revision 7fdf597e96a02165cfe22ff357b857d5fa15ed8a)
1--
2-- SPDX-License-Identifier: BSD-2-Clause
3--
4-- Copyright (c) 2021-2024 SRI International
5-- Copyright (c) 2024 Tyler Baxter <agge@FreeBSD.org>
6-- Copyright (c) 2023 Warner Losh <imp@bsdimp.com>
7-- Copyright (c) 2019 Kyle Evans <kevans@FreeBSD.org>
8--
9
10--
11-- Code to read in the config file that drives this. Since we inherit from the
12-- FreeBSD makesyscall.sh legacy, all config is done through a config file that
13-- sets a number of variables (as noted below); it used to be a .sh file that
14-- was sourced in. This dodges the need to write a command line parser.
15--
16
17local util = require("tools.util")
18
19--
20-- Global config map.
21-- Default configuration is native. Any of these may get replaced by an
22-- optionally specified configuration file.
23--
24local config = {
25	sysnames = "syscalls.c",
26	syshdr = "../sys/syscall.h",
27	sysmk = "/dev/null",
28	syssw = "init_sysent.c",
29	systrace = "systrace_args.c",
30	sysproto = "../sys/sysproto.h",
31	libsysmap = "/dev/null",
32	libsys_h = "/dev/null",
33	sysproto_h = "_SYS_SYSPROTO_H_",
34	syscallprefix = "SYS_",
35	switchname = "sysent",
36	namesname = "syscallnames",
37	abi_flags = {},
38	abi_func_prefix = "",
39	abi_type_suffix = "",
40	abi_long = "long",
41	abi_u_long = "u_long",
42	abi_semid_t = "semid_t",
43	abi_size_t = "size_t",
44	abi_ptr_array_t = "",
45	abi_headers = "",
46	abi_intptr_t = "intptr_t",
47	ptr_intptr_t_cast = "intptr_t",
48	obsol = {},
49	unimpl = {},
50	compat_set = "native",
51	mincompat = 0,
52	-- System calls that require ABI-specific handling.
53	syscall_abi_change = {},
54	-- System calls that appear to require handling, but don't.
55	syscall_no_abi_change = {},
56	-- Keep track of modifications if there are.
57	modifications = {},
58	-- Stores compat_sets from syscalls.conf; config.mergeCompat()
59	-- instantiates.
60	compat_options = {},
61}
62
63--
64-- For each entry, the ABI flag is the key. One may also optionally provide an
65-- expr, which are contained in an array associated with each key; expr gets
66-- applied to each argument type to indicate whether this argument is subject to
67-- ABI change given the configured flags.
68--
69config.known_abi_flags = {
70	long_size = {
71		"_Contains[a-z_]*_long_",
72		"^long [a-z0-9_]+$",
73		"long [*]",
74		"size_t [*]",
75		-- semid_t is not included because it is only used
76		-- as an argument or written out individually and
77		-- said writes are handled by the ksem framework.
78		-- Technically a sign-extension issue exists for
79		-- arguments, but because semid_t is actually a file
80		-- descriptor negative 32-bit values are invalid
81		-- regardless of sign-extension.
82	},
83	time_t_size = {
84		"_Contains[a-z_]*_timet_",
85	},
86	pointer_args = {
87		-- no expr
88	},
89	pointer_size = {
90		"_Contains[a-z_]*_ptr_",
91		"[*][*]",
92	},
93	pair_64bit = {
94		"^dev_t[ ]*$",
95		"^id_t[ ]*$",
96		"^off_t[ ]*$",
97	},
98}
99
100-- All compat option entries should have five entries:
101--	definition: The preprocessor macro that will be set for this.
102--	compatlevel: The level this compatibility should be included at. This
103--	    generally represents the version of FreeBSD that it is compatible
104--	    with, but ultimately it's just the level of mincompat in which it's
105--	    included.
106--	flag: The name of the flag in syscalls.master.
107--	prefix: The prefix to use for _args and syscall prototype.  This will be
108--	    used as-is, without "_" or any other character appended.
109--	descr: The description of this compat option in init_sysent.c comments.
110-- The special "stdcompat" entry will cause the other five to be autogenerated.
111local compat_option_sets = {
112	native = {
113		{
114			definition = "COMPAT_43",
115			compatlevel = 3,
116			flag = "COMPAT",
117			prefix = "o",
118			descr = "old",
119		},
120		{ stdcompat = "FREEBSD4" },
121		{ stdcompat = "FREEBSD6" },
122		{ stdcompat = "FREEBSD7" },
123		{ stdcompat = "FREEBSD10" },
124		{ stdcompat = "FREEBSD11" },
125		{ stdcompat = "FREEBSD12" },
126		{ stdcompat = "FREEBSD13" },
127		{ stdcompat = "FREEBSD14" },
128	},
129}
130
131--
132-- config looks like a shell script; in fact, the previous makesyscalls.sh
133-- script actually sourced it in.  It had a pretty common format, so we should
134-- be fine to make various assumptions.
135--
136-- This function processes config to be merged into our global config map with
137-- config.merge(). It aborts if there's malformed lines and returns NIL and a
138-- message if no file was provided.
139--
140function config.process(file)
141	local cfg = {}
142	local comment_line_expr = "^%s*#.*"
143	-- We capture any whitespace padding here so we can easily advance to
144	-- the end of the line as needed to check for any trailing bogus bits.
145	-- Alternatively, we could drop the whitespace and instead try to
146	-- use a pattern to strip out the meaty part of the line, but then we
147	-- would need to sanitize the line for potentially special characters.
148	local line_expr = "^([%w%p]+%s*)=(%s*[`\"]?[^\"`]*[`\"]?)"
149
150	if not file then
151		return nil, "No file given"
152	end
153
154	local fh = assert(io.open(file))
155
156	for nextline in fh:lines() do
157		-- Strip any whole-line comments.
158		nextline = nextline:gsub(comment_line_expr, "")
159		-- Parse it into key, value pairs.
160		local key, value = nextline:match(line_expr)
161		if key ~= nil and value ~= nil then
162			local kvp = key .. "=" .. value
163			key = util.trim(key)
164			value = util.trim(value)
165			local delim = value:sub(1,1)
166			if delim == '"' then
167				local trailing_context
168
169				-- Strip off the key/value part.
170				trailing_context = nextline:sub(kvp:len() + 1)
171				-- Strip off any trailing comment.
172				trailing_context = trailing_context:gsub("#.*$",
173				    "")
174				-- Strip off leading/trailing whitespace.
175				trailing_context = util.trim(trailing_context)
176				if trailing_context ~= "" then
177					print(trailing_context)
178					util.abort(1,
179					    "Malformed line: " .. nextline)
180				end
181
182				value = util.trim(value, delim)
183			else
184				-- Strip off potential comments.
185				value = value:gsub("#.*$", "")
186				-- Strip off any padding whitespace.
187				value = util.trim(value)
188				if value:match("%s") then
189					util.abort(1,
190					    "Malformed config line: " ..
191					    nextline)
192				end
193			end
194			cfg[key] = value
195		elseif not nextline:match("^%s*$") then
196			-- Make sure format violations don't get overlooked
197			-- here, but ignore blank lines.  Comments are already
198			-- stripped above.
199			util.abort(1, "Malformed config line: " .. nextline)
200		end
201	end
202
203	assert(fh:close())
204	return cfg
205end
206
207-- Merges processed configuration file into the global config map (see above),
208-- or returns NIL and a message if no file was provided.
209function config.merge(fh)
210	if not fh then
211		return nil, "No file given"
212	end
213
214	local res = assert(config.process(fh))
215
216	for k, v in pairs(res) do
217		if v ~= config[k] then
218			-- Handling of string lists:
219			if k:find("abi_flags") then
220				-- Match for pipe, that's how abi_flags
221				-- is formatted.
222				config[k] = util.setFromString(v, "[^|]+")
223			elseif k:find("syscall_abi_change") or
224			    k:find("syscall_no_abi_change") or
225			    k:find("obsol") or
226			    k:find("unimpl") then
227				-- Match for space, that's how these
228				-- are formatted.
229				config[k] = util.setFromString(v, "[^ ]+")
230			else
231				config[k] = v
232			end
233			-- Construct config modified table as config
234			-- is processed.
235			config.modifications[k] = true
236		else
237			-- config wasn't modified.
238			config.modifications[k] = false
239		end
240	end
241end
242
243-- Returns TRUE if there are ABI changes from native for the provided ABI flag.
244function config.abiChanges(name)
245	if config.known_abi_flags[name] == nil then
246		util.abort(1, "abi_changes: unknown flag: " .. name)
247	end
248	return config.abi_flags[name] ~= nil
249end
250
251-- Instantiates config.compat_options.
252function config.mergeCompat()
253	if config.compat_set ~= "" then
254		if not compat_option_sets[config.compat_set] then
255			util.abort(1, "Undefined compat set: " ..
256			    config.compat_set)
257		end
258
259		config.compat_options = compat_option_sets[config.compat_set]
260	end
261end
262
263return config
264