xref: /freebsd/sys/tools/syscalls/core/syscall.lua (revision 202ac0975edcc4729e7016a9e9cb921de45e3a70)
1--
2-- SPDX-License-Identifier: BSD-2-Clause
3--
4-- Copyright (c) 2024 Tyler Baxter <agge@FreeBSD.org>
5-- Copyright (c) 2023 Warner Losh <imp@bsdimp.com>
6-- Copyright (c) 2019 Kyle Evans <kevans@FreeBSD.org>
7--
8
9local config = require("config")
10local scarg = require("core.scarg")
11local scret = require("core.scret")
12local util = require("tools.util")
13
14local syscall = {}
15
16syscall.__index = syscall
17
18syscall.known_flags = util.set {
19	"STD",
20	"OBSOL",
21	"RESERVED",
22	"UNIMPL",
23	"NODEF",
24	"NOARGS",
25	"NOPROTO",
26	"NOSTD",
27
28	-- flags beyond this point are modifiers
29	"CAPENABLED",
30	"NOLIB",
31	"NORETURN",
32	"NOTSTATIC",
33	"SYSMUX",
34}
35
36-- Native is an arbitrarily large number to have a constant and not
37-- interfere with compat numbers.
38local native = 1000000
39
40-- Processes and assigns the appropriate thread flag for this system call.
41function syscall:processThr()
42	self.thr = "SY_THR_STATIC"
43	for k, _ in pairs(self.type) do
44		if k == "NOTSTATIC" then
45			self.thr = "SY_THR_ABSENT"
46		end
47	end
48end
49
50-- Processes and assigns the appropriate capability flag for this system call.
51-- "SYF_CAPENABLED" for capability enabled; "0" for NOT capability enabled.
52function syscall:processCap()
53	self.cap = "0"
54	local stripped = util.stripAbiPrefix(self.name, self.prefix)
55	for k, _ in pairs(self.type) do
56		if k == "CAPENABLED" then
57			self.cap = "SYF_CAPENABLED"
58		end
59	end
60end
61
62-- Check that this system call has a known type.
63local function checkType(type)
64	for k, _ in pairs(type) do
65		if not syscall.known_flags[k] and not
66			k:match("^COMPAT") then
67			util.abort(1, "Bad type: " .. k)
68		end
69	end
70end
71
72-- If there are ABI changes from native, process this system call to match the
73-- target ABI.
74function syscall:processChangesAbi()
75	-- First, confirm we want to uphold our changes_abi flag.
76	if config.syscall_no_abi_change[self.name] then
77		self.changes_abi = false
78	end
79	self.noproto = not util.isEmpty(config.abi_flags) and
80	    not self.changes_abi
81	if config.abiChanges("pointer_args") then
82		for _, v in ipairs(self.args) do
83			if util.isPtrType(v.type, config.abi_intptr_t) then
84				if config.syscall_no_abi_change[self.name] then
85					print("WARNING: " .. self.name ..
86					    " in syscall_no_abi_change, " ..
87					    "but pointers args are present")
88				end
89				self.changes_abi = true
90				goto ptrfound
91			end
92		end
93		::ptrfound::
94	end
95	if config.syscall_abi_change[self.name] then
96		self.changes_abi = true
97	end
98	if self.changes_abi then
99		self.noproto = false
100	end
101end
102
103-- Final processing of flags. Process any flags that haven't already been
104-- processed (e.g., dictionaries from syscalls.conf).
105function syscall:processFlags()
106	if config.obsol[self.name] or (self:compatLevel() > 0 and
107	    self:compatLevel() < tonumber(config.mincompat)) then
108		self.args = nil
109		self.type.OBSOL = true
110		-- Don't apply any ABI handling, declared as obsolete.
111		self.changes_abi = false
112	end
113	if config.unimpl[self.name] then
114		self.type.UNIMPL = true
115	end
116	if self.noproto or self.type.SYSMUX then
117		self.type.NOPROTO = true
118	end
119	if self.type.NODEF then
120		self.audit = "AUE_NULL"
121	end
122end
123
124-- Returns TRUE if prefix and arg_prefix are assigned; FALSE if they're left
125-- unassigned.  Relies on a valid changes_abi flag, so should be called AFTER
126-- processChangesAbi().
127function syscall:processPrefix()
128	-- If there are ABI changes from native, assign the correct prefixes.
129	if self.changes_abi then
130		self.arg_prefix = config.abi_func_prefix
131		self.prefix = config.abi_func_prefix
132		return true
133	end
134	return false
135end
136
137-- Validate that we're not skipping system calls by comparing this system call
138-- number to the previous system call number.  Called higher up the call stack
139-- by class FreeBSDSyscall.
140function syscall:validate(prev)
141	return prev + 1 == self.num
142end
143
144-- Return the compat prefix for this system call.
145function syscall:compatPrefix()
146	local c = self:compatLevel()
147	if self.type.OBSOL then
148		return "obs_"
149	end
150	if self.type.RESERVED then
151		return "reserved #"
152	end
153	if self.type.UNIMPL then
154		return "unimp_"
155	end
156	if c == 3 then
157		return "o"
158	end
159	if c < native then
160		return "freebsd" .. tostring(c) .. "_"
161	end
162	return ""
163end
164
165-- Return the symbol name for this system call.
166function syscall:symbol()
167	return self:compatPrefix() .. self.name
168end
169
170--
171-- Return the compatibility level for this system call.
172-- 	0 is obsolete.
173-- 	< 0 is this isn't really a system call we care about.
174-- 	3 is 4.3BSD in theory, but anything before FreeBSD 4.
175-- 	>= 4 is FreeBSD version, this system call was replaced with a new
176-- 	    version.
177--
178function syscall:compatLevel()
179	if self.type.UNIMPL or self.type.RESERVED then
180		return -1
181	elseif self.type.OBSOL then
182		return 0
183	elseif self.type.COMPAT then
184		return 3
185	end
186	for k, _ in pairs(self.type) do
187		local l = k:match("^COMPAT(%d+)")
188		if l ~= nil then
189			return tonumber(l)
190		end
191	end
192	return native
193end
194
195-- Adds the definition for this system call. Guarded by whether we already have
196-- a system call number or not.
197function syscall:addDef(line)
198	if self.num == nil then
199		local words = util.split(line, "%S+")
200		self.num = words[1]
201		self.audit = words[2]
202		self.type = util.setFromString(words[3], "[^|]+")
203		checkType(self.type)
204		self.name = words[4]
205		-- These next three are optional, and either all present
206		-- or all absent.
207		self.altname = words[5]
208		self.alttag = words[6]
209		self.rettype = words[7]
210		return true
211	end
212	return false
213end
214
215-- Adds the function declaration for this system call. If addDef() found an
216-- opening curly brace, then we're looking for a function declaration.
217function syscall:addFunc(line)
218	if self.name == "{" then
219		local words = util.split(line, "%S+")
220		-- Expect line is `type syscall(` or `type syscall(void);`.
221		if #words ~= 2 then
222			util.abort(1, "Malformed line " .. line)
223		end
224
225		local ret = scret:new({}, line)
226		self.ret = ret:add()
227		-- Don't clobber rettype set in the alt information.
228		if self.rettype == nil then
229			self.rettype = "int"
230		end
231
232		self.name = words[2]:match("([%w_]+)%(")
233		if words[2]:match("%);$") then
234			-- Now we're looking for ending curly brace.
235			self.expect_rbrace = true
236		end
237		return true
238	end
239	return false
240end
241
242-- Adds the argument(s) for this system call. Once addFunc() assigns a name
243-- for this system call, arguments are next in syscalls.master.
244function syscall:addArgs(line)
245	if not self.expect_rbrace then
246		if line:match("%);$") then
247			self.expect_rbrace = true
248			return true
249		end
250		local arg = scarg:new({}, line)
251		-- We don't want to add this argument if it doesn't process.
252		-- scarg:process() handles those conditions.
253		if arg:process() then
254			arg:append(self.args)
255		end
256		-- If this argument has ABI changes, set globally for this
257		-- system call.
258		self.changes_abi = self.changes_abi or arg:changesAbi()
259		return true
260	end
261	return false
262end
263
264-- Once we have a good syscall, add some final information to it.
265function syscall:finalize()
266	if self.name == nil then
267		self.name = ""
268	end
269
270	-- Preserve the original name as the alias.
271	self.alias = self.name
272
273	self:processChangesAbi()	-- process changes to the ABI
274	self:processFlags()		-- process any unprocessed flags
275
276	-- If there's changes to the ABI, these prefixes will be changed by
277	-- processPrefix(); otherwise, they'll remain empty.
278	self.prefix = ""
279	self.arg_prefix = ""
280	self:processPrefix()
281
282	self:processCap()	-- capability flag
283	self:processThr()	-- thread flag
284
285	-- Assign argument alias.
286	if self.alttag ~= nil then
287		self.arg_alias = self.alttag
288	elseif self.arg_alias == nil and self.name ~= nil then
289		-- argalias should be:
290		--   COMPAT_PREFIX + ABI Prefix + funcname
291		self.arg_alias = self:compatPrefix() .. self.arg_prefix ..
292		    self.name .. "_args"
293	elseif self.arg_alias ~= nil then
294		self.arg_alias = self.arg_prefix .. self.arg_alias
295	end
296
297	-- An empty string would not want a prefix; the entry doesn't have
298	-- a name so we want to keep the empty string.
299	if self.name ~= nil and self.name ~= "" then
300		self.name = self.prefix .. self.name
301	end
302
303	self:processArgstrings()
304	self:processArgsize()
305end
306
307-- Assigns the correct args_size. Defaults to "0", except if there's arguments
308-- or NODEF flag.
309function syscall:processArgsize()
310	if self.type.SYSMUX then	-- catch this first
311		self.args_size = "0"
312	elseif self.arg_alias ~= nil and
313	    (#self.args ~= 0 or self.type.NODEF) then
314		self.args_size = "AS(" .. self.arg_alias .. ")"
315	else
316		self.args_size = "0"
317	end
318end
319
320-- Constructs argstr_* strings for generated declerations/wrappers.
321function syscall:processArgstrings()
322	local type = ""
323	local type_var = ""
324	local var = ""
325	local comma = ""
326
327	for _, v in ipairs(self.args) do
328		local argname, argtype = v.name, v.type
329		type = type .. comma .. argtype
330		type_var = type_var .. comma .. argtype ..  " " .. argname
331		var = var .. comma .. argname
332		comma = ", "
333	end
334	if type == "" then
335		type = "void"
336		type_var = "void"
337	end
338
339	self.argstr_type = type
340	self.argstr_type_var = type_var
341	self.argstr_var = var
342end
343
344-- Interface to add this system call to the master system call table.
345-- The system call is built up one line at a time. The states describe the
346-- current parsing state.
347-- Returns TRUE when ready to add and FALSE while still parsing.
348function syscall:add(line)
349	if self:addDef(line) then
350		return self:isAdded(line)
351	end
352	if self:addFunc(line) then
353		return false -- Function added; keep going.
354	end
355	if self:addArgs(line) then
356		return false -- Arguments added; keep going.
357	end
358	return self:isAdded(line) -- Final validation, before adding.
359end
360
361-- Returns TRUE if this system call was succesfully added. There's two entry
362-- points to this function: (1) the entry in syscalls.master is one-line, or
363-- (2) the entry is a full system call. This function handles those cases and
364-- decides whether to exit early for (1) or validate a full system call for
365-- (2).  This function also handles cases where we don't want to add, and
366-- instead want to abort.
367function syscall:isAdded(line)
368	-- This system call is a range - exit early.
369	if tonumber(self.num) == nil then
370		-- The only allowed types are RESERVED and UNIMPL.
371		if not (self.type.RESERVED or self.type.UNIMPL) then
372			util.abort(1, "Range only allowed with RESERVED " ..
373			    "and UNIMPL: " ..  line)
374		end
375		self:finalize()
376		return true
377	-- This system call is a loadable system call - exit early.
378	elseif self.altname ~= nil and self.alttag ~= nil and
379		   self.rettype ~= nil then
380		self:finalize()
381		return true
382	-- This system call is only one line, and should only be one line
383	-- (we didn't make it to addFunc()) - exit early.
384	elseif self.name ~= "{"  and self.ret == nil then
385		self:finalize()
386		return true
387	-- This is a full system call and we've passed multiple states to
388	-- get here - final exit.
389	elseif self.expect_rbrace then
390		if not line:match("}$") then
391			util.abort(1, "Expected '}' found '" .. line ..
392			    "' instead.")
393		end
394		self:finalize()
395		return true
396	end
397	return false
398end
399
400-- Return TRUE if this system call is native.
401function syscall:native()
402	return self:compatLevel() == native
403end
404
405-- Make a shallow copy of `self` and replace the system call number with num
406-- (which should be a number).
407-- For system call ranges.
408function syscall:shallowCopy(num)
409	local obj = syscall:new()
410
411	-- shallow copy
412	for k, v in pairs(self) do
413		obj[k] = v
414	end
415	obj.num = num	-- except override range
416	return obj
417end
418
419-- Make a deep copy of the parameter object. Save copied tables in `copies`,
420-- indexed by original table.
421-- CREDIT: http://lua-users.org/wiki/CopyTable
422-- For a full system call (the nested arguments table should be a deep copy).
423local function deepCopy(orig, copies)
424	copies = copies or {}
425	local orig_type = type(orig)
426	local copy
427	if orig_type == 'table' then
428		if copies[orig] then
429			copy = copies[orig]
430		else
431			copy = {}
432			copies[orig] = copy
433			for orig_key, orig_value in next, orig, nil do
434				copy[deepCopy(orig_key, copies)] =
435				    deepCopy(orig_value, copies)
436			end
437			setmetatable(copy, deepCopy(getmetatable(orig), copies))
438		end
439	else -- number, string, boolean, etc
440		copy = orig
441	end
442	return copy
443end
444
445--
446-- In syscalls.master, system calls come in two types: (1) a fully defined
447-- system call with function declaration, with a distinct number for each system
448-- call; or (2) a one-line entry, sometimes with a distinct number and sometimes
449-- with a range of numbers. One-line entries can be obsolete, reserved, no
450-- definition, etc. Ranges are only allowed for reserved and unimplemented.
451--
452-- This function provides the iterator to traverse system calls by number. If
453-- the entry is a fully defined system call with a distinct number, the iterator
454-- creates a deep copy and captures any nested objects; if the entry is a range
455-- of numbers, the iterator creates shallow copies from the start of the range
456-- to the end of the range.
457--
458function syscall:iter()
459	local s = tonumber(self.num)
460	local e
461	if s == nil then
462		s, e = string.match(self.num, "(%d+)%-(%d+)")
463		s, e = tonumber(s), tonumber(e)
464		return function ()
465			if s <= e then
466				s = s + 1
467				return self:shallowCopy(s - 1)
468			end
469		end
470	else
471		e = s
472		self.num = s	-- Replace string with number, like the clones.
473		return function ()
474			if s == e then
475				local deep_copy = deepCopy(self)
476				s = e + 1
477				return deep_copy
478			end
479		end
480	end
481end
482
483function syscall:new(obj)
484	obj = obj or { }
485	setmetatable(obj, self)
486	self.__index = self
487
488	self.expect_rbrace = false
489	self.changes_abi = false
490	self.args = {}
491	self.noproto = false
492
493	return obj
494end
495
496return syscall
497