1-- 2-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3-- 4-- Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org> 5-- 6-- Redistribution and use in source and binary forms, with or without 7-- modification, are permitted provided that the following conditions 8-- are met: 9-- 1. Redistributions of source code must retain the above copyright 10-- notice, this list of conditions and the following disclaimer. 11-- 2. Redistributions in binary form must reproduce the above copyright 12-- notice, this list of conditions and the following disclaimer in the 13-- documentation and/or other materials provided with the distribution. 14-- 15-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18-- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25-- SUCH DAMAGE. 26-- 27-- $FreeBSD$ 28-- 29 30local config = require("config") 31local core = require("core") 32 33local cli = {} 34 35if not pager then 36 -- shim for the pager module that just doesn't do it. 37 -- XXX Remove after 12.2 goes EoL. 38 pager = { 39 open = function() end, 40 close = function() end, 41 output = function(str) 42 printc(str) 43 end, 44 } 45end 46 47-- Internal function 48-- Parses arguments to boot and returns two values: kernel_name, argstr 49-- Defaults to nil and "" respectively. 50-- This will also parse arguments to autoboot, but the with_kernel argument 51-- will need to be explicitly overwritten to false 52local function parseBootArgs(argv, with_kernel) 53 if with_kernel == nil then 54 with_kernel = true 55 end 56 if #argv == 0 then 57 if with_kernel then 58 return nil, "" 59 else 60 return "" 61 end 62 end 63 local kernel_name 64 local argstr = "" 65 66 for _, v in ipairs(argv) do 67 if with_kernel and v:sub(1,1) ~= "-" then 68 kernel_name = v 69 else 70 argstr = argstr .. " " .. v 71 end 72 end 73 if with_kernel then 74 return kernel_name, argstr 75 else 76 return argstr 77 end 78end 79 80local function setModule(module, loading) 81 if loading and config.enableModule(module) then 82 print(module .. " will be loaded") 83 elseif not loading and config.disableModule(module) then 84 print(module .. " will not be loaded") 85 end 86end 87 88-- Declares a global function cli_execute that attempts to dispatch the 89-- arguments passed as a lua function. This gives lua a chance to intercept 90-- builtin CLI commands like "boot" 91-- This function intentionally does not follow our general naming guideline for 92-- functions. This is global pollution, but the clearly separated 'cli' looks 93-- more like a module indicator to serve as a hint of where to look for the 94-- corresponding definition. 95function cli_execute(...) 96 local argv = {...} 97 -- Just in case... 98 if #argv == 0 then 99 return loader.command(...) 100 end 101 102 local cmd_name = argv[1] 103 local cmd = cli[cmd_name] 104 if cmd ~= nil and type(cmd) == "function" then 105 -- Pass argv wholesale into cmd. We could omit argv[0] since the 106 -- traditional reasons for including it don't necessarily apply, 107 -- it may not be totally redundant if we want to have one global 108 -- handling multiple commands 109 return cmd(...) 110 else 111 return loader.command(...) 112 end 113 114end 115 116function cli_execute_unparsed(str) 117 return cli_execute(loader.parse(str)) 118end 119 120-- Module exports 121 122function cli.boot(...) 123 local _, argv = cli.arguments(...) 124 local kernel, argstr = parseBootArgs(argv) 125 if kernel ~= nil then 126 loader.perform("unload") 127 config.selectKernel(kernel) 128 end 129 core.boot(argstr) 130end 131 132function cli.autoboot(...) 133 local _, argv = cli.arguments(...) 134 local argstr = parseBootArgs(argv, false) 135 core.autoboot(argstr) 136end 137 138cli['boot-conf'] = function(...) 139 local _, argv = cli.arguments(...) 140 local kernel, argstr = parseBootArgs(argv) 141 if kernel ~= nil then 142 loader.perform("unload") 143 config.selectKernel(kernel) 144 end 145 core.autoboot(argstr) 146end 147 148cli['read-conf'] = function(...) 149 local _, argv = cli.arguments(...) 150 config.readConf(assert(core.popFrontTable(argv))) 151end 152 153cli['reload-conf'] = function() 154 config.reload() 155end 156 157cli["enable-module"] = function(...) 158 local _, argv = cli.arguments(...) 159 if #argv == 0 then 160 print("usage error: enable-module module") 161 return 162 end 163 164 setModule(argv[1], true) 165end 166 167cli["disable-module"] = function(...) 168 local _, argv = cli.arguments(...) 169 if #argv == 0 then 170 print("usage error: disable-module module") 171 return 172 end 173 174 setModule(argv[1], false) 175end 176 177cli["toggle-module"] = function(...) 178 local _, argv = cli.arguments(...) 179 if #argv == 0 then 180 print("usage error: toggle-module module") 181 return 182 end 183 184 local module = argv[1] 185 setModule(module, not config.isModuleEnabled(module)) 186end 187 188cli["show-module-options"] = function() 189 local module_info = config.getModuleInfo() 190 local modules = module_info['modules'] 191 local blacklist = module_info['blacklist'] 192 local lines = {} 193 194 for module, info in pairs(modules) do 195 if #lines > 0 then 196 lines[#lines + 1] = "" 197 end 198 199 lines[#lines + 1] = "Name: " .. module 200 if info.name then 201 lines[#lines + 1] = "Path: " .. info.name 202 end 203 204 if info.type then 205 lines[#lines + 1] = "Type: " .. info.type 206 end 207 208 if info.flags then 209 lines[#lines + 1] = "Flags: " .. info.flags 210 end 211 212 if info.before then 213 lines[#lines + 1] = "Before load: " .. info.before 214 end 215 216 if info.after then 217 lines[#lines + 1] = "After load: " .. info.after 218 end 219 220 if info.error then 221 lines[#lines + 1] = "Error: " .. info.error 222 end 223 224 local status 225 if blacklist[module] and not info.force then 226 status = "Blacklisted" 227 elseif info.load == "YES" then 228 status = "Load" 229 else 230 status = "Don't load" 231 end 232 233 lines[#lines + 1] = "Status: " .. status 234 end 235 236 pager.open() 237 for _, v in ipairs(lines) do 238 pager.output(v .. "\n") 239 end 240 pager.close() 241end 242 243cli["disable-device"] = function(...) 244 local _, argv = cli.arguments(...) 245 local d, u 246 247 if #argv == 0 then 248 print("usage error: disable-device device") 249 return 250 end 251 252 d, u = string.match(argv[1], "(%w*%a)(%d+)") 253 if d ~= nil then 254 loader.setenv("hint." .. d .. "." .. u .. ".disabled", "1") 255 end 256end 257 258-- Used for splitting cli varargs into cmd_name and the rest of argv 259function cli.arguments(...) 260 local argv = {...} 261 local cmd_name 262 cmd_name, argv = core.popFrontTable(argv) 263 return cmd_name, argv 264end 265 266return cli 267