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