]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/tools/makesyscalls.lua
Spacing and alignment cleanups.
[FreeBSD/FreeBSD.git] / sys / tools / makesyscalls.lua
1 --
2 -- SPDX-License-Identifier: BSD-2-Clause
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
28
29 -- We generally assume that this script will be run by flua, however we've
30 -- carefully crafted modules for it that mimic interfaces provided by modules
31 -- available in ports.  Currently, this script is compatible with lua from ports
32 -- along with the compatible luafilesystem and lua-posix modules.
33 local lfs = require("lfs")
34 local unistd = require("posix.unistd")
35
36 local savesyscall = -1
37 local maxsyscall = -1
38 local generated_tag = "@" .. "generated"
39
40 -- Default configuration; any of these may get replaced by a configuration file
41 -- optionally specified.
42 local config = {
43         os_id_keyword = "FreeBSD",              -- obsolete, ignored on input, not generated
44         abi_func_prefix = "",
45         sysnames = "syscalls.c",
46         sysproto = "../sys/sysproto.h",
47         sysproto_h = "_SYS_SYSPROTO_H_",
48         syshdr = "../sys/syscall.h",
49         sysmk = "../sys/syscall.mk",
50         syssw = "init_sysent.c",
51         syscallprefix = "SYS_",
52         switchname = "sysent",
53         namesname = "syscallnames",
54         systrace = "systrace_args.c",
55         capabilities_conf = "capabilities.conf",
56         capenabled = {},
57         mincompat = 0,
58         abi_type_suffix = "",
59         abi_flags = "",
60         abi_flags_mask = 0,
61         ptr_intptr_t_cast = "intptr_t",
62 }
63
64 local config_modified = {}
65 local cleantmp = true
66 local tmpspace = "/tmp/sysent." .. unistd.getpid() .. "/"
67
68 local output_files = {
69         "sysnames",
70         "syshdr",
71         "sysmk",
72         "syssw",
73         "systrace",
74         "sysproto",
75 }
76
77 -- These ones we'll create temporary files for; generation purposes.
78 local temp_files = {
79         "sysaue",
80         "sysdcl",
81         "syscompat",
82         "syscompatdcl",
83         "sysent",
84         "sysinc",
85         "sysarg",
86         "sysprotoend",
87         "systracetmp",
88         "systraceret",
89 }
90
91 -- Opened files
92 local files = {}
93
94 local function cleanup()
95         for _, v in pairs(files) do
96                 assert(v:close())
97         end
98         if cleantmp then
99                 if lfs.dir(tmpspace) then
100                         for fname in lfs.dir(tmpspace) do
101                                 if fname ~= "." and fname ~= ".." then
102                                         assert(os.remove(tmpspace .. "/" ..
103                                             fname))
104                                 end
105                         end
106                 end
107
108                 if lfs.attributes(tmpspace) and not lfs.rmdir(tmpspace) then
109                         assert(io.stderr:write("Failed to clean up tmpdir: " ..
110                             tmpspace .. "\n"))
111                 end
112         else
113                 assert(io.stderr:write("Temp files left in " .. tmpspace ..
114                     "\n"))
115         end
116 end
117
118 local function abort(status, msg)
119         assert(io.stderr:write(msg .. "\n"))
120         cleanup()
121         os.exit(status)
122 end
123
124 -- Each entry should have a value so we can represent abi flags as a bitmask
125 -- for convenience.  One may also optionally provide an expr; this gets applied
126 -- to each argument type to indicate whether this argument is subject to ABI
127 -- change given the configured flags.
128 local known_abi_flags = {
129         long_size = {
130                 value   = 0x00000001,
131                 expr    = "_Contains[a-z_]*_long_",
132         },
133         time_t_size = {
134                 value   = 0x00000002,
135                 expr    = "_Contains[a-z_]*_timet_/",
136         },
137         pointer_args = {
138                 value   = 0x00000004,
139         },
140         pointer_size = {
141                 value   = 0x00000008,
142                 expr    = "_Contains[a-z_]*_ptr_",
143         },
144 }
145
146 local known_flags = {
147         STD             = 0x00000001,
148         OBSOL           = 0x00000002,
149         RESERVED        = 0x00000004,
150         UNIMPL          = 0x00000008,
151         NODEF           = 0x00000010,
152         NOARGS          = 0x00000020,
153         NOPROTO         = 0x00000040,
154         NOSTD           = 0x00000080,
155         NOTSTATIC       = 0x00000100,
156         CAPENABLED      = 0x00000200,
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.
172 local 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
188 local 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 .. "+$", "")
196 end
197
198 -- We have to io.popen it, making sure it's properly escaped, and grab the
199 -- output from the handle returned.
200 local 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
209 end
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
214 local 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
295 end
296
297 local 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
324 end
325
326 local 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
358 end
359
360 local 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
371 end
372
373 local 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
379 end
380
381 local 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
391 end
392
393 local 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"))
402 end
403
404 local 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))
410 end
411
412 local 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
418 end
419
420 local function isptrtype(type)
421         return type:find("*") or type:find("caddr_t")
422             -- XXX NOTYET: or type:find("intptr_t")
423 end
424
425 local process_syscall_def
426
427 -- These patterns are processed in order on any line that isn't empty.
428 local pattern_table = {
429         {
430                 -- To be removed soon
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
489 local 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         assert(io.close(fh))
538         return capentries
539 end
540
541 local 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
552 end
553
554 local 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
563 end
564
565 local 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
572 end
573
574 local function strip_arg_annotations(arg)
575         arg = arg:gsub("_In[^ ]*[_)] ?", "")
576         arg = arg:gsub("_Out[^ ]*[_)] ?", "")
577         return trim(arg)
578 end
579
580 local 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
589 end
590
591 local 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
626 end
627
628 local 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
784 end
785
786 local 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))
799 end
800
801 local 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
888 end
889
890 local 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
908 end
909
910 local function handle_reserved(sysnum, sysstart, sysend)
911         handle_unimpl(sysnum, sysstart, sysend, "reserved for local use")
912 end
913
914 process_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
1137 end
1138
1139 -- Entry point
1140
1141 if #arg < 1 or #arg > 2 then
1142         error("usage: " .. arg[0] .. " input-file <config-file>")
1143 end
1144
1145 local 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
1149 if configfile ~= nil then
1150         local res = assert(process_config(configfile))
1151
1152         for k, v in pairs(res) do
1153                 if v ~= config[k] then
1154                         config[k] = v
1155                         config_modified[k] = true
1156                 end
1157         end
1158 end
1159
1160 -- We ignore errors here if we're relying on the default configuration.
1161 if not config_modified.capenabled then
1162         config.capenabled = grab_capenabled(config.capabilities_conf,
1163             config_modified.capabilities_conf == nil)
1164 elseif config.capenabled ~= "" then
1165         -- Due to limitations in the config format mostly, we'll have a comma
1166         -- separated list.  Parse it into lines
1167         local capenabled = {}
1168         -- print("here: " .. config.capenabled)
1169         for sysc in config.capenabled:gmatch("([^,]+)") do
1170                 capenabled[sysc] = true
1171         end
1172         config.capenabled = capenabled
1173 end
1174 process_compat()
1175 process_abi_flags()
1176
1177 if not lfs.mkdir(tmpspace) then
1178         error("Failed to create tempdir " .. tmpspace)
1179 end
1180
1181 -- XXX Revisit the error handling here, we should probably move the rest of this
1182 -- into a function that we pcall() so we can catch the errors and clean up
1183 -- gracefully.
1184 for _, v in ipairs(temp_files) do
1185         local tmpname = tmpspace .. v
1186         files[v] = io.open(tmpname, "w+")
1187         -- XXX Revisit these with a pcall() + error handler
1188         if not files[v] then
1189                 abort(1, "Failed to open temp file: " .. tmpname)
1190         end
1191 end
1192
1193 for _, v in ipairs(output_files) do
1194         local tmpname = tmpspace .. v
1195         files[v] = io.open(tmpname, "w+")
1196         -- XXX Revisit these with a pcall() + error handler
1197         if not files[v] then
1198                 abort(1, "Failed to open temp output file: " .. tmpname)
1199         end
1200 end
1201
1202 -- Write out all of the preamble bits
1203 write_line("sysent", string.format([[
1204
1205 /* The casts are bogus but will do for now. */
1206 struct sysent %s[] = {
1207 ]], config.switchname))
1208
1209 write_line("syssw", string.format([[/*
1210  * System call switch table.
1211  *
1212  * DO NOT EDIT-- this file is automatically %s.
1213  */
1214
1215 ]], generated_tag))
1216
1217 write_line("sysarg", string.format([[/*
1218  * System call prototypes.
1219  *
1220  * DO NOT EDIT-- this file is automatically %s.
1221  */
1222
1223 #ifndef %s
1224 #define %s
1225
1226 #include <sys/signal.h>
1227 #include <sys/acl.h>
1228 #include <sys/cpuset.h>
1229 #include <sys/domainset.h>
1230 #include <sys/_ffcounter.h>
1231 #include <sys/_semaphore.h>
1232 #include <sys/ucontext.h>
1233 #include <sys/wait.h>
1234
1235 #include <bsm/audit_kevents.h>
1236
1237 struct proc;
1238
1239 struct thread;
1240
1241 #define PAD_(t) (sizeof(register_t) <= sizeof(t) ? \
1242                 0 : sizeof(register_t) - sizeof(t))
1243
1244 #if BYTE_ORDER == LITTLE_ENDIAN
1245 #define PADL_(t)        0
1246 #define PADR_(t)        PAD_(t)
1247 #else
1248 #define PADL_(t)        PAD_(t)
1249 #define PADR_(t)        0
1250 #endif
1251
1252 ]], generated_tag, config.sysproto_h,
1253     config.sysproto_h))
1254 for _, v in pairs(compat_options) do
1255         write_line(v.tmp, string.format("\n#ifdef %s\n\n", v.definition))
1256 end
1257
1258 write_line("sysnames", string.format([[/*
1259  * System call names.
1260  *
1261  * DO NOT EDIT-- this file is automatically %s.
1262  */
1263
1264 const char *%s[] = {
1265 ]], generated_tag, config.namesname))
1266
1267 write_line("syshdr", string.format([[/*
1268  * System call numbers.
1269  *
1270  * DO NOT EDIT-- this file is automatically %s.
1271  */
1272
1273 ]], generated_tag))
1274
1275 write_line("sysmk", string.format([[# FreeBSD system call object files.
1276 # DO NOT EDIT-- this file is automatically %s.
1277 MIASM = ]], generated_tag))
1278
1279 write_line("systrace", string.format([[/*
1280  * System call argument to DTrace register array converstion.
1281  *
1282  * DO NOT EDIT-- this file is automatically %s.
1283  * This file is part of the DTrace syscall provider.
1284  */
1285
1286 static void
1287 systrace_args(int sysnum, void *params, uint64_t *uarg, int *n_args)
1288 {
1289         int64_t *iarg = (int64_t *)uarg;
1290         switch (sysnum) {
1291 ]], generated_tag))
1292
1293 write_line("systracetmp", [[static void
1294 systrace_entry_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
1295 {
1296         const char *p = NULL;
1297         switch (sysnum) {
1298 ]])
1299
1300 write_line("systraceret", [[static void
1301 systrace_return_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
1302 {
1303         const char *p = NULL;
1304         switch (sysnum) {
1305 ]])
1306
1307 -- Processing the sysfile will parse out the preprocessor bits and put them into
1308 -- the appropriate place.  Any syscall-looking lines get thrown into the sysfile
1309 -- buffer, one per line, for later processing once they're all glued together.
1310 process_sysfile(sysfile)
1311
1312 write_line("sysinc",
1313     "\n#define AS(name) (sizeof(struct name) / sizeof(register_t))\n")
1314
1315 for _, v in pairs(compat_options) do
1316         if v.count > 0 then
1317                 write_line("sysinc", string.format([[
1318
1319 #ifdef %s
1320 #define %s(n, name) .sy_narg = n, .sy_call = (sy_call_t *)__CONCAT(%s, name)
1321 #else
1322 #define %s(n, name) .sy_narg = 0, .sy_call = (sy_call_t *)nosys
1323 #endif
1324 ]], v.definition, v.flag:lower(), v.prefix, v.flag:lower()))
1325         end
1326
1327         write_line(v.dcltmp, string.format("\n#endif /* %s */\n\n",
1328             v.definition))
1329 end
1330
1331 write_line("sysprotoend", string.format([[
1332
1333 #undef PAD_
1334 #undef PADL_
1335 #undef PADR_
1336
1337 #endif /* !%s */
1338 ]], config.sysproto_h))
1339
1340 write_line("sysmk", "\n")
1341 write_line("sysent", "};\n")
1342 write_line("sysnames", "};\n")
1343 -- maxsyscall is the highest seen; MAXSYSCALL should be one higher
1344 write_line("syshdr", string.format("#define\t%sMAXSYSCALL\t%d\n",
1345     config.syscallprefix, maxsyscall + 1))
1346 write_line("systrace", [[
1347         default:
1348                 *n_args = 0;
1349                 break;
1350         };
1351 }
1352 ]])
1353
1354 write_line("systracetmp", [[
1355         default:
1356                 break;
1357         };
1358         if (p != NULL)
1359                 strlcpy(desc, p, descsz);
1360 }
1361 ]])
1362
1363 write_line("systraceret", [[
1364         default:
1365                 break;
1366         };
1367         if (p != NULL)
1368                 strlcpy(desc, p, descsz);
1369 }
1370 ]])
1371
1372 -- Finish up; output
1373 write_line("syssw", read_file("sysinc"))
1374 write_line("syssw", read_file("sysent"))
1375
1376 write_line("sysproto", read_file("sysarg"))
1377 write_line("sysproto", read_file("sysdcl"))
1378 for _, v in pairs(compat_options) do
1379         write_line("sysproto", read_file(v.tmp))
1380         write_line("sysproto", read_file(v.dcltmp))
1381 end
1382 write_line("sysproto", read_file("sysaue"))
1383 write_line("sysproto", read_file("sysprotoend"))
1384
1385 write_line("systrace", read_file("systracetmp"))
1386 write_line("systrace", read_file("systraceret"))
1387
1388 for _, v in ipairs(output_files) do
1389         local target = config[v]
1390         if target ~= "/dev/null" then
1391                 local fh = assert(io.open(target, "w+"))
1392                 if fh == nil then
1393                         abort(1, "Failed to open '" .. target .. "'")
1394                 end
1395                 assert(fh:write(read_file(v)))
1396                 assert(fh:close())
1397         end
1398 end
1399
1400 cleanup()