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