xref: /freebsd-14.2/sys/tools/makesyscalls.lua (revision b9df18d6)
1--
2-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3--
4-- Copyright (c) 2019 Kyle Evans <[email protected]>
5--
6-- Redistribution and use in source and binary forms, with or without
7-- modification, are permitted provided that the following conditions
8-- are met:
9-- 1. Redistributions of source code must retain the above copyright
10--    notice, this list of conditions and the following disclaimer.
11-- 2. Redistributions in binary form must reproduce the above copyright
12--    notice, this list of conditions and the following disclaimer in the
13--    documentation and/or other materials provided with the distribution.
14--
15-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18-- ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25-- SUCH DAMAGE.
26--
27-- $FreeBSD$
28--
29
30
31-- We generally assume that this script will be run by flua, however we've
32-- carefully crafted modules for it that mimic interfaces provided by modules
33-- available in ports.  Currently, this script is compatible with lua from ports
34-- along with the compatible luafilesystem and lua-posix modules.
35local lfs = require("lfs")
36local unistd = require("posix.unistd")
37
38local savesyscall = -1
39local maxsyscall = -1
40local generated_tag = "@" .. "generated"
41
42-- Default configuration; any of these may get replaced by a configuration file
43-- optionally specified.
44local config = {
45	os_id_keyword = "FreeBSD",
46	abi_func_prefix = "",
47	sysnames = "syscalls.c",
48	sysproto = "../sys/sysproto.h",
49	sysproto_h = "_SYS_SYSPROTO_H_",
50	syshdr = "../sys/syscall.h",
51	sysmk = "../sys/syscall.mk",
52	syssw = "init_sysent.c",
53	syscallprefix = "SYS_",
54	switchname = "sysent",
55	namesname = "syscallnames",
56	systrace = "systrace_args.c",
57	capabilities_conf = "capabilities.conf",
58	capenabled = {},
59	mincompat = 0,
60	abi_type_suffix = "",
61	abi_flags = "",
62	abi_flags_mask = 0,
63	ptr_intptr_t_cast = "intptr_t",
64}
65
66local config_modified = {}
67local cleantmp = true
68local tmpspace = "/tmp/sysent." .. unistd.getpid() .. "/"
69
70local output_files = {
71	"sysnames",
72	"syshdr",
73	"sysmk",
74	"syssw",
75	"systrace",
76	"sysproto",
77}
78
79-- These ones we'll create temporary files for; generation purposes.
80local temp_files = {
81	"sysaue",
82	"sysdcl",
83	"syscompat",
84	"syscompatdcl",
85	"sysent",
86	"sysinc",
87	"sysarg",
88	"sysprotoend",
89	"systracetmp",
90	"systraceret",
91}
92
93-- Opened files
94local files = {}
95
96local function cleanup()
97	for _, v in pairs(files) do
98		v:close()
99	end
100	if cleantmp then
101		if lfs.dir(tmpspace) then
102			for fname in lfs.dir(tmpspace) do
103				os.remove(tmpspace .. "/" .. fname)
104			end
105		end
106
107		if lfs.attributes(tmpspace) and not lfs.rmdir(tmpspace) then
108			io.stderr:write("Failed to clean up tmpdir: " ..
109			    tmpspace .. "\n")
110		end
111	else
112		io.stderr:write("Temp files left in " .. tmpspace .. "\n")
113	end
114end
115
116local function abort(status, msg)
117	io.stderr:write(msg .. "\n")
118	cleanup()
119	os.exit(status)
120end
121
122-- Each entry should have a value so we can represent abi flags as a bitmask
123-- for convenience.  One may also optionally provide an expr; this gets applied
124-- to each argument type to indicate whether this argument is subject to ABI
125-- change given the configured flags.
126local known_abi_flags = {
127	long_size = {
128		value	= 0x00000001,
129		expr	= "_Contains[a-z_]*_long_",
130	},
131	time_t_size = {
132		value	= 0x00000002,
133		expr	= "_Contains[a-z_]*_timet_/",
134	},
135	pointer_args = {
136		value	= 0x00000004,
137	},
138	pointer_size = {
139		value	= 0x00000008,
140		expr	= "_Contains[a-z_]*_ptr_",
141	},
142}
143
144local known_flags = {
145	STD		= 0x00000001,
146	OBSOL		= 0x00000002,
147	RESERVED	= 0x00000004,
148	UNIMPL		= 0x00000008,
149	NODEF		= 0x00000010,
150	NOARGS		= 0x00000020,
151	NOPROTO		= 0x00000040,
152	NOSTD		= 0x00000080,
153	NOTSTATIC	= 0x00000100,
154	CAPENABLED	= 0x00000200,
155
156	-- Compat flags start from here.  We have plenty of space.
157}
158
159-- All compat_options entries should have five entries:
160--	definition: The preprocessor macro that will be set for this
161--	compatlevel: The level this compatibility should be included at.  This
162--	    generally represents the version of FreeBSD that it is compatible
163--	    with, but ultimately it's just the level of mincompat in which it's
164--	    included.
165--	flag: The name of the flag in syscalls.master.
166--	prefix: The prefix to use for _args and syscall prototype.  This will be
167--	    used as-is, without "_" or any other character appended.
168--	descr: The description of this compat option in init_sysent.c comments.
169-- The special "stdcompat" entry will cause the other five to be autogenerated.
170local compat_options = {
171	{
172		definition = "COMPAT_43",
173		compatlevel = 3,
174		flag = "COMPAT",
175		prefix = "o",
176		descr = "old",
177	},
178	{ stdcompat = "FREEBSD4" },
179	{ stdcompat = "FREEBSD6" },
180	{ stdcompat = "FREEBSD7" },
181	{ stdcompat = "FREEBSD10" },
182	{ stdcompat = "FREEBSD11" },
183	{ stdcompat = "FREEBSD12" },
184}
185
186local function trim(s, char)
187	if s == nil then
188		return nil
189	end
190	if char == nil then
191		char = "%s"
192	end
193	return s:gsub("^" .. char .. "+", ""):gsub(char .. "+$", "")
194end
195
196-- We have to io.popen it, making sure it's properly escaped, and grab the
197-- output from the handle returned.
198local function exec(cmd)
199	cmd = cmd:gsub('"', '\\"')
200
201	local shcmd = "/bin/sh -c \"" .. cmd .. "\""
202	local fh = io.popen(shcmd)
203	local output = fh:read("a")
204
205	fh:close()
206	return output
207end
208
209-- config looks like a shell script; in fact, the previous makesyscalls.sh
210-- script actually sourced it in.  It had a pretty common format, so we should
211-- be fine to make various assumptions
212local function process_config(file)
213	local cfg = {}
214	local comment_line_expr = "^%s*#.*"
215	-- We capture any whitespace padding here so we can easily advance to
216	-- the end of the line as needed to check for any trailing bogus bits.
217	-- Alternatively, we could drop the whitespace and instead try to
218	-- use a pattern to strip out the meaty part of the line, but then we
219	-- would need to sanitize the line for potentially special characters.
220	local line_expr = "^([%w%p]+%s*)=(%s*[`\"]?[^\"`]+[`\"]?)"
221
222	if file == nil then
223		return nil, "No file given"
224	end
225
226	local fh = io.open(file)
227	if fh == nil then
228		return nil, "Could not open file"
229	end
230
231	for nextline in fh:lines() do
232		-- Strip any whole-line comments
233		nextline = nextline:gsub(comment_line_expr, "")
234		-- Parse it into key, value pairs
235		local key, value = nextline:match(line_expr)
236		if key ~= nil and value ~= nil then
237			local kvp = key .. "=" .. value
238			key = trim(key)
239			value = trim(value)
240			local delim = value:sub(1,1)
241			if delim == '`' or delim == '"' then
242				local trailing_context
243				-- Strip off the key/value part
244				trailing_context = nextline:sub(kvp:len() + 1)
245				-- Strip off any trailing comment
246				trailing_context = trailing_context:gsub("#.*$",
247				    "")
248				-- Strip off leading/trailing whitespace
249				trailing_context = trim(trailing_context)
250				if trailing_context ~= "" then
251					print(trailing_context)
252					abort(1, "Malformed line: " .. nextline)
253				end
254			end
255			if delim == '`' then
256				-- Command substition may use $1 and $2 to mean
257				-- the syscall definition file and itself
258				-- respectively.  We'll go ahead and replace
259				-- $[0-9] with respective arg in case we want to
260				-- expand this in the future easily...
261				value = trim(value, delim)
262				for capture in value:gmatch("$([0-9]+)") do
263					capture = tonumber(capture)
264					if capture > #arg then
265						abort(1, "Not enough args: " ..
266						    value)
267					end
268					value = value:gsub("$" .. capture,
269					    arg[capture])
270				end
271
272				value = exec(value)
273			elseif delim == '"' then
274				value = trim(value, delim)
275			else
276				-- Strip off potential comments
277				value = value:gsub("#.*$", "")
278				-- Strip off any padding whitespace
279				value = trim(value)
280				if value:match("%s") then
281					abort(1, "Malformed config line: " ..
282					    nextline)
283				end
284			end
285			cfg[key] = value
286		elseif not nextline:match("^%s*$") then
287			-- Make sure format violations don't get overlooked
288			-- here, but ignore blank lines.  Comments are already
289			-- stripped above.
290			abort(1, "Malformed config line: " .. nextline)
291		end
292	end
293
294	io.close(fh)
295	return cfg
296end
297
298local function grab_capenabled(file, open_fail_ok)
299	local capentries = {}
300	local commentExpr = "#.*"
301
302	if file == nil then
303		print "No file"
304		return {}
305	end
306
307	local fh = io.open(file)
308	if fh == nil then
309		if not open_fail_ok then
310			abort(1, "Failed to open " .. file)
311		end
312		return {}
313	end
314
315	for nextline in fh:lines() do
316		-- Strip any comments
317		nextline = nextline:gsub(commentExpr, "")
318		if nextline ~= "" then
319			capentries[nextline] = true
320		end
321	end
322
323	io.close(fh)
324	return capentries
325end
326
327local function process_compat()
328	local nval = 0
329	for _, v in pairs(known_flags) do
330		if v > nval then
331			nval = v
332		end
333	end
334
335	nval = nval << 1
336	for _, v in pairs(compat_options) do
337		if v["stdcompat"] ~= nil then
338			local stdcompat = v["stdcompat"]
339			v["definition"] = "COMPAT_" .. stdcompat:upper()
340			v["compatlevel"] = tonumber(stdcompat:match("([0-9]+)$"))
341			v["flag"] = stdcompat:gsub("FREEBSD", "COMPAT")
342			v["prefix"] = stdcompat:lower() .. "_"
343			v["descr"] = stdcompat:lower()
344		end
345
346		local tmpname = "sys" .. v["flag"]:lower()
347		local dcltmpname = tmpname .. "dcl"
348		files[tmpname] = io.tmpfile()
349		files[dcltmpname] = io.tmpfile()
350		v["tmp"] = tmpname
351		v["dcltmp"] = dcltmpname
352
353		known_flags[v["flag"]] = nval
354		v["mask"] = nval
355		nval = nval << 1
356
357		v["count"] = 0
358	end
359end
360
361local function process_abi_flags()
362	local flags, mask = config["abi_flags"], 0
363	for txtflag in flags:gmatch("([^|]+)") do
364		if known_abi_flags[txtflag] == nil then
365			abort(1, "Unknown abi_flag: " .. txtflag)
366		end
367
368		mask = mask | known_abi_flags[txtflag]["value"]
369	end
370
371	config["abi_flags_mask"] = mask
372end
373
374local function abi_changes(name)
375	if known_abi_flags[name] == nil then
376		abort(1, "abi_changes: unknown flag: " .. name)
377	end
378
379	return config["abi_flags_mask"] & known_abi_flags[name]["value"] ~= 0
380end
381
382local function strip_abi_prefix(funcname)
383	local abiprefix = config["abi_func_prefix"]
384	local stripped_name
385	if abiprefix ~= "" and funcname:find("^" .. abiprefix) then
386		stripped_name = funcname:gsub("^" .. abiprefix, "")
387	else
388		stripped_name = funcname
389	end
390
391	return stripped_name
392end
393
394local function read_file(tmpfile)
395	if files[tmpfile] == nil then
396		print("Not found: " .. tmpfile)
397		return
398	end
399
400	local fh = files[tmpfile]
401	fh:seek("set")
402	return fh:read("a")
403end
404
405local function write_line(tmpfile, line)
406	if files[tmpfile] == nil then
407		print("Not found: " .. tmpfile)
408		return
409	end
410	files[tmpfile]:write(line)
411end
412
413local function write_line_pfile(tmppat, line)
414	for k in pairs(files) do
415		if k:match(tmppat) ~= nil then
416			files[k]:write(line)
417		end
418	end
419end
420
421local function isptrtype(type)
422	return type:find("*") or type:find("caddr_t")
423	    -- XXX NOTYET: or type:find("intptr_t")
424end
425
426local process_syscall_def
427
428-- These patterns are processed in order on any line that isn't empty.
429local pattern_table = {
430	{
431		pattern = "%s*$" .. config['os_id_keyword'],
432		process = function(_, _)
433			-- Ignore... ID tag
434		end,
435	},
436	{
437		dump_prevline = true,
438		pattern = "^#%s*include",
439		process = function(line)
440			line = line .. "\n"
441			write_line('sysinc', line)
442		end,
443	},
444	{
445		dump_prevline = true,
446		pattern = "^#",
447		process = function(line)
448			if line:find("^#%s*if") then
449				savesyscall = maxsyscall
450			elseif line:find("^#%s*else") then
451				maxsyscall = savesyscall
452			end
453			line = line .. "\n"
454			write_line('sysent', line)
455			write_line('sysdcl', line)
456			write_line('sysarg', line)
457			write_line_pfile('syscompat[0-9]*$', line)
458			write_line('sysnames', line)
459			write_line_pfile('systrace.*', line)
460		end,
461	},
462	{
463		-- Buffer anything else
464		pattern = ".+",
465		process = function(line, prevline)
466			local incomplete = line:find("\\$") ~= nil
467			-- Lines that end in \ get the \ stripped
468			-- Lines that start with a syscall number, prepend \n
469			line = trim(line):gsub("\\$", "")
470			if line:find("^[0-9]") and prevline then
471				process_syscall_def(prevline)
472				prevline = nil
473			end
474
475			prevline = (prevline or '') .. line
476			incomplete = incomplete or prevline:find(",$") ~= nil
477			incomplete = incomplete or prevline:find("{") ~= nil and
478			    prevline:find("}") == nil
479			if prevline:find("^[0-9]") and not incomplete then
480				process_syscall_def(prevline)
481				prevline = nil
482			end
483
484			return prevline
485		end,
486	},
487}
488
489local function process_sysfile(file)
490	local capentries = {}
491	local commentExpr = "^%s*;.*"
492
493	if file == nil then
494		print "No file"
495		return {}
496	end
497
498	local fh = io.open(file)
499	if fh == nil then
500		print("Failed to open " .. file)
501		return {}
502	end
503
504	local function do_match(nextline, prevline)
505		local pattern, handler, dump
506		for _, v in pairs(pattern_table) do
507			pattern = v['pattern']
508			handler = v['process']
509			dump = v['dump_prevline']
510			if nextline:match(pattern) then
511				if dump and prevline then
512					process_syscall_def(prevline)
513					prevline = nil
514				end
515
516				return handler(nextline, prevline)
517			end
518		end
519
520		abort(1, "Failed to handle: " .. nextline)
521	end
522
523	local prevline
524	for nextline in fh:lines() do
525		-- Strip any comments
526		nextline = nextline:gsub(commentExpr, "")
527		if nextline ~= "" then
528			prevline = do_match(nextline, prevline)
529		end
530	end
531
532	-- Dump any remainder
533	if prevline ~= nil and prevline:find("^[0-9]") then
534		process_syscall_def(prevline)
535	end
536
537	io.close(fh)
538	return capentries
539end
540
541local function get_mask(flags)
542	local mask = 0
543	for _, v in ipairs(flags) do
544		if known_flags[v] == nil then
545			abort(1, "Checking for unknown flag " .. v)
546		end
547
548		mask = mask | known_flags[v]
549	end
550
551	return mask
552end
553
554local function get_mask_pat(pflags)
555	local mask = 0
556	for k, v in pairs(known_flags) do
557		if k:find(pflags) then
558			mask = mask | v
559		end
560	end
561
562	return mask
563end
564
565local function align_sysent_comment(col)
566	write_line("sysent", "\t")
567	col = col + 8 - col % 8
568	while col < 56 do
569		write_line("sysent", "\t")
570		col = col + 8
571	end
572end
573
574local function strip_arg_annotations(arg)
575	arg = arg:gsub("_In[^ ]*[_)] ?", "")
576	arg = arg:gsub("_Out[^ ]*[_)] ?", "")
577	return trim(arg)
578end
579
580local function check_abi_changes(arg)
581	for k, v in pairs(known_abi_flags) do
582		local expr = v["expr"]
583		if abi_changes(k) and expr ~= nil and arg:find(expr) then
584			return true
585		end
586	end
587
588	return false
589end
590
591local function process_args(args)
592	local funcargs = {}
593
594	for arg in args:gmatch("([^,]+)") do
595		local abi_change = not isptrtype(arg) or check_abi_changes(arg)
596
597		arg = strip_arg_annotations(arg)
598
599		local argname = arg:match("([^* ]+)$")
600
601		-- argtype is... everything else.
602		local argtype = trim(arg:gsub(argname .. "$", ""), nil)
603
604		if argtype == "" and argname == "void" then
605			goto out
606		end
607
608		-- XX TODO: Forward declarations? See: sysstubfwd in CheriBSD
609		if abi_change then
610			local abi_type_suffix = config["abi_type_suffix"]
611			argtype = argtype:gsub("_native ", "")
612			argtype = argtype:gsub("(struct [^ ]*)", "%1" ..
613			    abi_type_suffix)
614			argtype = argtype:gsub("(union [^ ]*)", "%1" ..
615			    abi_type_suffix)
616		end
617
618		funcargs[#funcargs + 1] = {
619			type = argtype,
620			name = argname,
621		}
622	end
623
624	::out::
625	return funcargs
626end
627
628local function handle_noncompat(sysnum, thr_flag, flags, sysflags, rettype,
629    auditev, syscallret, funcname, funcalias, funcargs, argalias)
630	local argssize
631
632	if #funcargs > 0 or flags & known_flags["NODEF"] ~= 0 then
633		argssize = "AS(" .. argalias .. ")"
634	else
635		argssize = "0"
636	end
637
638	write_line("systrace", string.format([[
639	/* %s */
640	case %d: {
641]], funcname, sysnum))
642	write_line("systracetmp", string.format([[
643	/* %s */
644	case %d:
645]], funcname, sysnum))
646	write_line("systraceret", string.format([[
647	/* %s */
648	case %d:
649]], funcname, sysnum))
650
651	if #funcargs > 0 then
652		write_line("systracetmp", "\t\tswitch (ndx) {\n")
653		write_line("systrace", string.format(
654		    "\t\tstruct %s *p = params;\n", argalias))
655
656		local argtype, argname
657		for idx, arg in ipairs(funcargs) do
658			argtype = arg["type"]
659			argname = arg["name"]
660
661			argtype = trim(argtype:gsub("__restrict$", ""), nil)
662			-- Pointer arg?
663			if argtype:find("*") then
664				write_line("systracetmp", string.format(
665				    "\t\tcase %d:\n\t\t\tp = \"userland %s\";\n\t\t\tbreak;\n",
666				    idx - 1, argtype))
667			else
668				write_line("systracetmp", string.format(
669				    "\t\tcase %d:\n\t\t\tp = \"%s\";\n\t\t\tbreak;\n",
670				    idx - 1, argtype))
671			end
672
673			if isptrtype(argtype) then
674				write_line("systrace", string.format(
675				    "\t\tuarg[%d] = (%s)p->%s; /* %s */\n",
676				    idx - 1, config["ptr_intptr_t_cast"],
677				    argname, argtype))
678			elseif argtype == "union l_semun" then
679				write_line("systrace", string.format(
680				    "\t\tuarg[%d] = p->%s.buf; /* %s */\n",
681				    idx - 1, argname, argtype))
682			elseif argtype:sub(1,1) == "u" or argtype == "size_t" then
683				write_line("systrace", string.format(
684				    "\t\tuarg[%d] = p->%s; /* %s */\n",
685				    idx - 1, argname, argtype))
686			else
687				write_line("systrace", string.format(
688				    "\t\tiarg[%d] = p->%s; /* %s */\n",
689				    idx - 1, argname, argtype))
690			end
691		end
692
693		write_line("systracetmp",
694		    "\t\tdefault:\n\t\t\tbreak;\n\t\t};\n")
695
696		write_line("systraceret", string.format([[
697		if (ndx == 0 || ndx == 1)
698			p = "%s";
699		break;
700]], syscallret))
701	end
702	write_line("systrace", string.format(
703	    "\t\t*n_args = %d;\n\t\tbreak;\n\t}\n", #funcargs))
704	write_line("systracetmp", "\t\tbreak;\n")
705
706	local nargflags = get_mask({"NOARGS", "NOPROTO", "NODEF"})
707	if flags & nargflags == 0 then
708		if #funcargs > 0 then
709			write_line("sysarg", string.format("struct %s {\n",
710			    argalias))
711			for _, v in ipairs(funcargs) do
712				local argname, argtype = v["name"], v["type"]
713				write_line("sysarg", string.format(
714				    "\tchar %s_l_[PADL_(%s)]; %s %s; char %s_r_[PADR_(%s)];\n",
715				    argname, argtype,
716				    argtype, argname,
717				    argname, argtype))
718			end
719			write_line("sysarg", "};\n")
720		else
721			write_line("sysarg", string.format(
722			    "struct %s {\n\tregister_t dummy;\n};\n", argalias))
723		end
724	end
725
726	local protoflags = get_mask({"NOPROTO", "NODEF"})
727	if flags & protoflags == 0 then
728		if funcname == "nosys" or funcname == "lkmnosys" or
729		    funcname == "sysarch" or funcname:find("^freebsd") or
730		    funcname:find("^linux") or
731		    funcname:find("^cloudabi") then
732			write_line("sysdcl", string.format(
733			    "%s\t%s(struct thread *, struct %s *)",
734			    rettype, funcname, argalias))
735		else
736			write_line("sysdcl", string.format(
737			    "%s\tsys_%s(struct thread *, struct %s *)",
738			    rettype, funcname, argalias))
739		end
740		write_line("sysdcl", ";\n")
741		write_line("sysaue", string.format("#define\t%sAUE_%s\t%s\n",
742		    config['syscallprefix'], funcalias, auditev))
743	end
744
745	write_line("sysent",
746	    string.format("\t{ .sy_narg = %s, .sy_call = (sy_call_t *)", argssize))
747	local column = 8 + 2 + #argssize + 15
748
749	if flags & known_flags["NOSTD"] ~= 0 then
750		write_line("sysent", string.format(
751		    "lkmressys, .sy_auevent = AUE_NULL, " ..
752		    ".sy_flags = %s, .sy_thrcnt = SY_THR_ABSENT },",
753		    sysflags))
754		column = column + #"lkmressys" + #"AUE_NULL" + 3
755	else
756		if funcname == "nosys" or funcname == "lkmnosys" or
757		    funcname == "sysarch" or funcname:find("^freebsd") or
758		    funcname:find("^linux") or
759		    funcname:find("^cloudabi") then
760			write_line("sysent", string.format(
761			    "%s, .sy_auevent = %s, .sy_flags = %s, .sy_thrcnt = %s },",
762			    funcname, auditev, sysflags, thr_flag))
763			column = column + #funcname + #auditev + #sysflags + 3
764		else
765			write_line("sysent", string.format(
766			    "sys_%s, .sy_auevent = %s, .sy_flags = %s, .sy_thrcnt = %s },",
767			    funcname, auditev, sysflags, thr_flag))
768			column = column + #funcname + #auditev + #sysflags + 7
769		end
770	end
771
772	align_sysent_comment(column)
773	write_line("sysent", string.format("/* %d = %s */\n",
774	    sysnum, funcalias))
775	write_line("sysnames", string.format("\t\"%s\",\t\t\t/* %d = %s */\n",
776	    funcalias, sysnum, funcalias))
777
778	if flags & known_flags["NODEF"] == 0 then
779		write_line("syshdr", string.format("#define\t%s%s\t%d\n",
780		    config['syscallprefix'], funcalias, sysnum))
781		write_line("sysmk", string.format(" \\\n\t%s.o",
782		    funcalias))
783	end
784end
785
786local function handle_obsol(sysnum, funcname, comment)
787	write_line("sysent",
788	    "\t{ .sy_narg = 0, .sy_call = (sy_call_t *)nosys, " ..
789	    ".sy_auevent = AUE_NULL, .sy_flags = 0, .sy_thrcnt = SY_THR_ABSENT },")
790	align_sysent_comment(34)
791
792	write_line("sysent", string.format("/* %d = obsolete %s */\n",
793	    sysnum, comment))
794	write_line("sysnames", string.format(
795	    "\t\"obs_%s\",\t\t\t/* %d = obsolete %s */\n",
796	    funcname, sysnum, comment))
797	write_line("syshdr", string.format("\t\t\t\t/* %d is obsolete %s */\n",
798	    sysnum, comment))
799end
800
801local function handle_compat(sysnum, thr_flag, flags, sysflags, rettype,
802    auditev, funcname, funcalias, funcargs, argalias)
803	local argssize, out, outdcl, wrap, prefix, descr
804
805	if #funcargs > 0 or flags & known_flags["NODEF"] ~= 0 then
806		argssize = "AS(" .. argalias .. ")"
807	else
808		argssize = "0"
809	end
810
811	for _, v in pairs(compat_options) do
812		if flags & v["mask"] ~= 0 then
813			if config["mincompat"] > v["compatlevel"] then
814				funcname = strip_abi_prefix(funcname)
815				funcname = v["prefix"] .. funcname
816				return handle_obsol(sysnum, funcname, funcname)
817			end
818			v["count"] = v["count"] + 1
819			out = v["tmp"]
820			outdcl = v["dcltmp"]
821			wrap = v["flag"]:lower()
822			prefix = v["prefix"]
823			descr = v["descr"]
824			goto compatdone
825		end
826	end
827
828	::compatdone::
829	local dprotoflags = get_mask({"NOPROTO", "NODEF"})
830	local nargflags = dprotoflags | known_flags["NOARGS"]
831	if #funcargs > 0 and flags & nargflags == 0 then
832		write_line(out, string.format("struct %s {\n", argalias))
833		for _, v in ipairs(funcargs) do
834			local argname, argtype = v["name"], v["type"]
835			write_line(out, string.format(
836			    "\tchar %s_l_[PADL_(%s)]; %s %s; char %s_r_[PADR_(%s)];\n",
837			    argname, argtype,
838			    argtype, argname,
839			    argname, argtype))
840		end
841		write_line(out, "};\n")
842	elseif flags & nargflags == 0 then
843		write_line("sysarg", string.format(
844		    "struct %s {\n\tregister_t dummy;\n};\n", argalias))
845	end
846	if flags & dprotoflags == 0 then
847		write_line(outdcl, string.format(
848		    "%s\t%s%s(struct thread *, struct %s *);\n",
849		    rettype, prefix, funcname, argalias))
850		write_line("sysaue", string.format(
851		    "#define\t%sAUE_%s%s\t%s\n", config['syscallprefix'],
852		    prefix, funcname, auditev))
853	end
854
855	if flags & known_flags['NOSTD'] ~= 0 then
856		write_line("sysent", string.format(
857		    "\t{ .sy_narg = %s, .sy_call = (sy_call_t *)%s, " ..
858		    ".sy_auevent = %s, .sy_flags = 0, " ..
859		    ".sy_thrcnt = SY_THR_ABSENT },",
860		    "0", "lkmressys", "AUE_NULL"))
861		align_sysent_comment(8 + 2 + #"0" + 15 + #"lkmressys" +
862		    #"AUE_NULL" + 3)
863	else
864		write_line("sysent", string.format(
865		    "\t{ %s(%s,%s), .sy_auevent = %s, .sy_flags = %s, .sy_thrcnt = %s },",
866		    wrap, argssize, funcname, auditev, sysflags, thr_flag))
867		align_sysent_comment(8 + 9 + #argssize + 1 + #funcname +
868		    #auditev + #sysflags + 4)
869	end
870
871	write_line("sysent", string.format("/* %d = %s %s */\n",
872	    sysnum, descr, funcalias))
873	write_line("sysnames", string.format(
874	    "\t\"%s.%s\",\t\t/* %d = %s %s */\n",
875	    wrap, funcalias, sysnum, descr, funcalias))
876	-- Do not provide freebsdN_* symbols in libc for < FreeBSD 7
877	local nosymflags = get_mask({"COMPAT", "COMPAT4", "COMPAT6"})
878	if flags & nosymflags ~= 0 then
879		write_line("syshdr", string.format(
880		    "\t\t\t\t/* %d is %s %s */\n",
881		    sysnum, descr, funcalias))
882	elseif flags & known_flags["NODEF"] == 0 then
883		write_line("syshdr", string.format("#define\t%s%s%s\t%d\n",
884		    config['syscallprefix'], prefix, funcalias, sysnum))
885		write_line("sysmk", string.format(" \\\n\t%s%s.o",
886		    prefix, funcalias))
887	end
888end
889
890local function handle_unimpl(sysnum, sysstart, sysend, comment)
891	if sysstart == nil and sysend == nil then
892		sysstart = tonumber(sysnum)
893		sysend = tonumber(sysnum)
894	end
895
896	sysnum = sysstart
897	while sysnum <= sysend do
898		write_line("sysent", string.format(
899		    "\t{ .sy_narg = 0, .sy_call = (sy_call_t *)nosys, " ..
900		    ".sy_auevent = AUE_NULL, .sy_flags = 0, " ..
901		    ".sy_thrcnt = SY_THR_ABSENT },\t\t\t/* %d = %s */\n",
902		    sysnum, comment))
903		write_line("sysnames", string.format(
904		    "\t\"#%d\",\t\t\t/* %d = %s */\n",
905		    sysnum, sysnum, comment))
906		sysnum = sysnum + 1
907	end
908end
909
910local function handle_reserved(sysnum, sysstart, sysend, comment)
911	handle_unimpl(sysnum, sysstart, sysend, "reserved for local use")
912end
913
914process_syscall_def = function(line)
915	local sysstart, sysend, flags, funcname, sysflags
916	local thr_flag, syscallret
917	local orig = line
918	flags = 0
919	thr_flag = "SY_THR_STATIC"
920
921	-- Parse out the interesting information first
922	local initialExpr = "^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s*"
923	local sysnum, auditev, allflags = line:match(initialExpr)
924
925	if sysnum == nil or auditev == nil or allflags == nil then
926		-- XXX TODO: Better?
927		abort(1, "Completely malformed: " .. line)
928	end
929
930	if sysnum:find("-") then
931		sysstart, sysend = sysnum:match("^([%d]+)-([%d]+)$")
932		if sysstart == nil or sysend == nil then
933			abort(1, "Malformed range: " .. sysnum)
934		end
935		sysnum = nil
936		sysstart = tonumber(sysstart)
937		sysend = tonumber(sysend)
938		if sysstart ~= maxsyscall + 1 then
939			abort(1, "syscall number out of sync, missing " ..
940			    maxsyscall + 1)
941		end
942	else
943		sysnum = tonumber(sysnum)
944		if sysnum ~= maxsyscall + 1 then
945			abort(1, "syscall number out of sync, missing " ..
946			    maxsyscall + 1)
947		end
948	end
949
950	-- Split flags
951	for flag in allflags:gmatch("([^|]+)") do
952		if known_flags[flag] == nil then
953			abort(1, "Unknown flag " .. flag .. " for " ..  sysnum)
954		end
955		flags = flags | known_flags[flag]
956	end
957
958	if (flags & get_mask({"RESERVED", "UNIMPL"})) == 0 and sysnum == nil then
959		abort(1, "Range only allowed with RESERVED and UNIMPL: " .. line)
960	end
961
962	if (flags & known_flags["NOTSTATIC"]) ~= 0 then
963		thr_flag = "SY_THR_ABSENT"
964	end
965
966	-- Strip earlier bits out, leave declaration + alt
967	line = line:gsub("^.+" .. allflags .. "%s*", "")
968
969	local decl_fnd = line:find("^{") ~= nil
970	if decl_fnd and line:find("}") == nil then
971		abort(1, "Malformed, no closing brace: " .. line)
972	end
973
974	local decl, alt
975	if decl_fnd then
976		line = line:gsub("^{", "")
977		decl, alt = line:match("([^}]*)}[%s]*(.*)$")
978	else
979		alt = line
980	end
981
982	if decl == nil and alt == nil then
983		abort(1, "Malformed bits: " .. line)
984	end
985
986	local funcalias, funcomment, argalias, rettype, args
987	if not decl_fnd and alt ~= nil and alt ~= "" then
988		-- Peel off one entry for name
989		funcname = trim(alt:match("^([^%s]+)"), nil)
990		alt = alt:gsub("^([^%s]+)[%s]*", "")
991	end
992	-- Do we even need it?
993	if flags & get_mask({"OBSOL", "UNIMPL"}) ~= 0 then
994		local NF = 0
995		for _ in orig:gmatch("[^%s]+") do
996			NF = NF + 1
997		end
998
999		funcomment = funcname or ''
1000		if NF < 6 then
1001			funcomment = funcomment .. " " .. alt
1002		end
1003
1004		funcomment = trim(funcomment)
1005
1006--		if funcname ~= nil then
1007--		else
1008--			funcomment = trim(alt)
1009--		end
1010		goto skipalt
1011	end
1012
1013	if alt ~= nil and alt ~= "" then
1014		local altExpr = "^([^%s]+)%s+([^%s]+)%s+([^%s]+)"
1015		funcalias, argalias, rettype = alt:match(altExpr)
1016		funcalias = trim(funcalias)
1017		if funcalias == nil or argalias == nil or rettype == nil then
1018			abort(1, "Malformed alt: " .. line)
1019		end
1020	end
1021	if decl_fnd then
1022		-- Don't clobber rettype set in the alt information
1023		if rettype == nil then
1024			rettype = "int"
1025		end
1026		-- Peel off the return type
1027		syscallret = line:match("([^%s]+)%s")
1028		line = line:match("[^%s]+%s(.+)")
1029		-- Pointer incoming
1030		if line:sub(1,1) == "*" then
1031			syscallret = syscallret .. " "
1032		end
1033		while line:sub(1,1) == "*" do
1034			line = line:sub(2)
1035			syscallret = syscallret .. "*"
1036		end
1037		funcname = line:match("^([^(]+)%(")
1038		if funcname == nil then
1039			abort(1, "Not a signature? " .. line)
1040		end
1041		args = line:match("^[^(]+%((.+)%)[^)]*$")
1042		args = trim(args, '[,%s]')
1043	end
1044
1045	::skipalt::
1046
1047	if funcname == nil then
1048		funcname = funcalias
1049	end
1050
1051	funcname = trim(funcname)
1052
1053	sysflags = "0"
1054
1055	-- NODEF events do not get audited
1056	if flags & known_flags['NODEF'] ~= 0 then
1057		auditev = 'AUE_NULL'
1058	end
1059
1060	-- If applicable; strip the ABI prefix from the name
1061	local stripped_name = strip_abi_prefix(funcname)
1062
1063	if flags & known_flags['CAPENABLED'] ~= 0 or
1064	    config["capenabled"][funcname] ~= nil or
1065	    config["capenabled"][stripped_name] ~= nil then
1066		sysflags = "SYF_CAPENABLED"
1067	end
1068
1069	local funcargs = {}
1070	if args ~= nil then
1071		funcargs = process_args(args)
1072	end
1073
1074	local argprefix = ''
1075	if abi_changes("pointer_args") then
1076		for _, v in ipairs(funcargs) do
1077			if isptrtype(v["type"]) then
1078				-- argalias should be:
1079				--   COMPAT_PREFIX + ABI Prefix + funcname
1080				argprefix = config['abi_func_prefix']
1081				funcalias = config['abi_func_prefix'] ..
1082				    funcname
1083				goto ptrfound
1084			end
1085		end
1086		::ptrfound::
1087	end
1088	if funcalias == nil or funcalias == "" then
1089		funcalias = funcname
1090	end
1091
1092	if argalias == nil and funcname ~= nil then
1093		argalias = argprefix .. funcname .. "_args"
1094		for _, v in pairs(compat_options) do
1095			local mask = v["mask"]
1096			if (flags & mask) ~= 0 then
1097				-- Multiple aliases doesn't seem to make
1098				-- sense.
1099				argalias = v["prefix"] .. argalias
1100				goto out
1101			end
1102		end
1103		::out::
1104	elseif argalias ~= nil then
1105		argalias = argprefix .. argalias
1106	end
1107
1108	local ncompatflags = get_mask({"STD", "NODEF", "NOARGS", "NOPROTO",
1109	    "NOSTD"})
1110	local compatflags = get_mask_pat("COMPAT.*")
1111	-- Now try compat...
1112	if flags & compatflags ~= 0 then
1113		if flags & known_flags['STD'] ~= 0 then
1114			abort(1, "Incompatible COMPAT/STD: " .. line)
1115		end
1116		handle_compat(sysnum, thr_flag, flags, sysflags, rettype,
1117		    auditev, funcname, funcalias, funcargs, argalias)
1118	elseif flags & ncompatflags ~= 0 then
1119		handle_noncompat(sysnum, thr_flag, flags, sysflags, rettype,
1120		    auditev, syscallret, funcname, funcalias, funcargs,
1121		    argalias)
1122	elseif flags & known_flags["OBSOL"] ~= 0 then
1123		handle_obsol(sysnum, funcname, funcomment)
1124	elseif flags & known_flags["RESERVED"] ~= 0 then
1125		handle_reserved(sysnum, sysstart, sysend)
1126	elseif flags & known_flags["UNIMPL"] ~= 0 then
1127		handle_unimpl(sysnum, sysstart, sysend, funcomment)
1128	else
1129		abort(1, "Bad flags? " .. line)
1130	end
1131
1132	if sysend ~= nil then
1133		maxsyscall = sysend
1134	elseif sysnum ~= nil then
1135		maxsyscall = sysnum
1136	end
1137end
1138
1139-- Entry point
1140
1141if #arg < 1 or #arg > 2 then
1142	abort(1, "usage: " .. arg[0] .. " input-file <config-file>")
1143end
1144
1145local sysfile, configfile = arg[1], arg[2]
1146
1147-- process_config either returns nil and a message, or a
1148-- table that we should merge into the global config
1149if configfile ~= nil then
1150	local res, msg = process_config(configfile)
1151
1152	if res == nil then
1153		-- Error... handle?
1154		print(msg)
1155		os.exit(1)
1156	end
1157
1158	for k, v in pairs(res) do
1159		if v ~= config[k] then
1160			config[k] = v
1161			config_modified[k] = true
1162		end
1163	end
1164end
1165
1166-- We ignore errors here if we're relying on the default configuration.
1167if not config_modified["capenabled"] then
1168	config["capenabled"] = grab_capenabled(config['capabilities_conf'],
1169	    config_modified["capabilities_conf"] == nil)
1170elseif config["capenabled"] ~= "" then
1171	-- Due to limitations in the config format mostly, we'll have a comma
1172	-- separated list.  Parse it into lines
1173	local capenabled = {}
1174	-- print("here: " .. config["capenabled"])
1175	for sysc in config["capenabled"]:gmatch("([^,]+)") do
1176		capenabled[sysc] = true
1177	end
1178	config["capenabled"] = capenabled
1179end
1180process_compat()
1181process_abi_flags()
1182
1183if not lfs.mkdir(tmpspace) then
1184	abort(1, "Failed to create tempdir " .. tmpspace)
1185end
1186
1187for _, v in ipairs(temp_files) do
1188	local tmpname = tmpspace .. v
1189	files[v] = io.open(tmpname, "w+")
1190end
1191
1192for _, v in ipairs(output_files) do
1193	local tmpname = tmpspace .. v
1194	files[v] = io.open(tmpname, "w+")
1195end
1196
1197-- Write out all of the preamble bits
1198write_line("sysent", string.format([[
1199
1200/* The casts are bogus but will do for now. */
1201struct sysent %s[] = {
1202]], config['switchname']))
1203
1204write_line("syssw", string.format([[/*
1205 * System call switch table.
1206 *
1207 * DO NOT EDIT-- this file is automatically %s.
1208 * $%s$
1209 */
1210
1211]], generated_tag, config['os_id_keyword']))
1212
1213write_line("sysarg", string.format([[/*
1214 * System call prototypes.
1215 *
1216 * DO NOT EDIT-- this file is automatically %s.
1217 * $%s$
1218 */
1219
1220#ifndef %s
1221#define	%s
1222
1223#include <sys/signal.h>
1224#include <sys/acl.h>
1225#include <sys/cpuset.h>
1226#include <sys/domainset.h>
1227#include <sys/_ffcounter.h>
1228#include <sys/_semaphore.h>
1229#include <sys/ucontext.h>
1230#include <sys/wait.h>
1231
1232#include <bsm/audit_kevents.h>
1233
1234struct proc;
1235
1236struct thread;
1237
1238#define	PAD_(t)	(sizeof(register_t) <= sizeof(t) ? \
1239		0 : sizeof(register_t) - sizeof(t))
1240
1241#if BYTE_ORDER == LITTLE_ENDIAN
1242#define	PADL_(t)	0
1243#define	PADR_(t)	PAD_(t)
1244#else
1245#define	PADL_(t)	PAD_(t)
1246#define	PADR_(t)	0
1247#endif
1248
1249]], generated_tag, config['os_id_keyword'], config['sysproto_h'],
1250    config['sysproto_h']))
1251for _, v in pairs(compat_options) do
1252	write_line(v["tmp"], string.format("\n#ifdef %s\n\n", v["definition"]))
1253end
1254
1255write_line("sysnames", string.format([[/*
1256 * System call names.
1257 *
1258 * DO NOT EDIT-- this file is automatically %s.
1259 * $%s$
1260 */
1261
1262const char *%s[] = {
1263]], generated_tag, config['os_id_keyword'], config['namesname']))
1264
1265write_line("syshdr", string.format([[/*
1266 * System call numbers.
1267 *
1268 * DO NOT EDIT-- this file is automatically %s.
1269 * $%s$
1270 */
1271
1272]], generated_tag, config['os_id_keyword']))
1273
1274write_line("sysmk", string.format([[# FreeBSD system call object files.
1275# DO NOT EDIT-- this file is automatically %s.
1276# $%s$
1277MIASM = ]], generated_tag, config['os_id_keyword']))
1278
1279write_line("systrace", string.format([[/*
1280 * System call argument to DTrace register array converstion.
1281 *
1282 * DO NOT EDIT-- this file is automatically %s.
1283 * $%s$
1284 * This file is part of the DTrace syscall provider.
1285 */
1286
1287static void
1288systrace_args(int sysnum, void *params, uint64_t *uarg, int *n_args)
1289{
1290	int64_t *iarg = (int64_t *)uarg;
1291	switch (sysnum) {
1292]], generated_tag, config['os_id_keyword']))
1293
1294write_line("systracetmp", [[static void
1295systrace_entry_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
1296{
1297	const char *p = NULL;
1298	switch (sysnum) {
1299]])
1300
1301write_line("systraceret", [[static void
1302systrace_return_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
1303{
1304	const char *p = NULL;
1305	switch (sysnum) {
1306]])
1307
1308-- Processing the sysfile will parse out the preprocessor bits and put them into
1309-- the appropriate place.  Any syscall-looking lines get thrown into the sysfile
1310-- buffer, one per line, for later processing once they're all glued together.
1311process_sysfile(sysfile)
1312
1313write_line("sysinc",
1314    "\n#define AS(name) (sizeof(struct name) / sizeof(register_t))\n")
1315
1316for _, v in pairs(compat_options) do
1317	if v["count"] > 0 then
1318		write_line("sysinc", string.format([[
1319
1320#ifdef %s
1321#define %s(n, name) .sy_narg = n, .sy_call = (sy_call_t *)__CONCAT(%s, name)
1322#else
1323#define %s(n, name) .sy_narg = 0, .sy_call = (sy_call_t *)nosys
1324#endif
1325]], v["definition"], v["flag"]:lower(), v["prefix"], v["flag"]:lower()))
1326	end
1327
1328	write_line(v["dcltmp"], string.format("\n#endif /* %s */\n\n",
1329	    v["definition"]))
1330end
1331
1332write_line("sysprotoend", string.format([[
1333
1334#undef PAD_
1335#undef PADL_
1336#undef PADR_
1337
1338#endif /* !%s */
1339]], config["sysproto_h"]))
1340
1341write_line("sysmk", "\n")
1342write_line("sysent", "};\n")
1343write_line("sysnames", "};\n")
1344-- maxsyscall is the highest seen; MAXSYSCALL should be one higher
1345write_line("syshdr", string.format("#define\t%sMAXSYSCALL\t%d\n",
1346    config["syscallprefix"], maxsyscall + 1))
1347write_line("systrace", [[
1348	default:
1349		*n_args = 0;
1350		break;
1351	};
1352}
1353]])
1354
1355write_line("systracetmp", [[
1356	default:
1357		break;
1358	};
1359	if (p != NULL)
1360		strlcpy(desc, p, descsz);
1361}
1362]])
1363
1364write_line("systraceret", [[
1365	default:
1366		break;
1367	};
1368	if (p != NULL)
1369		strlcpy(desc, p, descsz);
1370}
1371]])
1372
1373-- Finish up; output
1374write_line("syssw", read_file("sysinc"))
1375write_line("syssw", read_file("sysent"))
1376
1377write_line("sysproto", read_file("sysarg"))
1378write_line("sysproto", read_file("sysdcl"))
1379for _, v in pairs(compat_options) do
1380	write_line("sysproto", read_file(v["tmp"]))
1381	write_line("sysproto", read_file(v["dcltmp"]))
1382end
1383write_line("sysproto", read_file("sysaue"))
1384write_line("sysproto", read_file("sysprotoend"))
1385
1386write_line("systrace", read_file("systracetmp"))
1387write_line("systrace", read_file("systraceret"))
1388
1389for _, v in ipairs(output_files) do
1390	local target = config[v]
1391	if target ~= "/dev/null" then
1392		local fh = io.open(target, "w+")
1393		if fh == nil then
1394			abort(1, "Failed to open '" .. target .. "'")
1395		end
1396		fh:write(read_file(v))
1397		fh:close()
1398	end
1399end
1400
1401cleanup()
1402