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