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