diff --git a/sys/tools/makesyscalls.lua b/sys/tools/makesyscalls.lua index e723346f1679..48ad05eb0469 100644 --- a/sys/tools/makesyscalls.lua +++ b/sys/tools/makesyscalls.lua @@ -1,1546 +1,1561 @@ -- -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD -- -- Copyright (c) 2019 Kyle Evans -- -- 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. -- -- $FreeBSD$ -- -- 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", 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 = {}, 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", 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, -- Compat flags start from here. We have plenty of space. } -- All compat_options 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_options = { { definition = "COMPAT_43", compatlevel = 3, flag = "COMPAT", prefix = "o", descr = "old", }, { stdcompat = "FREEBSD4" }, { stdcompat = "FREEBSD6" }, { stdcompat = "FREEBSD7" }, { stdcompat = "FREEBSD10" }, { stdcompat = "FREEBSD11" }, { stdcompat = "FREEBSD12" }, } 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 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 = { { 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 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("_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 abi_change = not isptrtype(arg) or check_abi_changes(arg) + 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 abi_change then + 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 + return funcargs, changes_abi end local function handle_noncompat(sysnum, thr_flag, flags, sysflags, rettype, auditev, syscallret, funcname, funcalias, funcargs, argalias) local argssize if #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 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 write_line("systrace", string.format( "\t\t*n_args = %d;\n\t\tbreak;\n\t}\n", #funcargs)) 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\tregister_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["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\tregister_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, comment) 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 = process_args(args) + funcargs, changes_abi = process_args(args) 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 - -- argalias should be: - -- COMPAT_PREFIX + ABI Prefix + funcname - argprefix = config['abi_func_prefix'] - funcprefix = config['abi_func_prefix'] - funcalias = funcprefix .. funcname + changes_abi = true goto ptrfound end end ::ptrfound:: 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 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 ") 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 -- 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_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. * $%s$ */ ]], generated_tag, config['os_id_keyword'])) write_line("sysarg", string.format([[/* * System call prototypes. * * DO NOT EDIT-- this file is automatically %s. * $%s$ */ #ifndef %s #define %s #include #include #include #include #include #include #include #include #include struct proc; struct thread; #define PAD_(t) (sizeof(register_t) <= sizeof(t) ? \ 0 : sizeof(register_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['os_id_keyword'], 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. * $%s$ */ const char *%s[] = { ]], generated_tag, config['os_id_keyword'], config['namesname'])) write_line("syshdr", string.format([[/* * System call numbers. * * DO NOT EDIT-- this file is automatically %s. * $%s$ */ ]], generated_tag, config['os_id_keyword'])) write_line("sysmk", string.format([[# FreeBSD system call object files. # DO NOT EDIT-- this file is automatically %s. # $%s$ MIASM = ]], generated_tag, config['os_id_keyword'])) write_line("systrace", string.format([[/* * System call argument to DTrace register array converstion. * * DO NOT EDIT-- this file is automatically %s. * $%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, config['os_id_keyword'])) 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(register_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()