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