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