xref: /freebsd/stand/lua/cli.lua (revision e508c3431d8e1ace6118e150837a0d0d67f1672a)
1--
2-- SPDX-License-Identifier: BSD-2-Clause
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
28local config = require("config")
29local core = require("core")
30
31local cli = {}
32
33-- Internal function
34-- Parses arguments to boot and returns two values: kernel_name, argstr
35-- Defaults to nil and "" respectively.
36-- This will also parse arguments to autoboot, but the with_kernel argument
37-- will need to be explicitly overwritten to false
38local function parseBootArgs(argv, with_kernel)
39	if with_kernel == nil then
40		with_kernel = true
41	end
42	if #argv == 0 then
43		if with_kernel then
44			return nil, ""
45		else
46			return ""
47		end
48	end
49	local kernel_name
50	local argstr = ""
51
52	for _, v in ipairs(argv) do
53		if with_kernel and v:sub(1,1) ~= "-" then
54			kernel_name = v
55		else
56			argstr = argstr .. " " .. v
57		end
58	end
59	if with_kernel then
60		return kernel_name, argstr
61	else
62		return argstr
63	end
64end
65
66local function setModule(module, loading)
67	if loading and config.enableModule(module) then
68		print(module .. " will be loaded")
69	elseif not loading and config.disableModule(module) then
70		print(module .. " will not be loaded")
71	end
72end
73
74-- Declares a global function cli_execute that attempts to dispatch the
75-- arguments passed as a lua function. This gives lua a chance to intercept
76-- builtin CLI commands like "boot"
77-- This function intentionally does not follow our general naming guideline for
78-- functions. This is global pollution, but the clearly separated 'cli' looks
79-- more like a module indicator to serve as a hint of where to look for the
80-- corresponding definition.
81function cli_execute(...)
82	local argv = {...}
83	-- Just in case...
84	if #argv == 0 then
85		return loader.command(...)
86	end
87
88	local cmd_name = argv[1]
89	local cmd = cli[cmd_name]
90	if cmd ~= nil and type(cmd) == "function" then
91		-- Pass argv wholesale into cmd. We could omit argv[0] since the
92		-- traditional reasons for including it don't necessarily apply,
93		-- it may not be totally redundant if we want to have one global
94		-- handling multiple commands
95		return cmd(...)
96	else
97		return loader.command(...)
98	end
99
100end
101
102function cli_execute_unparsed(str)
103	return cli_execute(loader.parse(str))
104end
105
106-- Module exports
107
108function cli.boot(...)
109	local _, argv = cli.arguments(...)
110	local kernel, argstr = parseBootArgs(argv)
111	if kernel ~= nil then
112		loader.perform("unload")
113		config.selectKernel(kernel)
114	end
115	core.boot(argstr)
116end
117
118function cli.autoboot(...)
119	local _, argv = cli.arguments(...)
120	local argstr = parseBootArgs(argv, false)
121	core.autoboot(argstr)
122end
123
124cli['boot-conf'] = function(...)
125	local _, argv = cli.arguments(...)
126	local kernel, argstr = parseBootArgs(argv)
127	if kernel ~= nil then
128		loader.perform("unload")
129		config.selectKernel(kernel)
130	end
131	core.autoboot(argstr)
132end
133
134cli['read-conf'] = function(...)
135	local _, argv = cli.arguments(...)
136	config.readConf(assert(core.popFrontTable(argv)))
137end
138
139cli['reload-conf'] = function()
140	config.reload()
141end
142
143cli["enable-module"] = function(...)
144	local _, argv = cli.arguments(...)
145	if #argv == 0 then
146		print("usage error: enable-module module")
147		return
148	end
149
150	setModule(argv[1], true)
151end
152
153cli["disable-module"] = function(...)
154	local _, argv = cli.arguments(...)
155	if #argv == 0 then
156		print("usage error: disable-module module")
157		return
158	end
159
160	setModule(argv[1], false)
161end
162
163cli['be-list'] = function(...)
164	local _, argv = cli.arguments(...)
165	if #argv ~= 0 then
166		print("usage error: be-list")
167		return
168	end
169
170	for _, bootenv in core.bootenvIter() do
171		print(bootenv)
172	end
173end
174
175cli['be-switch'] = function(...)
176	local _, argv = cli.arguments(...)
177	if #argv == 0 then
178		print("usage error: be-switch beName")
179		return
180	end
181
182	local env = argv[1]
183	core.switchBE(env)
184end
185
186cli["toggle-module"] = function(...)
187	local _, argv = cli.arguments(...)
188	if #argv == 0 then
189		print("usage error: toggle-module module")
190		return
191	end
192
193	local module = argv[1]
194	setModule(module, not config.isModuleEnabled(module))
195end
196
197cli["show-module-options"] = function()
198	local module_info = config.getModuleInfo()
199	local modules = module_info['modules']
200	local blacklist = module_info['blacklist']
201	local lines = {}
202
203	for module, info in pairs(modules) do
204		if #lines > 0 then
205			lines[#lines + 1] = ""
206		end
207
208		lines[#lines + 1] = "Name:        " .. module
209		if info.name then
210			lines[#lines + 1] = "Path:        " .. info.name
211		end
212
213		if info.type then
214			lines[#lines + 1] = "Type:        " .. info.type
215		end
216
217		if info.flags then
218			lines[#lines + 1] = "Flags:       " .. info.flags
219		end
220
221		if info.before then
222			lines[#lines + 1] = "Before load: " .. info.before
223		end
224
225		if info.after then
226			lines[#lines + 1] = "After load:  " .. info.after
227		end
228
229		if info.error then
230			lines[#lines + 1] = "Error:       " .. info.error
231		end
232
233		local status
234		if blacklist[module] and not info.force then
235			status = "Blacklisted"
236		elseif info.load == "YES" then
237			status = "Load"
238		else
239			status = "Don't load"
240		end
241
242		lines[#lines + 1] = "Status:      " .. status
243	end
244
245	pager.open()
246	for _, v in ipairs(lines) do
247		pager.output(v .. "\n")
248	end
249	pager.close()
250end
251
252cli["disable-device"] = function(...)
253	local _, argv = cli.arguments(...)
254	local d, u
255
256	if #argv == 0 then
257		print("usage error: disable-device device")
258		return
259	end
260
261	if #argv > 1 then
262		print("Too many arguments")
263		print("usage error: disable-device device")
264		return
265	end
266
267	d, u = string.match(argv[1], "(%w*%a)(%d+)")
268	if d ~= nil and u ~= nil then
269		loader.setenv("hint." .. d .. "." .. u .. ".disabled", "1")
270	else
271		print("Cannot parse " .. argv[1] .." into driver and unit number.")
272	end
273end
274
275-- Used for splitting cli varargs into cmd_name and the rest of argv
276function cli.arguments(...)
277	local argv = {...}
278	local cmd_name
279	cmd_name, argv = core.popFrontTable(argv)
280	return cmd_name, argv
281end
282
283return cli
284