opnsense-src/sys/tools/syscalls/config.lua
agge3 9ded074e87 Refactor makesyscalls.lua into a library
* main.lua replicates the functionality of makesyscalls.lua
* Individual files are generated by their associated module
  * Modules can be called as standalone scripts to generate a specific
    file
* Data and procedures are performed by objects instead of procedual code
* Bitmasks are replaced by declarative types
* Temporary files are no longer produced, writing is stored in memory
* Comments provide explanation to functions and semantics

Google Summer of Code 2024 Final Work Product

Co-authored-by: Warner Losh <imp@freebsd.org>
Co-authored-by: Kyle Evans <kevans@freebsd.org>
Co-authored-by: Brooks Davis <brooks@freebsd.org>
Sponsored by:    Google (GSoC 24)
Pull Request:	https://github.com/freebsd/freebsd-src/pull/1362
Signed-off-by: agge3 <sterspark@gmail.com>
2024-10-30 21:04:30 +00:00

312 lines
9 KiB
Lua

--
-- SPDX-License-Identifier: BSD-2-Clause
--
-- Copyright (c) 2021-2024 SRI International
-- Copyright (c) 2024 Tyler Baxter <agge@FreeBSD.org>
-- Copyright (c) 2023 Warner Losh <imp@bsdimp.com>
-- Copyright (c) 2019 Kyle Evans <kevans@FreeBSD.org>
--
--
-- Code to read in the config file that drives this. Since we inherit from the
-- FreeBSD makesyscall.sh legacy, all config is done through a config file that
-- sets a number of variables (as noted below); it used to be a .sh file that
-- was sourced in. This dodges the need to write a command line parser.
--
local util = require("tools.util")
--
-- Global config map.
-- Default configuration is native. Any of these may get replaced by an
-- optionally specified configuration file.
--
local config = {
sysnames = "syscalls.c",
syshdr = "../sys/syscall.h",
sysmk = "/dev/null",
syssw = "init_sysent.c",
systrace = "systrace_args.c",
sysproto = "../sys/sysproto.h",
libsysmap = "/dev/null",
libsys_h = "/dev/null",
sysproto_h = "_SYS_SYSPROTO_H_",
syscallprefix = "SYS_",
switchname = "sysent",
namesname = "syscallnames",
abi_flags = {},
abi_func_prefix = "",
abi_type_suffix = "",
abi_long = "long",
abi_u_long = "u_long",
abi_semid_t = "semid_t",
abi_size_t = "size_t",
abi_ptr_array_t = "",
abi_headers = "",
abi_intptr_t = "intptr_t",
ptr_intptr_t_cast = "intptr_t",
obsol = {},
unimpl = {},
capabilities_conf = "capabilities.conf",
compat_set = "native",
mincompat = 0,
capenabled = {},
-- System calls that require ABI-specific handling.
syscall_abi_change = {},
-- System calls that appear to require handling, but don't.
syscall_no_abi_change = {},
-- Keep track of modifications if there are.
modifications = {},
-- Stores compat_sets from syscalls.conf; config.mergeCompat()
-- instantiates.
compat_options = {},
}
--
-- For each entry, the ABI flag is the key. One may also optionally provide an
-- expr, which are contained in an array associated with each key; expr gets
-- applied to each argument type to indicate whether this argument is subject to
-- ABI change given the configured flags.
--
config.known_abi_flags = {
long_size = {
"_Contains[a-z_]*_long_",
"^long [a-z0-9_]+$",
"long [*]",
"size_t [*]",
-- semid_t is not included because it is only used
-- as an argument or written out individually and
-- said writes are handled by the ksem framework.
-- Technically a sign-extension issue exists for
-- arguments, but because semid_t is actually a file
-- descriptor negative 32-bit values are invalid
-- regardless of sign-extension.
},
time_t_size = {
"_Contains[a-z_]*_timet_",
},
pointer_args = {
-- no expr
},
pointer_size = {
"_Contains[a-z_]*_ptr_",
"[*][*]",
},
pair_64bit = {
"^dev_t[ ]*$",
"^id_t[ ]*$",
"^off_t[ ]*$",
},
}
-- All compat option entries should have five entries:
-- definition: The preprocessor macro that will be set for this.
-- compatlevel: The level this compatibility should be included at. This
-- generally represents the version of FreeBSD that it is compatible
-- with, but ultimately it's just the level of mincompat in which it's
-- included.
-- flag: The name of the flag in syscalls.master.
-- prefix: The prefix to use for _args and syscall prototype. This will be
-- used as-is, without "_" or any other character appended.
-- descr: The description of this compat option in init_sysent.c comments.
-- The special "stdcompat" entry will cause the other five to be autogenerated.
local compat_option_sets = {
native = {
{
definition = "COMPAT_43",
compatlevel = 3,
flag = "COMPAT",
prefix = "o",
descr = "old",
},
{ stdcompat = "FREEBSD4" },
{ stdcompat = "FREEBSD6" },
{ stdcompat = "FREEBSD7" },
{ stdcompat = "FREEBSD10" },
{ stdcompat = "FREEBSD11" },
{ stdcompat = "FREEBSD12" },
{ stdcompat = "FREEBSD13" },
{ stdcompat = "FREEBSD14" },
},
}
--
-- config looks like a shell script; in fact, the previous makesyscalls.sh
-- script actually sourced it in. It had a pretty common format, so we should
-- be fine to make various assumptions.
--
-- This function processes config to be merged into our global config map with
-- config.merge(). It aborts if there's malformed lines and returns NIL and a
-- message if no file was provided.
--
function config.process(file)
local cfg = {}
local comment_line_expr = "^%s*#.*"
-- We capture any whitespace padding here so we can easily advance to
-- the end of the line as needed to check for any trailing bogus bits.
-- Alternatively, we could drop the whitespace and instead try to
-- use a pattern to strip out the meaty part of the line, but then we
-- would need to sanitize the line for potentially special characters.
local line_expr = "^([%w%p]+%s*)=(%s*[`\"]?[^\"`]*[`\"]?)"
if not file then
return nil, "No file given"
end
local fh = assert(io.open(file))
for nextline in fh:lines() do
-- Strip any whole-line comments.
nextline = nextline:gsub(comment_line_expr, "")
-- Parse it into key, value pairs.
local key, value = nextline:match(line_expr)
if key ~= nil and value ~= nil then
local kvp = key .. "=" .. value
key = util.trim(key)
value = util.trim(value)
local delim = value:sub(1,1)
if delim == '"' then
local trailing_context
-- Strip off the key/value part.
trailing_context = nextline:sub(kvp:len() + 1)
-- Strip off any trailing comment.
trailing_context = trailing_context:gsub("#.*$",
"")
-- Strip off leading/trailing whitespace.
trailing_context = util.trim(trailing_context)
if trailing_context ~= "" then
print(trailing_context)
util.abort(1,
"Malformed line: " .. nextline)
end
value = util.trim(value, delim)
else
-- Strip off potential comments.
value = value:gsub("#.*$", "")
-- Strip off any padding whitespace.
value = util.trim(value)
if value:match("%s") then
util.abort(1,
"Malformed config line: " ..
nextline)
end
end
cfg[key] = value
elseif not nextline:match("^%s*$") then
-- Make sure format violations don't get overlooked
-- here, but ignore blank lines. Comments are already
-- stripped above.
util.abort(1, "Malformed config line: " .. nextline)
end
end
assert(fh:close())
return cfg
end
-- Merges processed configuration file into the global config map (see above),
-- or returns NIL and a message if no file was provided.
function config.merge(fh)
if not fh then
return nil, "No file given"
end
local res = assert(config.process(fh))
for k, v in pairs(res) do
if v ~= config[k] then
-- Handling of string lists:
if k:find("abi_flags") then
-- Match for pipe, that's how abi_flags
-- is formatted.
config[k] = util.setFromString(v, "[^|]+")
elseif k:find("capenabled") or
k:find("syscall_abi_change") or
k:find("syscall_no_abi_change") or
k:find("obsol") or
k:find("unimpl") then
-- Match for space, that's how these
-- are formatted.
config[k] = util.setFromString(v, "[^ ]+")
else
config[k] = v
end
-- Construct config modified table as config
-- is processed.
config.modifications[k] = true
else
-- config wasn't modified.
config.modifications[k] = false
end
end
end
-- Returns TRUE if there are ABI changes from native for the provided ABI flag.
function config.abiChanges(name)
if config.known_abi_flags[name] == nil then
util.abort(1, "abi_changes: unknown flag: " .. name)
end
return config.abi_flags[name] ~= nil
end
-- Instantiates config.compat_options.
function config.mergeCompat()
if config.compat_set ~= "" then
if not compat_option_sets[config.compat_set] then
util.abort(1, "Undefined compat set: " ..
config.compat_set)
end
config.compat_options = compat_option_sets[config.compat_set]
end
end
-- Parses the provided capabilities.conf. Returns a string (comma separated
-- list) as its formatted in capabilities.conf, or NIL and a message if no file
-- was provided.
local function grabCapenabled(file, open_fail_ok)
local capentries = {}
local commentExpr = "#.*"
if file == nil then
return nil, "No file given"
end
local fh, msg, errno = io.open(file)
if fh == nil then
if not open_fail_ok then
util.abort(errno, msg)
end
return nil, msg
end
for nextline in fh:lines() do
-- Strip any comments.
nextline = nextline:gsub(commentExpr, "")
if nextline ~= "" then
capentries[nextline] = true
end
end
assert(fh:close())
return capentries
end
-- Merge capability (Capsicum) configuration into the global config.
function config.mergeCapability()
-- We ignore errors here if we're relying on the default configuration.
if not config.modifications.capenabled then
config.capenabled = grabCapenabled(config.capabilities_conf,
config.modifications.capabilities_conf == nil)
elseif config.capenabled ~= "" then
-- We have a comma separated list from the format of
-- capabilities.conf, split it into a set with boolean values
-- for each key.
config.capenabled = util.setFromString(config.capenabled,
"[^,]+")
end
end
return config