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