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