File

util/prosodyctl/shell.lua @ 11592:64cfa396bb84

net.server_epoll: Fix reporting of socket connect timeout If the underlying TCP connection times out before the write timeout kicks in, end up here with err="timeout", which the following code treats as a minor issue. Then, due to epoll apparently returning the EPOLLOUT (writable) event too, we go on and try to write to the socket (commonly stream headers). This fails because the socket is closed, which becomes the error returned up the stack to the rest of Prosody. This also trips the 'onconnect' signal, which has effects on various things, such as the net.connect state machine. Probably undesirable effects. With this, we instead return "connection timeout", like server_event, and destroy the connection handle properly. And then nothing else happens because the connection has been destroyed.
author Kim Alvefur <zash@zash.se>
date Mon, 07 Jun 2021 17:37:14 +0200
parent 11522:5bd38d9197e1
child 11890:b9aab1962a2b
line wrap: on
line source

local config = require "core.configmanager";
local server = require "net.server";
local st = require "util.stanza";
local path = require "util.paths";
local parse_args = require "util.argparse".parse;
local unpack = table.unpack or _G.unpack;

local have_readline, readline = pcall(require, "readline");

local adminstream = require "util.adminstream";

if have_readline then
	readline.set_readline_name("prosody");
	readline.set_options({
			histfile = path.join(prosody.paths.data, ".shell_history");
			ignoredups = true;
		});
end

local function read_line()
	if have_readline then
		return readline.readline("prosody> ");
	else
		io.write("prosody> ");
		return io.read("*line");
	end
end

local function send_line(client, line)
	client.send(st.stanza("repl-input"):text(line));
end

local function repl(client)
	local line = read_line();
	if not line or line == "quit" or line == "exit" or line == "bye" then
		if not line then
			print("");
		end
		if have_readline then
			readline.save_history();
		end
		os.exit();
	end
	send_line(client, line);
end

local function printbanner()
	print([[
                     ____                \   /     _
                    |  _ \ _ __ ___  ___  _-_   __| |_   _
                    | |_) | '__/ _ \/ __|/ _ \ / _` | | | |
                    |  __/| | | (_) \__ \ |_| | (_| | |_| |
                    |_|   |_|  \___/|___/\___/ \__,_|\__, |
                    A study in simplicity            |___/

]]);
	print("Welcome to the Prosody administration console. For a list of commands, type: help");
	print("You may find more help on using this console in our online documentation at ");
	print("https://prosody.im/doc/console\n");
end

local function start(arg) --luacheck: ignore 212/arg
	local client = adminstream.client();
	local opts, err, where = parse_args(arg);

	if not opts then
		if err == "param-not-found" then
			print("Unknown command-line option: "..tostring(where));
		elseif err == "missing-value" then
			print("Expected a value to follow command-line option: "..where);
		end
		os.exit(1);
	end

	if arg[1] then
		if arg[2] then
			-- prosodyctl shell module reload foo bar.com --> module:reload("foo", "bar.com")
			-- COMPAT Lua 5.1 doesn't have the separator argument to string.rep
			arg[1] = string.format("%s:%s("..string.rep("%q, ", #arg-2):sub(1, -3)..")", unpack(arg));
		end

		client.events.add_handler("connected", function()
			client.send(st.stanza("repl-input"):text(arg[1]));
			return true;
		end, 1);

		local errors = 0; -- TODO This is weird, but works for now.
		client.events.add_handler("received", function(stanza)
			if stanza.name == "repl-output" or stanza.name == "repl-result" then
				if stanza.attr.type == "error" then
					errors = errors + 1;
					io.stderr:write(stanza:get_text(), "\n");
				else
					print(stanza:get_text());
				end
			end
			if stanza.name == "repl-result" then
				os.exit(errors);
			end
			return true;
		end, 1);
	end

	client.events.add_handler("connected", function ()
		if not opts.quiet then
			printbanner();
		end
		repl(client);
	end);

	client.events.add_handler("disconnected", function ()
		print("--- session closed ---");
		os.exit();
	end);

	client.events.add_handler("received", function (stanza)
		if stanza.name == "repl-output" or stanza.name == "repl-result" then
			local result_prefix = stanza.attr.type == "error" and "!" or "|";
			print(result_prefix.." "..stanza:get_text());
		end
		if stanza.name == "repl-result" then
			repl(client);
		end
	end);

	local socket_path = path.resolve_relative_path(prosody.paths.data, opts.socket or config.get("*", "admin_socket") or "prosody.sock");
	local conn = adminstream.connection(socket_path, client.listeners);
	local ok, err = conn:connect();
	if not ok then
		if err == "no unix socket support" then
			print("** LuaSocket unix socket support not available or incompatible, ensure your");
			print("** version is up to date.");
		else
			print("** Unable to connect to server - is it running? Is mod_admin_shell enabled?");
			print("** Connection error: "..err);
		end
		os.exit(1);
	end
	server.loop();
end

return {
	shell = start;
};