diff --git a/share/examples/tests/tests/atf/Kyuafile b/share/examples/tests/tests/atf/Kyuafile index 8c60947d1082..ef2407d0f11c 100644 --- a/share/examples/tests/tests/atf/Kyuafile +++ b/share/examples/tests/tests/atf/Kyuafile @@ -1,46 +1,45 @@ --- $FreeBSD$ -- -- Copyright 2013 Google Inc. -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are -- met: -- -- * Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- * Neither the name of Google Inc. nor the names of its contributors -- may be used to endorse or promote products derived from this software -- without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax(2) -- All tests provided by the FreeBSD base system should set the test_suite -- property to FreeBSD. This creates a namespace in the configuration file -- in which specific run-time properties can be passed to the tests below. test_suite('FreeBSD') -- Register the various test programs into the test suite defined in this -- directory. -- -- Note that, while Kyua supports overriding the test case metadata -- properties (e.g. their timeout) along the test program definition, you -- should not do so for ATF test programs. The ATF test cases themselves -- encode the right values. atf_test_program{name='cp_test'} atf_test_program{name='printf_test'} diff --git a/share/examples/tests/tests/plain/Kyuafile b/share/examples/tests/tests/plain/Kyuafile index c9301b0ef97e..c427a6045e95 100644 --- a/share/examples/tests/tests/plain/Kyuafile +++ b/share/examples/tests/tests/plain/Kyuafile @@ -1,47 +1,46 @@ --- $FreeBSD$ -- -- Copyright 2013 Google Inc. -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are -- met: -- -- * Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- * Neither the name of Google Inc. nor the names of its contributors -- may be used to endorse or promote products derived from this software -- without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax(2) -- All tests provided by the FreeBSD base system should set the test_suite -- property to FreeBSD. This creates a namespace in the configuration file -- in which specific run-time properties can be passed to the tests below. test_suite('FreeBSD') -- Register the various test programs into the test suite defined in this -- directory. -- -- Because plain test programs cannot define metadata in their code (they -- have no mechanism to communicate that to Kyua), we can instead define -- any metadata properties in here. These have the exact same meaning as -- their ATF counterparts. These properties are often useful to define -- prerequisites for the execution of the tests. plain_test_program{name='cp_test', required_programs='/bin/cp'} plain_test_program{name='printf_test'} diff --git a/share/examples/tests/tests/tap/Kyuafile b/share/examples/tests/tests/tap/Kyuafile index 032d9a91d8da..64339c54c012 100644 --- a/share/examples/tests/tests/tap/Kyuafile +++ b/share/examples/tests/tests/tap/Kyuafile @@ -1,47 +1,46 @@ --- $FreeBSD$ -- -- Copyright 2013 Google Inc. -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are -- met: -- -- * Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- * Neither the name of Google Inc. nor the names of its contributors -- may be used to endorse or promote products derived from this software -- without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax(2) -- All tests provided by the FreeBSD base system should set the test_suite -- property to FreeBSD. This creates a namespace in the configuration file -- in which specific run-time properties can be passed to the tests below. test_suite('FreeBSD') -- Register the various test programs into the test suite defined in this -- directory. -- -- Because plain test programs cannot define metadata in their code (they -- have no mechanism to communicate that to Kyua), we can instead define -- any metadata properties in here. These have the exact same meaning as -- their ATF counterparts. These properties are often useful to define -- prerequisites for the execution of the tests. tap_test_program{name='cp_test', required_programs='/bin/cp'} tap_test_program{name='printf_test'} diff --git a/share/man/man9/style.lua.9 b/share/man/man9/style.lua.9 index d6067ddeb4df..0417efc83c45 100644 --- a/share/man/man9/style.lua.9 +++ b/share/man/man9/style.lua.9 @@ -1,126 +1,125 @@ .\"- .\" SPDX-License-Identifier: BSD-2-Clause .\" .\" Copyright (c) 2018 Kyle Evans .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL [your name] OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .Dd February 25, 2018 .Dt STYLE.LUA 9 .Os .Sh NAME .Nm style.lua .Nd .Fx lua file style guide .Sh DESCRIPTION This file specifies the preferred style for lua source files in the .Fx source tree. Many of the style rules are implicit in the examples. Be careful to check the examples before assuming that .Nm is silent on an issue. .Pp The copyright header should be a series of single-line comments. Use the single-line comment style for every line in a multi-line comment. .Pp After any copyright header, there is a blank line, and the .Li $\&FreeBSD$ comment for non-C/C++ source files. .Pp The preferred method of including other files and modules is with .Fn require name , such as: .Bd -literal --- $FreeBSD$ config = require("config"); menu = require("menu"); password = require("password"); -- One blank line following the module require block .Ed .Pp .Fn include is generally avoided. .Pp Indentation and wrapping should match the guidelines provided by .Xr style 9 . Do note that it is ok to wrap much earlier than 80 columns if readability would otherwise suffer. .Pp Where possible, .Fn s:method ... is preferred to .Fn method s ... . This is applicable to objects with methods. String are a commonly-used example of objects with methods. .Pp Testing for .Va nil should be done explicitly, rather than as a boolean expression. Single-line conditional statements and loops should be avoided. .Pp .Ic local variables should be preferred to global variables in module scope. internal_underscores tend to be preferred for variable identifiers, while camelCase tends to be preferred for function identifiers. .Pp If a table definition spans multiple lines, then the final value in the table should include the optional terminating comma. For example: .Bd -literal -- No terminating comma needed for trivial table definitions local trivial_table = {1, 2, 3, 4} local complex_table = { { id = "foo", func = foo_function, -- Trailing comma preferred }, { id = "bar", func = bar_function, }, -- Trailing comma preferred } .Ed .Pp This reduces the chance for errors to be introduced when modifying more complex tables. .Pp Multiple local variables should not be declared .Sy and initialized on a single line. Lines containing multiple variable declarations without initialization are ok. Lines containing multiple variable declarations initialized to a single function call returning a tuple with the same number of values is also ok. .Pp Initialization .Sy should be done at declaration time as appropriate. .Sh SEE ALSO .Xr style 9 .Sh HISTORY This manual page is inspired from the same source as .Xr style 9 manual page in .Fx . diff --git a/tests/Kyuafile b/tests/Kyuafile index 10cf039ae041..3e97c4ce5cde 100644 --- a/tests/Kyuafile +++ b/tests/Kyuafile @@ -1,52 +1,51 @@ --- $FreeBSD$ -- -- Copyright 2011 Google Inc. -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are -- met: -- -- * Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- * Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- * Neither the name of Google Inc. nor the names of its contributors -- may be used to endorse or promote products derived from this software -- without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -- Automatically recurses into any subdirectory that holds a Kyuafile. -- As such, this Kyuafile is suitable for installation into the root of -- the tests hierarchy as well as into any other subdirectory that needs -- "auto-discovery" of tests. -- -- This file is based on the Kyuafile.top sample file distributed in the -- kyua-cli package. syntax(2) local directory = fs.dirname(current_kyuafile()) for file in fs.files(directory) do if file == "." or file == ".." then -- Skip these special entries. else local kyuafile_relative = fs.join(file, "Kyuafile") local kyuafile_absolute = fs.join(directory, kyuafile_relative) if fs.exists(kyuafile_absolute) then include(kyuafile_relative) end end end diff --git a/tools/lua/template.lua b/tools/lua/template.lua index 3662953b0f2e..6c7d33c8ab0f 100644 --- a/tools/lua/template.lua +++ b/tools/lua/template.lua @@ -1,652 +1,651 @@ -- From lua-resty-template (modified to remove external dependencies) --[[ Copyright (c) 2014 - 2020 Aapo Talvensaari All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the {organization} nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ]]-- --- $FreeBSD$ local setmetatable = setmetatable local loadstring = loadstring local tostring = tostring local setfenv = setfenv local require = require local concat = table.concat local assert = assert local write = io.write local pcall = pcall local phase local open = io.open local load = load local type = type local dump = string.dump local find = string.find local gsub = string.gsub local byte = string.byte local null local sub = string.sub local var local _VERSION = _VERSION local _ENV = _ENV -- luacheck: globals _ENV local _G = _G local HTML_ENTITIES = { ["&"] = "&", ["<"] = "<", [">"] = ">", ['"'] = """, ["'"] = "'", ["/"] = "/" } local CODE_ENTITIES = { ["{"] = "{", ["}"] = "}", ["&"] = "&", ["<"] = "<", [">"] = ">", ['"'] = """, ["'"] = "'", ["/"] = "/" } local VAR_PHASES local ESC = byte("\27") local NUL = byte("\0") local HT = byte("\t") local VT = byte("\v") local LF = byte("\n") local SOL = byte("/") local BSOL = byte("\\") local SP = byte(" ") local AST = byte("*") local NUM = byte("#") local LPAR = byte("(") local LSQB = byte("[") local LCUB = byte("{") local MINUS = byte("-") local PERCNT = byte("%") local EMPTY = "" local VIEW_ENV if _VERSION == "Lua 5.1" then VIEW_ENV = { __index = function(t, k) return t.context[k] or t.template[k] or _G[k] end } else VIEW_ENV = { __index = function(t, k) return t.context[k] or t.template[k] or _ENV[k] end } end local newtab do local ok ok, newtab = pcall(require, "table.new") if not ok then newtab = function() return {} end end end local function enabled(val) if val == nil then return true end return val == true or (val == "1" or val == "true" or val == "on") end local function trim(s) return gsub(gsub(s, "^%s+", EMPTY), "%s+$", EMPTY) end local function rpos(view, s) while s > 0 do local c = byte(view, s, s) if c == SP or c == HT or c == VT or c == NUL then s = s - 1 else break end end return s end local function escaped(view, s) if s > 1 and byte(view, s - 1, s - 1) == BSOL then if s > 2 and byte(view, s - 2, s - 2) == BSOL then return false, 1 else return true, 1 end end return false, 0 end local function read_file(path) local file, err = open(path, "rb") if not file then return nil, err end local content content, err = file:read "*a" file:close() return content, err end local function load_view(template) return function(view, plain) if plain == true then return view end local path, root = view, template.root if root and root ~= EMPTY then if byte(root, -1) == SOL then root = sub(root, 1, -2) end if byte(view, 1) == SOL then path = sub(view, 2) end path = root .. "/" .. path end return plain == false and assert(read_file(path)) or read_file(path) or view end end local function load_file(func) return function(view) return func(view, false) end end local function load_string(func) return function(view) return func(view, true) end end local function loader(template) return function(view) return assert(load(view, nil, nil, setmetatable({ template = template }, VIEW_ENV))) end end local function visit(visitors, content, tag, name) if not visitors then return content end for i = 1, visitors.n do content = visitors[i](content, tag, name) end return content end local function new(template, safe) template = template or newtab(0, 26) template._VERSION = "2.0" template.cache = {} template.load = load_view(template) template.load_file = load_file(template.load) template.load_string = load_string(template.load) template.print = write local load_chunk = loader(template) local caching if VAR_PHASES and VAR_PHASES[phase()] then caching = enabled(var.template_cache) else caching = true end local visitors function template.visit(func) if not visitors then visitors = { func, n = 1 } return end visitors.n = visitors.n + 1 visitors[visitors.n] = func end function template.caching(enable) if enable ~= nil then caching = enable == true end return caching end function template.output(s) if s == nil or s == null then return EMPTY end if type(s) == "function" then return template.output(s()) end return tostring(s) end function template.escape(s, c) if type(s) == "string" then if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end return gsub(s, "[\">/<'&]", HTML_ENTITIES) end return template.output(s) end function template.new(view, layout) local vt = type(view) if vt == "boolean" then return new(nil, view) end if vt == "table" then return new(view, safe) end if vt == "nil" then return new(nil, safe) end local render local process if layout then if type(layout) == "table" then render = function(self, context) context = context or self context.blocks = context.blocks or {} context.view = template.process(view, context) layout.blocks = context.blocks or {} layout.view = context.view or EMPTY layout:render() end process = function(self, context) context = context or self context.blocks = context.blocks or {} context.view = template.process(view, context) layout.blocks = context.blocks or {} layout.view = context.view return tostring(layout) end else render = function(self, context) context = context or self context.blocks = context.blocks or {} context.view = template.process(view, context) template.render(layout, context) end process = function(self, context) context = context or self context.blocks = context.blocks or {} context.view = template.process(view, context) return template.process(layout, context) end end else render = function(self, context) return template.render(view, context or self) end process = function(self, context) return template.process(view, context or self) end end if safe then return setmetatable({ render = function(...) local ok, err = pcall(render, ...) if not ok then return nil, err end end, process = function(...) local ok, output = pcall(process, ...) if not ok then return nil, output end return output end, }, { __tostring = function(...) local ok, output = pcall(process, ...) if not ok then return "" end return output end }) end return setmetatable({ render = render, process = process }, { __tostring = process }) end function template.precompile(view, path, strip, plain) local chunk = dump(template.compile(view, nil, plain), strip ~= false) if path then local file = open(path, "wb") file:write(chunk) file:close() end return chunk end function template.precompile_string(view, path, strip) return template.precompile(view, path, strip, true) end function template.precompile_file(view, path, strip) return template.precompile(view, path, strip, false) end function template.compile(view, cache_key, plain) assert(view, "view was not provided for template.compile(view, cache_key, plain)") if cache_key == "no-cache" then return load_chunk(template.parse(view, plain)), false end cache_key = cache_key or view local cache = template.cache if cache[cache_key] then return cache[cache_key], true end local func = load_chunk(template.parse(view, plain)) if caching then cache[cache_key] = func end return func, false end function template.compile_file(view, cache_key) return template.compile(view, cache_key, false) end function template.compile_string(view, cache_key) return template.compile(view, cache_key, true) end function template.parse(view, plain) assert(view, "view was not provided for template.parse(view, plain)") if plain ~= true then view = template.load(view, plain) if byte(view, 1, 1) == ESC then return view end end local j = 2 local c = {[[ context=... or {} local ___,blocks,layout={},blocks or {} local function include(v, c) return template.process(v, c or context) end local function echo(...) for i=1,select("#", ...) do ___[#___+1] = tostring(select(i, ...)) end end ]] } local i, s = 1, find(view, "{", 1, true) while s do local t, p = byte(view, s + 1, s + 1), s + 2 if t == LCUB then local e = find(view, "}}", p, true) if e then local z, w = escaped(view, s) if i < s - w then c[j] = "___[#___+1]=[=[\n" c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) c[j+2] = "]=]\n" j=j+3 end if z then i = s else c[j] = "___[#___+1]=template.escape(" c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "{") c[j+2] = ")\n" j=j+3 s, i = e + 1, e + 2 end end elseif t == AST then local e = find(view, "*}", p, true) if e then local z, w = escaped(view, s) if i < s - w then c[j] = "___[#___+1]=[=[\n" c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) c[j+2] = "]=]\n" j=j+3 end if z then i = s else c[j] = "___[#___+1]=template.output(" c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "*") c[j+2] = ")\n" j=j+3 s, i = e + 1, e + 2 end end elseif t == PERCNT then local e = find(view, "%}", p, true) if e then local z, w = escaped(view, s) if z then if i < s - w then c[j] = "___[#___+1]=[=[\n" c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) c[j+2] = "]=]\n" j=j+3 end i = s else local n = e + 2 if byte(view, n, n) == LF then n = n + 1 end local r = rpos(view, s - 1) if i <= r then c[j] = "___[#___+1]=[=[\n" c[j+1] = visit(visitors, sub(view, i, r)) c[j+2] = "]=]\n" j=j+3 end c[j] = visit(visitors, trim(sub(view, p, e - 1)), "%") c[j+1] = "\n" j=j+2 s, i = n - 1, n end end elseif t == LPAR then local e = find(view, ")}", p, true) if e then local z, w = escaped(view, s) if i < s - w then c[j] = "___[#___+1]=[=[\n" c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) c[j+2] = "]=]\n" j=j+3 end if z then i = s else local f = visit(visitors, sub(view, p, e - 1), "(") local x = find(f, ",", 2, true) if x then c[j] = "___[#___+1]=include([=[" c[j+1] = trim(sub(f, 1, x - 1)) c[j+2] = "]=]," c[j+3] = trim(sub(f, x + 1)) c[j+4] = ")\n" j=j+5 else c[j] = "___[#___+1]=include([=[" c[j+1] = trim(f) c[j+2] = "]=])\n" j=j+3 end s, i = e + 1, e + 2 end end elseif t == LSQB then local e = find(view, "]}", p, true) if e then local z, w = escaped(view, s) if i < s - w then c[j] = "___[#___+1]=[=[\n" c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) c[j+2] = "]=]\n" j=j+3 end if z then i = s else c[j] = "___[#___+1]=include(" c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "[") c[j+2] = ")\n" j=j+3 s, i = e + 1, e + 2 end end elseif t == MINUS then local e = find(view, "-}", p, true) if e then local x, y = find(view, sub(view, s, e + 1), e + 2, true) if x then local z, w = escaped(view, s) if z then if i < s - w then c[j] = "___[#___+1]=[=[\n" c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) c[j+2] = "]=]\n" j=j+3 end i = s else y = y + 1 x = x - 1 if byte(view, y, y) == LF then y = y + 1 end local b = trim(sub(view, p, e - 1)) if b == "verbatim" or b == "raw" then if i < s - w then c[j] = "___[#___+1]=[=[\n" c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) c[j+2] = "]=]\n" j=j+3 end c[j] = "___[#___+1]=[=[" c[j+1] = visit(visitors, sub(view, e + 2, x)) c[j+2] = "]=]\n" j=j+3 else if byte(view, x, x) == LF then x = x - 1 end local r = rpos(view, s - 1) if i <= r then c[j] = "___[#___+1]=[=[\n" c[j+1] = visit(visitors, sub(view, i, r)) c[j+2] = "]=]\n" j=j+3 end c[j] = 'blocks["' c[j+1] = b c[j+2] = '"]=include[=[' c[j+3] = visit(visitors, sub(view, e + 2, x), "-", b) c[j+4] = "]=]\n" j=j+5 end s, i = y - 1, y end end end elseif t == NUM then local e = find(view, "#}", p, true) if e then local z, w = escaped(view, s) if i < s - w then c[j] = "___[#___+1]=[=[\n" c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) c[j+2] = "]=]\n" j=j+3 end if z then i = s else e = e + 2 if byte(view, e, e) == LF then e = e + 1 end s, i = e - 1, e end end end s = find(view, "{", s + 1, true) end s = sub(view, i) if s and s ~= EMPTY then c[j] = "___[#___+1]=[=[\n" c[j+1] = visit(visitors, s) c[j+2] = "]=]\n" j=j+3 end c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)" -- luacheck: ignore return concat(c) end function template.parse_file(view) return template.parse(view, false) end function template.parse_string(view) return template.parse(view, true) end function template.process(view, context, cache_key, plain) assert(view, "view was not provided for template.process(view, context, cache_key, plain)") return template.compile(view, cache_key, plain)(context) end function template.process_file(view, context, cache_key) assert(view, "view was not provided for template.process_file(view, context, cache_key)") return template.compile(view, cache_key, false)(context) end function template.process_string(view, context, cache_key) assert(view, "view was not provided for template.process_string(view, context, cache_key)") return template.compile(view, cache_key, true)(context) end function template.render(view, context, cache_key, plain) assert(view, "view was not provided for template.render(view, context, cache_key, plain)") template.print(template.process(view, context, cache_key, plain)) end function template.render_file(view, context, cache_key) assert(view, "view was not provided for template.render_file(view, context, cache_key)") template.render(view, context, cache_key, false) end function template.render_string(view, context, cache_key) assert(view, "view was not provided for template.render_string(view, context, cache_key)") template.render(view, context, cache_key, true) end if safe then return setmetatable({}, { __index = function(_, k) if type(template[k]) == "function" then return function(...) local ok, a, b = pcall(template[k], ...) if not ok then return nil, a end return a, b end end return template[k] end, __new_index = function(_, k, v) template[k] = v end, }) end return template end return new() diff --git a/tools/pkgbase/metalog_reader.lua b/tools/pkgbase/metalog_reader.lua index be0ccf293869..6a5b33f308ae 100644 --- a/tools/pkgbase/metalog_reader.lua +++ b/tools/pkgbase/metalog_reader.lua @@ -1,534 +1,533 @@ #!/usr/libexec/flua -- SPDX-License-Identifier: BSD-2-Clause -- -- Copyright(c) 2020 The FreeBSD Foundation. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions -- are met: -- 1. Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- 2. Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- -- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -- SUCH DAMAGE. --- $FreeBSD$ function main(args) if #args == 0 then usage() end local filename local printall, checkonly, pkgonly = #args == 1, false, false local dcount, dsize, fuid, fgid, fid = false, false, false, false, false local verbose = false local w_notagdirs = false local i = 1 while i <= #args do if args[i] == '-h' then usage(true) elseif args[i] == '-a' then printall = true elseif args[i] == '-c' then printall = false checkonly = true elseif args[i] == '-p' then printall = false pkgonly = true while i < #args do i = i+1 if args[i] == '-count' then dcount = true elseif args[i] == '-size' then dsize = true elseif args[i] == '-fsetuid' then fuid = true elseif args[i] == '-fsetgid' then fgid = true elseif args[i] == '-fsetid' then fid = true else i = i-1 break end end elseif args[i] == '-v' then verbose = true elseif args[i] == '-Wcheck-notagdir' then w_notagdirs = true elseif args[i]:match('^%-') then io.stderr:write('Unknown argument '..args[i]..'.\n') usage() else filename = args[i] end i = i+1 end if filename == nil then io.stderr:write('Missing filename.\n') usage() end local sess = Analysis_session(filename, verbose, w_notagdirs) local errors if printall then io.write('--- PACKAGE REPORTS ---\n') io.write(sess.pkg_report_full()) io.write('--- LINTING REPORTS ---\n') errors = print_lints(sess) elseif checkonly then errors = print_lints(sess) elseif pkgonly then io.write(sess.pkg_report_simple(dcount, dsize, { fuid and sess.pkg_issetuid or nil, fgid and sess.pkg_issetgid or nil, fid and sess.pkg_issetid or nil })) else io.stderr:write('This text should not be displayed.') usage() end if errors then return 1 end end --- @param man boolean function usage(man) local sn = 'Usage: '..arg[0].. ' [-h] [-a | -c | -p [-count] [-size] [-f...]] [-W...] metalog-path \n' if man then io.write('\n') io.write(sn) io.write( [[ The script reads METALOG file created by pkgbase (make packages) and generates reports about the installed system and issues. It accepts an mtree file in a format that's returned by `mtree -c | mtree -C` Options: -a prints all scan results. this is the default option if no option is provided. -c lints the file and gives warnings/errors, including duplication and conflicting metadata -Wcheck-notagdir entries with dir type and no tags will be also included the first time they appear -p list all package names found in the file as exactly specified by `tags=package=...` -count display the number of files of the package -size display the size of the package -fsetgid only include packages with setgid files -fsetuid only include packages with setuid files -fsetid only include packages with setgid or setuid files -v verbose mode -h help page ]]) os.exit() else io.stderr:write(sn) os.exit(1) end end --- @param sess Analysis_session function print_lints(sess) local dupwarn, duperr = sess.dup_report() io.write(dupwarn) io.write(duperr) local inodewarn, inodeerr = sess.inode_report() io.write(inodewarn) io.write(inodeerr) return #duperr > 0 or #inodeerr > 0 end --- @param t table function sortedPairs(t) local sortedk = {} for k in next, t do sortedk[#sortedk+1] = k end table.sort(sortedk) local i = 0 return function() i = i + 1 return sortedk[i], t[sortedk[i]] end end --- @param t table --- @param f function U> function table_map(t, f) local res = {} for k, v in pairs(t) do res[k] = f(v) end return res end --- @class MetalogRow -- a table contaning file's info, from a line content from METALOG file -- all fields in the table are strings -- sample output: -- { -- filename = ./usr/share/man/man3/inet6_rthdr_segments.3.gz -- lineno = 5 -- attrs = { -- gname = 'wheel' -- uname = 'root' -- mode = '0444' -- size = '1166' -- time = nil -- type = 'file' -- tags = 'package=clibs,debug' -- } -- } --- @param line string function MetalogRow(line, lineno) local res, attrs = {}, {} local filename, rest = line:match('^(%S+) (.+)$') -- mtree file has space escaped as '\\040', not affecting splitting -- string by space for attrpair in rest:gmatch('[^ ]+') do local k, v = attrpair:match('^(.-)=(.+)') attrs[k] = v end res.filename = filename res.linenum = lineno res.attrs = attrs return res end -- check if an array of MetalogRows are equivalent. if not, the first field -- that's different is returned secondly --- @param rows MetalogRow[] --- @param ignore_name boolean --- @param ignore_tags boolean function metalogrows_all_equal(rows, ignore_name, ignore_tags) local __eq = function(l, o) if not ignore_name and l.filename ~= o.filename then return false, 'filename' end -- ignoring linenum in METALOG file as it's not relavant for k in pairs(l.attrs) do if ignore_tags and k == 'tags' then goto continue end if l.attrs[k] ~= o.attrs[k] and o.attrs[k] ~= nil then return false, k end ::continue:: end return true end for _, v in ipairs(rows) do local bol, offby = __eq(v, rows[1]) if not bol then return false, offby end end return true end --- @param tagstr string function pkgname_from_tag(tagstr) local ext, pkgname, pkgend = '', '', '' for seg in tagstr:gmatch('[^,]+') do if seg:match('package=') then pkgname = seg:sub(9) elseif seg == 'development' or seg == 'profile' or seg == 'debug' or seg == 'docs' then pkgend = seg else ext = ext == '' and seg or ext..'-'..seg end end pkgname = pkgname ..(ext == '' and '' or '-'..ext) ..(pkgend == '' and '' or '-'..pkgend) return pkgname end --- @class Analysis_session --- @param metalog string --- @param verbose boolean --- @param w_notagdirs boolean turn on to also check directories function Analysis_session(metalog, verbose, w_notagdirs) local stage_root = {} local files = {} -- map -- set is map. if bool is true then elem exists local pkgs = {} -- map> ----- used to keep track of files not belonging to a pkg. not used so ----- it is commented with ----- -----local nopkg = {} -- set --- @public local swarn = {} --- @public local serrs = {} -- returns number of files in package and size of package -- nil is returned upon errors --- @param pkgname string local function pkg_size(pkgname) local filecount, sz = 0, 0 for filename in pairs(pkgs[pkgname]) do local rows = files[filename] -- normally, there should be only one row per filename -- if these rows are equal, there should be warning, but it -- does not affect size counting. if not, it is an error if #rows > 1 and not metalogrows_all_equal(rows) then return nil end local row = rows[1] if row.attrs.type == 'file' then sz = sz + tonumber(row.attrs.size) end filecount = filecount + 1 end return filecount, sz end --- @param pkgname string --- @param mode number local function pkg_ismode(pkgname, mode) for filename in pairs(pkgs[pkgname]) do for _, row in ipairs(files[filename]) do if tonumber(row.attrs.mode, 8) & mode ~= 0 then return true end end end return false end --- @param pkgname string --- @public local function pkg_issetuid(pkgname) return pkg_ismode(pkgname, 2048) end --- @param pkgname string --- @public local function pkg_issetgid(pkgname) return pkg_ismode(pkgname, 1024) end --- @param pkgname string --- @public local function pkg_issetid(pkgname) return pkg_issetuid(pkgname) or pkg_issetgid(pkgname) end -- sample return: -- { [*string]: { count=1, size=2, issetuid=true, issetgid=true } } local function pkg_report_helper_table() local res = {} for pkgname in pairs(pkgs) do res[pkgname] = {} res[pkgname].count, res[pkgname].size = pkg_size(pkgname) res[pkgname].issetuid = pkg_issetuid(pkgname) res[pkgname].issetgid = pkg_issetgid(pkgname) end return res end -- returns a string describing package scan report --- @public local function pkg_report_full() local sb = {} for pkgname, v in sortedPairs(pkg_report_helper_table()) do sb[#sb+1] = 'Package '..pkgname..':' if v.issetuid or v.issetgid then sb[#sb+1] = ''..table.concat({ v.issetuid and ' setuid' or '', v.issetgid and ' setgid' or '' }, '') end sb[#sb+1] = '\n number of files: '..(v.count or '?') ..'\n total size: '..(v.size or '?') sb[#sb+1] = '\n' end return table.concat(sb, '') end --- @param have_count boolean --- @param have_size boolean --- @param filters function[] --- @public -- returns a string describing package size report. -- sample: "mypackage 2 2048"* if both booleans are true local function pkg_report_simple(have_count, have_size, filters) filters = filters or {} local sb = {} for pkgname, v in sortedPairs(pkg_report_helper_table()) do local pred = true -- doing a foldl to all the function results with (and) for _, f in pairs(filters) do pred = pred and f(pkgname) end if pred then sb[#sb+1] = pkgname..table.concat({ have_count and (' '..(v.count or '?')) or '', have_size and (' '..(v.size or '?')) or ''}, '') ..'\n' end end return table.concat(sb, '') end -- returns a string describing duplicate file warnings, -- returns a string describing duplicate file errors --- @public local function dup_report() local warn, errs = {}, {} for filename, rows in sortedPairs(files) do if #rows == 1 then goto continue end local iseq, offby = metalogrows_all_equal(rows) if iseq then -- repeated line, just a warning local dupmsg = filename .. ' ' .. rows[1].attrs.type .. ' repeated with same meta: line ' .. table.concat(table_map(rows, function(e) return e.linenum end), ',') if rows[1].attrs.type == "dir" then if verbose then warn[#warn+1] = 'warning: ' .. dupmsg .. '\n' end else errs[#errs+1] = 'error: ' .. dupmsg .. '\n' end elseif not metalogrows_all_equal(rows, false, true) then -- same filename (possibly different tags), different metadata, an error errs[#errs+1] = 'error: '..filename ..' exists in multiple locations and with different meta: line ' ..table.concat( table_map(rows, function(e) return e.linenum end), ',') ..'. off by "'..offby..'"' errs[#errs+1] = '\n' end ::continue:: end return table.concat(warn, ''), table.concat(errs, '') end -- returns a string describing warnings of found hard links -- returns a string describing errors of found hard links --- @public local function inode_report() -- obtain inodes of filenames local attributes = require('lfs').attributes local inm = {} -- map local unstatables = {} -- string[] for filename in pairs(files) do -- i only took the first row of a filename, -- and skip links and folders if files[filename][1].attrs.type ~= 'file' then goto continue end local fs = attributes(stage_root .. filename) if fs == nil then unstatables[#unstatables+1] = filename goto continue end local inode = fs.ino inm[inode] = inm[inode] or {} table.insert(inm[inode], filename) ::continue:: end local warn, errs = {}, {} for _, filenames in pairs(inm) do if #filenames == 1 then goto continue end -- i only took the first row of a filename local rows = table_map(filenames, function(e) return files[e][1] end) local iseq, offby = metalogrows_all_equal(rows, true, true) if not iseq then errs[#errs+1] = 'error: ' ..'entries point to the same inode but have different meta: ' ..table.concat(filenames, ',')..' in line ' ..table.concat( table_map(rows, function(e) return e.linenum end), ',') ..'. off by "'..offby..'"' errs[#errs+1] = '\n' end ::continue:: end if #unstatables > 0 then warn[#warn+1] = verbose and 'note: skipped checking inodes: '..table.concat(unstatables, ',')..'\n' or 'note: skipped checking inodes for '..#unstatables..' entries\n' end return table.concat(warn, ''), table.concat(errs, '') end -- The METALOG file is assumed to be at the top of the stage directory. stage_root = string.gsub(metalog, '/[^/]*$', '/') do local fp, errmsg, errcode = io.open(metalog, 'r') if fp == nil then io.stderr:write('cannot open '..metalog..': '..errmsg..': '..errcode..'\n') os.exit(1) end -- scan all lines and put file data into the dictionaries local firsttimes = {} -- set local lineno = 0 for line in fp:lines() do -----local isinpkg = false lineno = lineno + 1 -- skip lines beginning with # if line:match('^%s*#') then goto continue end -- skip blank lines if line:match('^%s*$') then goto continue end local data = MetalogRow(line, lineno) -- entries with dir and no tags... ignore for the first time if not w_notagdirs and data.attrs.tags == nil and data.attrs.type == 'dir' and not firsttimes[data.filename] then firsttimes[data.filename] = true goto continue end files[data.filename] = files[data.filename] or {} table.insert(files[data.filename], data) if data.attrs.tags ~= nil then pkgname = pkgname_from_tag(data.attrs.tags) pkgs[pkgname] = pkgs[pkgname] or {} pkgs[pkgname][data.filename] = true ------isinpkg = true end -----if not isinpkg then nopkg[data.filename] = true end ::continue:: end fp:close() end return { warn = swarn, errs = serrs, pkg_issetuid = pkg_issetuid, pkg_issetgid = pkg_issetgid, pkg_issetid = pkg_issetid, pkg_report_full = pkg_report_full, pkg_report_simple = pkg_report_simple, dup_report = dup_report, inode_report = inode_report } end os.exit(main(arg)) diff --git a/usr.bin/kyua/kyua.conf-default b/usr.bin/kyua/kyua.conf-default index 9c1e8286eb53..e2e73c66515c 100644 --- a/usr.bin/kyua/kyua.conf-default +++ b/usr.bin/kyua/kyua.conf-default @@ -1,14 +1,13 @@ --- $FreeBSD$ -- -- System-wide configuration file for kyua(1). See kyua.conf(5) for details -- on the syntax. -- syntax(2) -- User to drop privileges to when invoking kyua(1) as root and a test case -- requests to be run with non-root permissions. unprivileged_user = 'tests' -- An example to set a configuration property specific to FreeBSD. --test_suites.FreeBSD.fstype = 'ffs'