net.server_epoll: Add support for systemd socket activation
Allows creating listening sockets and accepting client connections
before Prosody starts.
This is unlike normal Prosody dynamic resource management, where ports
may added and removed at any time, and the ports defined by the config.
Weird things happen if these are closed (e.g. due to reload) so here we
prevent closing and ensure sockets are reused when opened again.
module:set_global();
local have_unix, unix = pcall(require, "socket.unix");
if have_unix and type(unix) == "function" then
-- COMPAT #1717
-- Before the introduction of datagram support, only the stream socket
-- constructor was exported instead of a module table. Due to the lack of a
-- proper release of LuaSocket, distros have settled on shipping either the
-- last RC tag or some commit since then.
-- Here we accommodate both variants.
unix = { stream = unix };
end
if not have_unix or type(unix) ~= "table" then
module:log_status("error", "LuaSocket unix socket support not available or incompatible, ensure it is up to date");
return;
end
local server = require "prosody.net.server";
local adminstream = require "prosody.util.adminstream";
local st = require "prosody.util.stanza";
local socket_path = module:get_option_path("admin_socket", "prosody.sock", "data");
local sessions = module:shared("sessions");
local function fire_admin_event(session, stanza)
local event_data = {
origin = session, stanza = stanza;
};
local event_name;
if stanza.attr.xmlns then
event_name = "admin/"..stanza.attr.xmlns..":"..stanza.name;
else
event_name = "admin/"..stanza.name;
end
module:log("debug", "Firing %s", event_name);
local ret = module:fire_event(event_name, event_data);
if ret == nil then
session.send(st.stanza("repl-result", { type = "error" }):text("No module handled this query. Is mod_admin_shell enabled?"));
end
return ret;
end
module:hook("server-stopping", function ()
for _, session in pairs(sessions) do
session:close("system-shutdown");
end
os.remove(socket_path);
end);
--- Unix domain socket management
local conn, sock;
local listeners = adminstream.server(sessions, fire_admin_event).listeners;
local function accept_connection()
module:log("debug", "accepting...");
local client = sock:accept();
if not client then return; end
server.wrapclient(client, "unix", 0, listeners, "*a");
end
function module.load()
sock = unix.stream();
sock:settimeout(0);
os.remove(socket_path);
local ok, err = sock:bind(socket_path);
if not ok then
module:log_status("error", "Unable to bind admin socket %s: %s", socket_path, err);
return;
end
local ok, err = sock:listen();
if not ok then
module:log_status("error", "Unable to listen on admin socket %s: %s", socket_path, err);
return;
end
if server.wrapserver then
conn = server.wrapserver(sock, socket_path, 0, listeners);
else
conn = server.watchfd(sock:getfd(), accept_connection);
end
end
function module.unload()
if conn then
conn:close();
end
if sock then
sock:close();
end
os.remove(socket_path);
end