freebsd-dev/sys/tools/makesyscalls.lua
Warner Losh 8c99d94c90 sys: Remove $FreeBSD$: two-line lua tag
Remove /^--\n--\s*\$FreeBSD\$.*$\n/
2023-08-16 11:55:27 -06:00

1613 lines
42 KiB
Lua

--
-- SPDX-License-Identifier: BSD-2-Clause
--
-- Copyright (c) 2019 Kyle Evans <kevans@FreeBSD.org>
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions
-- are met:
-- 1. Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-- SUCH DAMAGE.
--
-- We generally assume that this script will be run by flua, however we've
-- carefully crafted modules for it that mimic interfaces provided by modules
-- available in ports. Currently, this script is compatible with lua from ports
-- along with the compatible luafilesystem and lua-posix modules.
local lfs = require("lfs")
local unistd = require("posix.unistd")
local savesyscall = -1
local maxsyscall = -1
local generated_tag = "@" .. "generated"
-- Default configuration; any of these may get replaced by a configuration file
-- optionally specified.
local config = {
os_id_keyword = "FreeBSD", -- obsolete, ignored on input, not generated
abi_func_prefix = "",
sysnames = "syscalls.c",
sysproto = "../sys/sysproto.h",
sysproto_h = "_SYS_SYSPROTO_H_",
syshdr = "../sys/syscall.h",
sysmk = "../sys/syscall.mk",
syssw = "init_sysent.c",
syscallprefix = "SYS_",
switchname = "sysent",
namesname = "syscallnames",
systrace = "systrace_args.c",
capabilities_conf = "capabilities.conf",
capenabled = {},
compat_set = "native",
mincompat = 0,
abi_type_suffix = "",
abi_flags = "",
abi_flags_mask = 0,
abi_headers = "",
abi_intptr_t = "intptr_t",
abi_size_t = "size_t",
abi_u_long = "u_long",
abi_long = "long",
abi_semid_t = "semid_t",
abi_ptr_array_t = "",
ptr_intptr_t_cast = "intptr_t",
syscall_abi_change = "",
sys_abi_change = {},
syscall_no_abi_change = "",
sys_no_abi_change = {},
obsol = "",
obsol_dict = {},
unimpl = "",
unimpl_dict = {},
}
local config_modified = {}
local cleantmp = true
local tmpspace = "/tmp/sysent." .. unistd.getpid() .. "/"
local output_files = {
"sysnames",
"syshdr",
"sysmk",
"syssw",
"systrace",
"sysproto",
}
-- These ones we'll create temporary files for; generation purposes.
local temp_files = {
"sysaue",
"sysdcl",
"syscompat",
"syscompatdcl",
"sysent",
"sysinc",
"sysarg",
"sysprotoend",
"systracetmp",
"systraceret",
}
-- Opened files
local files = {}
local function cleanup()
for _, v in pairs(files) do
assert(v:close())
end
if cleantmp then
if lfs.dir(tmpspace) then
for fname in lfs.dir(tmpspace) do
if fname ~= "." and fname ~= ".." then
assert(os.remove(tmpspace .. "/" ..
fname))
end
end
end
if lfs.attributes(tmpspace) and not lfs.rmdir(tmpspace) then
assert(io.stderr:write("Failed to clean up tmpdir: " ..
tmpspace .. "\n"))
end
else
assert(io.stderr:write("Temp files left in " .. tmpspace ..
"\n"))
end
end
local function abort(status, msg)
assert(io.stderr:write(msg .. "\n"))
cleanup()
os.exit(status)
end
-- Each entry should have a value so we can represent abi flags as a bitmask
-- for convenience. One may also optionally provide an expr; this gets applied
-- to each argument type to indicate whether this argument is subject to ABI
-- change given the configured flags.
local known_abi_flags = {
long_size = {
value = 0x00000001,
exprs = {
"_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 = {
value = 0x00000002,
exprs = {
"_Contains[a-z_]*_timet_",
},
},
pointer_args = {
value = 0x00000004,
},
pointer_size = {
value = 0x00000008,
exprs = {
"_Contains[a-z_]*_ptr_",
"[*][*]",
},
},
pair_64bit = {
value = 0x00000010,
exprs = {
"^dev_t[ ]*$",
"^id_t[ ]*$",
"^off_t[ ]*$",
},
},
}
local known_flags = {
STD = 0x00000001,
OBSOL = 0x00000002,
RESERVED = 0x00000004,
UNIMPL = 0x00000008,
NODEF = 0x00000010,
NOARGS = 0x00000020,
NOPROTO = 0x00000040,
NOSTD = 0x00000080,
NOTSTATIC = 0x00000100,
CAPENABLED = 0x00000200,
SYSMUX = 0x00000400,
-- Compat flags start from here. We have plenty of space.
}
-- 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" },
},
}
-- compat_options will be resolved to a set from the configuration.
local compat_options
local function trim(s, char)
if s == nil then
return nil
end
if char == nil then
char = "%s"
end
return s:gsub("^" .. char .. "+", ""):gsub(char .. "+$", "")
end
-- 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
local function process_config(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 = trim(key)
value = 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 = trim(trailing_context)
if trailing_context ~= "" then
print(trailing_context)
abort(1, "Malformed line: " .. nextline)
end
value = trim(value, delim)
else
-- Strip off potential comments
value = value:gsub("#.*$", "")
-- Strip off any padding whitespace
value = trim(value)
if value:match("%s") then
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.
abort(1, "Malformed config line: " .. nextline)
end
end
assert(io.close(fh))
return cfg
end
local function grab_capenabled(file, open_fail_ok)
local capentries = {}
local commentExpr = "#.*"
if file == nil then
print "No file"
return {}
end
local fh = io.open(file)
if fh == nil then
if not open_fail_ok then
abort(1, "Failed to open " .. file)
end
return {}
end
for nextline in fh:lines() do
-- Strip any comments
nextline = nextline:gsub(commentExpr, "")
if nextline ~= "" then
capentries[nextline] = true
end
end
assert(io.close(fh))
return capentries
end
local function process_compat()
local nval = 0
for _, v in pairs(known_flags) do
if v > nval then
nval = v
end
end
nval = nval << 1
for _, v in pairs(compat_options) do
if v.stdcompat ~= nil then
local stdcompat = v.stdcompat
v.definition = "COMPAT_" .. stdcompat:upper()
v.compatlevel = tonumber(stdcompat:match("([0-9]+)$"))
v.flag = stdcompat:gsub("FREEBSD", "COMPAT")
v.prefix = stdcompat:lower() .. "_"
v.descr = stdcompat:lower()
end
local tmpname = "sys" .. v.flag:lower()
local dcltmpname = tmpname .. "dcl"
files[tmpname] = io.tmpfile()
files[dcltmpname] = io.tmpfile()
v.tmp = tmpname
v.dcltmp = dcltmpname
known_flags[v.flag] = nval
v.mask = nval
nval = nval << 1
v.count = 0
end
end
local function process_abi_flags()
local flags, mask = config.abi_flags, 0
for txtflag in flags:gmatch("([^|]+)") do
if known_abi_flags[txtflag] == nil then
abort(1, "Unknown abi_flag: " .. txtflag)
end
mask = mask | known_abi_flags[txtflag].value
end
config.abi_flags_mask = mask
end
local function process_obsol()
local obsol = config.obsol
for syscall in obsol:gmatch("([^ ]+)") do
config.obsol_dict[syscall] = true
end
end
local function process_unimpl()
local unimpl = config.unimpl
for syscall in unimpl:gmatch("([^ ]+)") do
config.unimpl_dict[syscall] = true
end
end
local function process_syscall_abi_change()
local changes_abi = config.syscall_abi_change
for syscall in changes_abi:gmatch("([^ ]+)") do
config.sys_abi_change[syscall] = true
end
local no_changes = config.syscall_no_abi_change
for syscall in no_changes:gmatch("([^ ]+)") do
config.sys_no_abi_change[syscall] = true
end
end
local function abi_changes(name)
if known_abi_flags[name] == nil then
abort(1, "abi_changes: unknown flag: " .. name)
end
return config.abi_flags_mask & known_abi_flags[name].value ~= 0
end
local function strip_abi_prefix(funcname)
local abiprefix = config.abi_func_prefix
local stripped_name
if funcname == nil then
return nil
end
if abiprefix ~= "" and funcname:find("^" .. abiprefix) then
stripped_name = funcname:gsub("^" .. abiprefix, "")
else
stripped_name = funcname
end
return stripped_name
end
local function read_file(tmpfile)
if files[tmpfile] == nil then
print("Not found: " .. tmpfile)
return
end
local fh = files[tmpfile]
assert(fh:seek("set"))
return assert(fh:read("a"))
end
local function write_line(tmpfile, line)
if files[tmpfile] == nil then
print("Not found: " .. tmpfile)
return
end
assert(files[tmpfile]:write(line))
end
local function write_line_pfile(tmppat, line)
for k in pairs(files) do
if k:match(tmppat) ~= nil then
assert(files[k]:write(line))
end
end
end
-- Check both literal intptr_t and the abi version because this needs
-- to work both before and after the substitution
local function isptrtype(type)
return type:find("*") or type:find("caddr_t") or
type:find("intptr_t") or type:find(config.abi_intptr_t)
end
local function isptrarraytype(type)
return type:find("[*][*]") or type:find("[*][ ]*const[ ]*[*]")
end
-- Find types that are always 64-bits wide
local function is64bittype(type)
return type:find("^dev_t[ ]*$") or type:find("^id_t[ ]*$") or type:find("^off_t[ ]*$")
end
local process_syscall_def
-- These patterns are processed in order on any line that isn't empty.
local pattern_table = {
{
-- To be removed soon
pattern = "%s*$" .. config.os_id_keyword,
process = function(_, _)
-- Ignore... ID tag
end,
},
{
dump_prevline = true,
pattern = "^#%s*include",
process = function(line)
line = line .. "\n"
write_line("sysinc", line)
end,
},
{
dump_prevline = true,
pattern = "^#",
process = function(line)
if line:find("^#%s*if") then
savesyscall = maxsyscall
elseif line:find("^#%s*else") then
maxsyscall = savesyscall
end
line = line .. "\n"
write_line("sysent", line)
write_line("sysdcl", line)
write_line("sysarg", line)
write_line_pfile("syscompat[0-9]*$", line)
write_line("sysnames", line)
write_line_pfile("systrace.*", line)
end,
},
{
dump_prevline = true,
pattern = "%%ABI_HEADERS%%",
process = function()
if config.abi_headers ~= "" then
local line = config.abi_headers .. "\n"
write_line("sysinc", line)
end
end,
},
{
-- Buffer anything else
pattern = ".+",
process = function(line, prevline)
local incomplete = line:find("\\$") ~= nil
-- Lines that end in \ get the \ stripped
-- Lines that start with a syscall number, prepend \n
line = trim(line):gsub("\\$", "")
if line:find("^[0-9]") and prevline then
process_syscall_def(prevline)
prevline = nil
end
prevline = (prevline or '') .. line
incomplete = incomplete or prevline:find(",$") ~= nil
incomplete = incomplete or prevline:find("{") ~= nil and
prevline:find("}") == nil
if prevline:find("^[0-9]") and not incomplete then
process_syscall_def(prevline)
prevline = nil
end
return prevline
end,
},
}
local function process_sysfile(file)
local capentries = {}
local commentExpr = "^%s*;.*"
if file == nil then
print "No file"
return {}
end
local fh = io.open(file)
if fh == nil then
print("Failed to open " .. file)
return {}
end
local function do_match(nextline, prevline)
local pattern, handler, dump
for _, v in pairs(pattern_table) do
pattern = v.pattern
handler = v.process
dump = v.dump_prevline
if nextline:match(pattern) then
if dump and prevline then
process_syscall_def(prevline)
prevline = nil
end
return handler(nextline, prevline)
end
end
abort(1, "Failed to handle: " .. nextline)
end
local prevline
for nextline in fh:lines() do
-- Strip any comments
nextline = nextline:gsub(commentExpr, "")
if nextline ~= "" then
prevline = do_match(nextline, prevline)
end
end
-- Dump any remainder
if prevline ~= nil and prevline:find("^[0-9]") then
process_syscall_def(prevline)
end
assert(io.close(fh))
return capentries
end
local function get_mask(flags)
local mask = 0
for _, v in ipairs(flags) do
if known_flags[v] == nil then
abort(1, "Checking for unknown flag " .. v)
end
mask = mask | known_flags[v]
end
return mask
end
local function get_mask_pat(pflags)
local mask = 0
for k, v in pairs(known_flags) do
if k:find(pflags) then
mask = mask | v
end
end
return mask
end
local function align_sysent_comment(col)
write_line("sysent", "\t")
col = col + 8 - col % 8
while col < 56 do
write_line("sysent", "\t")
col = col + 8
end
end
local function strip_arg_annotations(arg)
arg = arg:gsub("_Contains_[^ ]*[_)] ?", "")
arg = arg:gsub("_In[^ ]*[_)] ?", "")
arg = arg:gsub("_Out[^ ]*[_)] ?", "")
return trim(arg)
end
local function check_abi_changes(arg)
for k, v in pairs(known_abi_flags) do
local exprs = v.exprs
if abi_changes(k) and exprs ~= nil then
for _, e in pairs(exprs) do
if arg:find(e) then
return true
end
end
end
end
return false
end
local function process_args(args)
local funcargs = {}
local changes_abi = false
for arg in args:gmatch("([^,]+)") do
local arg_abi_change = check_abi_changes(arg)
changes_abi = changes_abi or arg_abi_change
arg = strip_arg_annotations(arg)
local argname = arg:match("([^* ]+)$")
-- argtype is... everything else.
local argtype = trim(arg:gsub(argname .. "$", ""), nil)
if argtype == "" and argname == "void" then
goto out
end
-- is64bittype() needs a bare type so check it after argname
-- is removed
changes_abi = changes_abi or (abi_changes("pair_64bit") and is64bittype(argtype))
argtype = argtype:gsub("intptr_t", config.abi_intptr_t)
argtype = argtype:gsub("semid_t", config.abi_semid_t)
if isptrtype(argtype) then
argtype = argtype:gsub("size_t", config.abi_size_t)
argtype = argtype:gsub("^long", config.abi_long);
argtype = argtype:gsub("^u_long", config.abi_u_long);
argtype = argtype:gsub("^const u_long", "const " .. config.abi_u_long);
elseif argtype:find("^long$") then
argtype = config.abi_long
end
if isptrarraytype(argtype) and config.abi_ptr_array_t ~= "" then
-- `* const *` -> `**`
argtype = argtype:gsub("[*][ ]*const[ ]*[*]", "**")
-- e.g., `struct aiocb **` -> `uint32_t *`
argtype = argtype:gsub("[^*]*[*]", config.abi_ptr_array_t .. " ", 1)
end
-- XX TODO: Forward declarations? See: sysstubfwd in CheriBSD
if arg_abi_change then
local abi_type_suffix = config.abi_type_suffix
argtype = argtype:gsub("(struct [^ ]*)", "%1" ..
abi_type_suffix)
argtype = argtype:gsub("(union [^ ]*)", "%1" ..
abi_type_suffix)
end
if abi_changes("pair_64bit") and is64bittype(argtype) then
if #funcargs % 2 == 1 then
funcargs[#funcargs + 1] = {
type = "int",
name = "_pad",
}
end
funcargs[#funcargs + 1] = {
type = "uint32_t",
name = argname .. "1",
}
funcargs[#funcargs + 1] = {
type = "uint32_t",
name = argname .. "2",
}
else
funcargs[#funcargs + 1] = {
type = argtype,
name = argname,
}
end
end
::out::
return funcargs, changes_abi
end
local function handle_noncompat(sysnum, thr_flag, flags, sysflags, rettype,
auditev, syscallret, funcname, funcalias, funcargs, argalias)
local argssize
if flags & known_flags.SYSMUX ~= 0 then
argssize = "0"
elseif #funcargs > 0 or flags & known_flags.NODEF ~= 0 then
argssize = "AS(" .. argalias .. ")"
else
argssize = "0"
end
write_line("systrace", string.format([[
/* %s */
case %d: {
]], funcname, sysnum))
write_line("systracetmp", string.format([[
/* %s */
case %d:
]], funcname, sysnum))
write_line("systraceret", string.format([[
/* %s */
case %d:
]], funcname, sysnum))
if #funcargs > 0 and flags & known_flags.SYSMUX == 0 then
write_line("systracetmp", "\t\tswitch (ndx) {\n")
write_line("systrace", string.format(
"\t\tstruct %s *p = params;\n", argalias))
local argtype, argname, desc, padding
padding = ""
for idx, arg in ipairs(funcargs) do
argtype = arg.type
argname = arg.name
argtype = trim(argtype:gsub("__restrict$", ""), nil)
if argtype == "int" and argname == "_pad" and abi_changes("pair_64bit") then
write_line("systracetmp", "#ifdef PAD64_REQUIRED\n")
end
-- Pointer arg?
if argtype:find("*") then
desc = "userland " .. argtype
else
desc = argtype;
end
write_line("systracetmp", string.format(
"\t\tcase %d%s:\n\t\t\tp = \"%s\";\n\t\t\tbreak;\n",
idx - 1, padding, desc))
if argtype == "int" and argname == "_pad" and abi_changes("pair_64bit") then
padding = " - _P_"
write_line("systracetmp", "#define _P_ 0\n#else\n#define _P_ 1\n#endif\n")
end
if isptrtype(argtype) then
write_line("systrace", string.format(
"\t\tuarg[a++] = (%s)p->%s; /* %s */\n",
config.ptr_intptr_t_cast,
argname, argtype))
elseif argtype == "union l_semun" then
write_line("systrace", string.format(
"\t\tuarg[a++] = p->%s.buf; /* %s */\n",
argname, argtype))
elseif argtype:sub(1,1) == "u" or argtype == "size_t" then
write_line("systrace", string.format(
"\t\tuarg[a++] = p->%s; /* %s */\n",
argname, argtype))
else
if argtype == "int" and argname == "_pad" and abi_changes("pair_64bit") then
write_line("systrace", "#ifdef PAD64_REQUIRED\n")
end
write_line("systrace", string.format(
"\t\tiarg[a++] = p->%s; /* %s */\n",
argname, argtype))
if argtype == "int" and argname == "_pad" and abi_changes("pair_64bit") then
write_line("systrace", "#endif\n")
end
end
end
write_line("systracetmp",
"\t\tdefault:\n\t\t\tbreak;\n\t\t};\n")
if padding ~= "" then
write_line("systracetmp", "#undef _P_\n\n")
end
write_line("systraceret", string.format([[
if (ndx == 0 || ndx == 1)
p = "%s";
break;
]], syscallret))
end
local n_args = #funcargs
if flags & known_flags.SYSMUX ~= 0 then
n_args = 0
end
write_line("systrace", string.format(
"\t\t*n_args = %d;\n\t\tbreak;\n\t}\n", n_args))
write_line("systracetmp", "\t\tbreak;\n")
local nargflags = get_mask({"NOARGS", "NOPROTO", "NODEF"})
if flags & nargflags == 0 then
if #funcargs > 0 then
write_line("sysarg", string.format("struct %s {\n",
argalias))
for _, v in ipairs(funcargs) do
local argname, argtype = v.name, v.type
if argtype == "int" and argname == "_pad" and abi_changes("pair_64bit") then
write_line("sysarg", "#ifdef PAD64_REQUIRED\n")
end
write_line("sysarg", string.format(
"\tchar %s_l_[PADL_(%s)]; %s %s; char %s_r_[PADR_(%s)];\n",
argname, argtype,
argtype, argname,
argname, argtype))
if argtype == "int" and argname == "_pad" and abi_changes("pair_64bit") then
write_line("sysarg", "#endif\n")
end
end
write_line("sysarg", "};\n")
else
write_line("sysarg", string.format(
"struct %s {\n\tsyscallarg_t dummy;\n};\n", argalias))
end
end
local protoflags = get_mask({"NOPROTO", "NODEF"})
if flags & protoflags == 0 then
if funcname == "nosys" or funcname == "lkmnosys" or
funcname == "sysarch" or funcname:find("^freebsd") or
funcname:find("^linux") then
write_line("sysdcl", string.format(
"%s\t%s(struct thread *, struct %s *)",
rettype, funcname, argalias))
else
write_line("sysdcl", string.format(
"%s\tsys_%s(struct thread *, struct %s *)",
rettype, funcname, argalias))
end
write_line("sysdcl", ";\n")
write_line("sysaue", string.format("#define\t%sAUE_%s\t%s\n",
config.syscallprefix, funcalias, auditev))
end
write_line("sysent",
string.format("\t{ .sy_narg = %s, .sy_call = (sy_call_t *)", argssize))
local column = 8 + 2 + #argssize + 15
if flags & known_flags.SYSMUX ~= 0 then
write_line("sysent", string.format(
"nosys, .sy_auevent = AUE_NULL, " ..
".sy_flags = %s, .sy_thrcnt = SY_THR_STATIC },",
sysflags))
column = column + #"nosys" + #"AUE_NULL" + 3
elseif flags & known_flags.NOSTD ~= 0 then
write_line("sysent", string.format(
"lkmressys, .sy_auevent = AUE_NULL, " ..
".sy_flags = %s, .sy_thrcnt = SY_THR_ABSENT },",
sysflags))
column = column + #"lkmressys" + #"AUE_NULL" + 3
else
if funcname == "nosys" or funcname == "lkmnosys" or
funcname == "sysarch" or funcname:find("^freebsd") or
funcname:find("^linux") then
write_line("sysent", string.format(
"%s, .sy_auevent = %s, .sy_flags = %s, .sy_thrcnt = %s },",
funcname, auditev, sysflags, thr_flag))
column = column + #funcname + #auditev + #sysflags + 3
else
write_line("sysent", string.format(
"sys_%s, .sy_auevent = %s, .sy_flags = %s, .sy_thrcnt = %s },",
funcname, auditev, sysflags, thr_flag))
column = column + #funcname + #auditev + #sysflags + 7
end
end
align_sysent_comment(column)
write_line("sysent", string.format("/* %d = %s */\n",
sysnum, funcalias))
write_line("sysnames", string.format("\t\"%s\",\t\t\t/* %d = %s */\n",
funcalias, sysnum, funcalias))
if flags & known_flags.NODEF == 0 then
write_line("syshdr", string.format("#define\t%s%s\t%d\n",
config.syscallprefix, funcalias, sysnum))
write_line("sysmk", string.format(" \\\n\t%s.o",
funcalias))
end
end
local function handle_obsol(sysnum, funcname, comment)
write_line("sysent",
"\t{ .sy_narg = 0, .sy_call = (sy_call_t *)nosys, " ..
".sy_auevent = AUE_NULL, .sy_flags = 0, .sy_thrcnt = SY_THR_ABSENT },")
align_sysent_comment(34)
write_line("sysent", string.format("/* %d = obsolete %s */\n",
sysnum, comment))
write_line("sysnames", string.format(
"\t\"obs_%s\",\t\t\t/* %d = obsolete %s */\n",
funcname, sysnum, comment))
write_line("syshdr", string.format("\t\t\t\t/* %d is obsolete %s */\n",
sysnum, comment))
end
local function handle_compat(sysnum, thr_flag, flags, sysflags, rettype,
auditev, funcname, funcalias, funcargs, argalias)
local argssize, out, outdcl, wrap, prefix, descr
if #funcargs > 0 or flags & known_flags.NODEF ~= 0 then
argssize = "AS(" .. argalias .. ")"
else
argssize = "0"
end
for _, v in pairs(compat_options) do
if flags & v.mask ~= 0 then
if config.mincompat > v.compatlevel then
funcname = strip_abi_prefix(funcname)
funcname = v.prefix .. funcname
return handle_obsol(sysnum, funcname, funcname)
end
v.count = v.count + 1
out = v.tmp
outdcl = v.dcltmp
wrap = v.flag:lower()
prefix = v.prefix
descr = v.descr
goto compatdone
end
end
::compatdone::
local dprotoflags = get_mask({"NOPROTO", "NODEF"})
local nargflags = dprotoflags | known_flags.NOARGS
if #funcargs > 0 and flags & nargflags == 0 then
write_line(out, string.format("struct %s {\n", argalias))
for _, v in ipairs(funcargs) do
local argname, argtype = v.name, v.type
write_line(out, string.format(
"\tchar %s_l_[PADL_(%s)]; %s %s; char %s_r_[PADR_(%s)];\n",
argname, argtype,
argtype, argname,
argname, argtype))
end
write_line(out, "};\n")
elseif flags & nargflags == 0 then
write_line("sysarg", string.format(
"struct %s {\n\tsyscallarg_t dummy;\n};\n", argalias))
end
if flags & dprotoflags == 0 then
write_line(outdcl, string.format(
"%s\t%s%s(struct thread *, struct %s *);\n",
rettype, prefix, funcname, argalias))
write_line("sysaue", string.format(
"#define\t%sAUE_%s%s\t%s\n", config.syscallprefix,
prefix, funcname, auditev))
end
if flags & known_flags.NOSTD ~= 0 then
write_line("sysent", string.format(
"\t{ .sy_narg = %s, .sy_call = (sy_call_t *)%s, " ..
".sy_auevent = %s, .sy_flags = 0, " ..
".sy_thrcnt = SY_THR_ABSENT },",
"0", "lkmressys", "AUE_NULL"))
align_sysent_comment(8 + 2 + #"0" + 15 + #"lkmressys" +
#"AUE_NULL" + 3)
else
write_line("sysent", string.format(
"\t{ %s(%s,%s), .sy_auevent = %s, .sy_flags = %s, .sy_thrcnt = %s },",
wrap, argssize, funcname, auditev, sysflags, thr_flag))
align_sysent_comment(8 + 9 + #argssize + 1 + #funcname +
#auditev + #sysflags + 4)
end
write_line("sysent", string.format("/* %d = %s %s */\n",
sysnum, descr, funcalias))
write_line("sysnames", string.format(
"\t\"%s.%s\",\t\t/* %d = %s %s */\n",
wrap, funcalias, sysnum, descr, funcalias))
-- Do not provide freebsdN_* symbols in libc for < FreeBSD 7
local nosymflags = get_mask({"COMPAT", "COMPAT4", "COMPAT6"})
if flags & nosymflags ~= 0 then
write_line("syshdr", string.format(
"\t\t\t\t/* %d is %s %s */\n",
sysnum, descr, funcalias))
elseif flags & known_flags.NODEF == 0 then
write_line("syshdr", string.format("#define\t%s%s%s\t%d\n",
config.syscallprefix, prefix, funcalias, sysnum))
write_line("sysmk", string.format(" \\\n\t%s%s.o",
prefix, funcalias))
end
end
local function handle_unimpl(sysnum, sysstart, sysend, comment)
if sysstart == nil and sysend == nil then
sysstart = tonumber(sysnum)
sysend = tonumber(sysnum)
end
sysnum = sysstart
while sysnum <= sysend do
write_line("sysent", string.format(
"\t{ .sy_narg = 0, .sy_call = (sy_call_t *)nosys, " ..
".sy_auevent = AUE_NULL, .sy_flags = 0, " ..
".sy_thrcnt = SY_THR_ABSENT },\t\t\t/* %d = %s */\n",
sysnum, comment))
write_line("sysnames", string.format(
"\t\"#%d\",\t\t\t/* %d = %s */\n",
sysnum, sysnum, comment))
sysnum = sysnum + 1
end
end
local function handle_reserved(sysnum, sysstart, sysend)
handle_unimpl(sysnum, sysstart, sysend, "reserved for local use")
end
process_syscall_def = function(line)
local sysstart, sysend, flags, funcname, sysflags
local thr_flag, syscallret
local orig = line
flags = 0
thr_flag = "SY_THR_STATIC"
-- Parse out the interesting information first
local initialExpr = "^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s*"
local sysnum, auditev, allflags = line:match(initialExpr)
if sysnum == nil or auditev == nil or allflags == nil then
-- XXX TODO: Better?
abort(1, "Completely malformed: " .. line)
end
if sysnum:find("-") then
sysstart, sysend = sysnum:match("^([%d]+)-([%d]+)$")
if sysstart == nil or sysend == nil then
abort(1, "Malformed range: " .. sysnum)
end
sysnum = nil
sysstart = tonumber(sysstart)
sysend = tonumber(sysend)
if sysstart ~= maxsyscall + 1 then
abort(1, "syscall number out of sync, missing " ..
maxsyscall + 1)
end
else
sysnum = tonumber(sysnum)
if sysnum ~= maxsyscall + 1 then
abort(1, "syscall number out of sync, missing " ..
maxsyscall + 1)
end
end
-- Split flags
for flag in allflags:gmatch("([^|]+)") do
if known_flags[flag] == nil then
abort(1, "Unknown flag " .. flag .. " for " .. sysnum)
end
flags = flags | known_flags[flag]
end
if (flags & get_mask({"RESERVED", "UNIMPL"})) == 0 and sysnum == nil then
abort(1, "Range only allowed with RESERVED and UNIMPL: " .. line)
end
if (flags & known_flags.NOTSTATIC) ~= 0 then
thr_flag = "SY_THR_ABSENT"
end
-- Strip earlier bits out, leave declaration + alt
line = line:gsub("^.+" .. allflags .. "%s*", "")
local decl_fnd = line:find("^{") ~= nil
if decl_fnd and line:find("}") == nil then
abort(1, "Malformed, no closing brace: " .. line)
end
local decl, alt
if decl_fnd then
line = line:gsub("^{", "")
decl, alt = line:match("([^}]*)}[%s]*(.*)$")
else
alt = line
end
if decl == nil and alt == nil then
abort(1, "Malformed bits: " .. line)
end
local funcalias, funcomment, argalias, rettype, args
if not decl_fnd and alt ~= nil and alt ~= "" then
-- Peel off one entry for name
funcname = trim(alt:match("^([^%s]+)"), nil)
alt = alt:gsub("^([^%s]+)[%s]*", "")
end
-- Do we even need it?
if flags & get_mask({"OBSOL", "UNIMPL"}) ~= 0 then
local NF = 0
for _ in orig:gmatch("[^%s]+") do
NF = NF + 1
end
funcomment = funcname or ''
if NF < 6 then
funcomment = funcomment .. " " .. alt
end
funcomment = trim(funcomment)
-- if funcname ~= nil then
-- else
-- funcomment = trim(alt)
-- end
goto skipalt
end
if alt ~= nil and alt ~= "" then
local altExpr = "^([^%s]+)%s+([^%s]+)%s+([^%s]+)"
funcalias, argalias, rettype = alt:match(altExpr)
funcalias = trim(funcalias)
if funcalias == nil or argalias == nil or rettype == nil then
abort(1, "Malformed alt: " .. line)
end
end
if decl_fnd then
-- Don't clobber rettype set in the alt information
if rettype == nil then
rettype = "int"
end
-- Peel off the return type
syscallret = line:match("([^%s]+)%s")
line = line:match("[^%s]+%s(.+)")
-- Pointer incoming
if line:sub(1,1) == "*" then
syscallret = syscallret .. " "
end
while line:sub(1,1) == "*" do
line = line:sub(2)
syscallret = syscallret .. "*"
end
funcname = line:match("^([^(]+)%(")
if funcname == nil then
abort(1, "Not a signature? " .. line)
end
args = line:match("^[^(]+%((.+)%)[^)]*$")
args = trim(args, '[,%s]')
end
::skipalt::
if funcname == nil then
funcname = funcalias
end
funcname = trim(funcname)
if config.obsol_dict[funcname] then
local compat_prefix = ""
for _, v in pairs(compat_options) do
if flags & v.mask ~= 0 then
compat_prefix = v.prefix
goto obsol_compat_done
end
end
::obsol_compat_done::
args = nil
flags = known_flags.OBSOL
funcomment = compat_prefix .. funcname
end
if config.unimpl_dict[funcname] then
flags = known_flags.UNIMPL
funcomment = funcname
end
sysflags = "0"
-- NODEF events do not get audited
if flags & known_flags.NODEF ~= 0 then
auditev = 'AUE_NULL'
end
-- If applicable; strip the ABI prefix from the name
local stripped_name = strip_abi_prefix(funcname)
if flags & known_flags.CAPENABLED ~= 0 or
config.capenabled[funcname] ~= nil or
config.capenabled[stripped_name] ~= nil then
sysflags = "SYF_CAPENABLED"
end
local funcargs = {}
local changes_abi = false
if args ~= nil then
funcargs, changes_abi = process_args(args)
end
if config.sys_no_abi_change[funcname] then
changes_abi = false
end
local noproto = config.abi_flags ~= "" and not changes_abi
local argprefix = ''
local funcprefix = ''
if abi_changes("pointer_args") then
for _, v in ipairs(funcargs) do
if isptrtype(v.type) then
if config.sys_no_abi_change[funcname] then
print("WARNING: " .. funcname ..
" in syscall_no_abi_change, but pointers args are present")
end
changes_abi = true
goto ptrfound
end
end
::ptrfound::
end
if config.sys_abi_change[funcname] then
changes_abi = true
end
if changes_abi then
-- argalias should be:
-- COMPAT_PREFIX + ABI Prefix + funcname
argprefix = config.abi_func_prefix
funcprefix = config.abi_func_prefix
funcalias = funcprefix .. funcname
noproto = false
end
if funcname ~= nil then
funcname = funcprefix .. funcname
end
if funcalias == nil or funcalias == "" then
funcalias = funcname
end
if argalias == nil and funcname ~= nil then
argalias = funcname .. "_args"
for _, v in pairs(compat_options) do
local mask = v.mask
if (flags & mask) ~= 0 then
-- Multiple aliases doesn't seem to make
-- sense.
argalias = v.prefix .. argalias
goto out
end
end
::out::
elseif argalias ~= nil then
argalias = argprefix .. argalias
end
local ncompatflags = get_mask({"STD", "NODEF", "NOARGS", "NOPROTO",
"NOSTD"})
local compatflags = get_mask_pat("COMPAT.*")
if noproto or flags & known_flags.SYSMUX ~= 0 then
flags = flags | known_flags.NOPROTO;
end
if flags & known_flags.OBSOL ~= 0 then
handle_obsol(sysnum, funcname, funcomment)
elseif flags & known_flags.RESERVED ~= 0 then
handle_reserved(sysnum, sysstart, sysend)
elseif flags & known_flags.UNIMPL ~= 0 then
handle_unimpl(sysnum, sysstart, sysend, funcomment)
elseif flags & compatflags ~= 0 then
if flags & known_flags.STD ~= 0 then
abort(1, "Incompatible COMPAT/STD: " .. line)
end
handle_compat(sysnum, thr_flag, flags, sysflags, rettype,
auditev, funcname, funcalias, funcargs, argalias)
elseif flags & ncompatflags ~= 0 then
handle_noncompat(sysnum, thr_flag, flags, sysflags, rettype,
auditev, syscallret, funcname, funcalias, funcargs,
argalias)
else
abort(1, "Bad flags? " .. line)
end
if sysend ~= nil then
maxsyscall = sysend
elseif sysnum ~= nil then
maxsyscall = sysnum
end
end
-- Entry point
if #arg < 1 or #arg > 2 then
error("usage: " .. arg[0] .. " input-file <config-file>")
end
local sysfile, configfile = arg[1], arg[2]
-- process_config either returns nil and a message, or a
-- table that we should merge into the global config
if configfile ~= nil then
local res = assert(process_config(configfile))
for k, v in pairs(res) do
if v ~= config[k] then
config[k] = v
config_modified[k] = true
end
end
end
local compat_set = config.compat_set
if compat_set ~= "" then
if not compat_option_sets[compat_set] then
abort(1, "Undefined compat set: " .. compat_set)
end
compat_options = compat_option_sets[compat_set]
else
compat_options = {}
end
-- We ignore errors here if we're relying on the default configuration.
if not config_modified.capenabled then
config.capenabled = grab_capenabled(config.capabilities_conf,
config_modified.capabilities_conf == nil)
elseif config.capenabled ~= "" then
-- Due to limitations in the config format mostly, we'll have a comma
-- separated list. Parse it into lines
local capenabled = {}
-- print("here: " .. config.capenabled)
for sysc in config.capenabled:gmatch("([^,]+)") do
capenabled[sysc] = true
end
config.capenabled = capenabled
end
process_compat()
process_abi_flags()
process_syscall_abi_change()
process_obsol()
process_unimpl()
if not lfs.mkdir(tmpspace) then
error("Failed to create tempdir " .. tmpspace)
end
-- XXX Revisit the error handling here, we should probably move the rest of this
-- into a function that we pcall() so we can catch the errors and clean up
-- gracefully.
for _, v in ipairs(temp_files) do
local tmpname = tmpspace .. v
files[v] = io.open(tmpname, "w+")
-- XXX Revisit these with a pcall() + error handler
if not files[v] then
abort(1, "Failed to open temp file: " .. tmpname)
end
end
for _, v in ipairs(output_files) do
local tmpname = tmpspace .. v
files[v] = io.open(tmpname, "w+")
-- XXX Revisit these with a pcall() + error handler
if not files[v] then
abort(1, "Failed to open temp output file: " .. tmpname)
end
end
-- Write out all of the preamble bits
write_line("sysent", string.format([[
/* The casts are bogus but will do for now. */
struct sysent %s[] = {
]], config.switchname))
write_line("syssw", string.format([[/*
* System call switch table.
*
* DO NOT EDIT-- this file is automatically %s.
*/
]], generated_tag))
write_line("sysarg", string.format([[/*
* System call prototypes.
*
* DO NOT EDIT-- this file is automatically %s.
*/
#ifndef %s
#define %s
#include <sys/signal.h>
#include <sys/acl.h>
#include <sys/cpuset.h>
#include <sys/domainset.h>
#include <sys/_ffcounter.h>
#include <sys/_semaphore.h>
#include <sys/ucontext.h>
#include <sys/wait.h>
#include <bsm/audit_kevents.h>
struct proc;
struct thread;
#define PAD_(t) (sizeof(syscallarg_t) <= sizeof(t) ? \
0 : sizeof(syscallarg_t) - sizeof(t))
#if BYTE_ORDER == LITTLE_ENDIAN
#define PADL_(t) 0
#define PADR_(t) PAD_(t)
#else
#define PADL_(t) PAD_(t)
#define PADR_(t) 0
#endif
]], generated_tag, config.sysproto_h, config.sysproto_h))
if abi_changes("pair_64bit") then
write_line("sysarg", string.format([[
#if !defined(PAD64_REQUIRED) && !defined(__amd64__)
#define PAD64_REQUIRED
#endif
]]))
end
if abi_changes("pair_64bit") then
write_line("systrace", string.format([[
#if !defined(PAD64_REQUIRED) && !defined(__amd64__)
#define PAD64_REQUIRED
#endif
]]))
end
for _, v in pairs(compat_options) do
write_line(v.tmp, string.format("\n#ifdef %s\n\n", v.definition))
end
write_line("sysnames", string.format([[/*
* System call names.
*
* DO NOT EDIT-- this file is automatically %s.
*/
const char *%s[] = {
]], generated_tag, config.namesname))
write_line("syshdr", string.format([[/*
* System call numbers.
*
* DO NOT EDIT-- this file is automatically %s.
*/
]], generated_tag))
write_line("sysmk", string.format([[# FreeBSD system call object files.
# DO NOT EDIT-- this file is automatically %s.
MIASM = ]], generated_tag))
write_line("systrace", string.format([[/*
* System call argument to DTrace register array converstion.
*
* DO NOT EDIT-- this file is automatically %s.
* This file is part of the DTrace syscall provider.
*/
static void
systrace_args(int sysnum, void *params, uint64_t *uarg, int *n_args)
{
int64_t *iarg = (int64_t *)uarg;
int a = 0;
switch (sysnum) {
]], generated_tag))
write_line("systracetmp", [[static void
systrace_entry_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
{
const char *p = NULL;
switch (sysnum) {
]])
write_line("systraceret", [[static void
systrace_return_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
{
const char *p = NULL;
switch (sysnum) {
]])
-- Processing the sysfile will parse out the preprocessor bits and put them into
-- the appropriate place. Any syscall-looking lines get thrown into the sysfile
-- buffer, one per line, for later processing once they're all glued together.
process_sysfile(sysfile)
write_line("sysinc",
"\n#define AS(name) (sizeof(struct name) / sizeof(syscallarg_t))\n")
for _, v in pairs(compat_options) do
if v.count > 0 then
write_line("sysinc", string.format([[
#ifdef %s
#define %s(n, name) .sy_narg = n, .sy_call = (sy_call_t *)__CONCAT(%s, name)
#else
#define %s(n, name) .sy_narg = 0, .sy_call = (sy_call_t *)nosys
#endif
]], v.definition, v.flag:lower(), v.prefix, v.flag:lower()))
end
write_line(v.dcltmp, string.format("\n#endif /* %s */\n\n",
v.definition))
end
write_line("sysprotoend", string.format([[
#undef PAD_
#undef PADL_
#undef PADR_
#endif /* !%s */
]], config.sysproto_h))
write_line("sysmk", "\n")
write_line("sysent", "};\n")
write_line("sysnames", "};\n")
-- maxsyscall is the highest seen; MAXSYSCALL should be one higher
write_line("syshdr", string.format("#define\t%sMAXSYSCALL\t%d\n",
config.syscallprefix, maxsyscall + 1))
write_line("systrace", [[
default:
*n_args = 0;
break;
};
}
]])
write_line("systracetmp", [[
default:
break;
};
if (p != NULL)
strlcpy(desc, p, descsz);
}
]])
write_line("systraceret", [[
default:
break;
};
if (p != NULL)
strlcpy(desc, p, descsz);
}
]])
-- Finish up; output
write_line("syssw", read_file("sysinc"))
write_line("syssw", read_file("sysent"))
write_line("sysproto", read_file("sysarg"))
write_line("sysproto", read_file("sysdcl"))
for _, v in pairs(compat_options) do
write_line("sysproto", read_file(v.tmp))
write_line("sysproto", read_file(v.dcltmp))
end
write_line("sysproto", read_file("sysaue"))
write_line("sysproto", read_file("sysprotoend"))
write_line("systrace", read_file("systracetmp"))
write_line("systrace", read_file("systraceret"))
for _, v in ipairs(output_files) do
local target = config[v]
if target ~= "/dev/null" then
local fh = assert(io.open(target, "w+"))
if fh == nil then
abort(1, "Failed to open '" .. target .. "'")
end
assert(fh:write(read_file(v)))
assert(fh:close())
end
end
cleanup()