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["toggle-module"] = function(...)
164	local _, argv = cli.arguments(...)
165	if #argv == 0 then
166		print("usage error: toggle-module module")
167		return
168	end
169
170	local module = argv[1]
171	setModule(module, not config.isModuleEnabled(module))
172end
173
174cli["show-module-options"] = function()
175	local module_info = config.getModuleInfo()
176	local modules = module_info['modules']
177	local blacklist = module_info['blacklist']
178	local lines = {}
179
180	for module, info in pairs(modules) do
181		if #lines > 0 then
182			lines[#lines + 1] = ""
183		end
184
185		lines[#lines + 1] = "Name:        " .. module
186		if info.name then
187			lines[#lines + 1] = "Path:        " .. info.name
188		end
189
190		if info.type then
191			lines[#lines + 1] = "Type:        " .. info.type
192		end
193
194		if info.flags then
195			lines[#lines + 1] = "Flags:       " .. info.flags
196		end
197
198		if info.before then
199			lines[#lines + 1] = "Before load: " .. info.before
200		end
201
202		if info.after then
203			lines[#lines + 1] = "After load:  " .. info.after
204		end
205
206		if info.error then
207			lines[#lines + 1] = "Error:       " .. info.error
208		end
209
210		local status
211		if blacklist[module] and not info.force then
212			status = "Blacklisted"
213		elseif info.load == "YES" then
214			status = "Load"
215		else
216			status = "Don't load"
217		end
218
219		lines[#lines + 1] = "Status:      " .. status
220	end
221
222	pager.open()
223	for _, v in ipairs(lines) do
224		pager.output(v .. "\n")
225	end
226	pager.close()
227end
228
229cli["disable-device"] = function(...)
230	local _, argv = cli.arguments(...)
231	local d, u
232
233	if #argv == 0 then
234		print("usage error: disable-device device")
235		return
236	end
237
238	d, u = string.match(argv[1], "(%w*%a)(%d+)")
239	if d ~= nil then
240		loader.setenv("hint." .. d .. "." .. u .. ".disabled", "1")
241	end
242end
243
244-- Used for splitting cli varargs into cmd_name and the rest of argv
245function cli.arguments(...)
246	local argv = {...}
247	local cmd_name
248	cmd_name, argv = core.popFrontTable(argv)
249	return cmd_name, argv
250end
251
252return cli
253