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