mod_posix: Move everything to util.startup
authorKim Alvefur <zash@zash.se>
Sat, 23 Mar 2024 20:48:19 +0100
changeset 13465 c673ff1075bd
parent 13464 a688947fab1e
child 13466 720aed1f5cf2
mod_posix: Move everything to util.startup This allows greater control over the order of events. Notably, the internal ordering between daemonization, initialization of libunbound and setup of signal handling is sensitive. libunbound starts a separate thread for processing DNS requests. If this thread is started before signal handling has been set up, it will not inherit the signal handlers and instead behave as it would have before signal handlers were set up, i.e. cause the whole process to immediately exit. libunbound is usually initialized on the first DNS request, usually triggered by an outgoing s2s connection attempt. If daemonization happens before signals have been set up, signals may not be processed at all.
plugins/mod_posix.lua
util/startup.lua
--- a/plugins/mod_posix.lua	Sun Mar 17 10:10:24 2024 +0000
+++ b/plugins/mod_posix.lua	Sat Mar 23 20:48:19 2024 +0100
@@ -6,117 +6,6 @@
 -- COPYING file in the source package for more information.
 --
 
-
-local want_pposix_version = "0.4.0";
-
-local pposix = assert(require "prosody.util.pposix");
-if pposix._VERSION ~= want_pposix_version then
-	module:log("warn", "Unknown version (%s) of binary pposix module, expected %s."
-		.. "Perhaps you need to recompile?", tostring(pposix._VERSION), want_pposix_version);
-end
-
-
-local lfs = require "lfs";
-local stat = lfs.attributes;
-
-local prosody = _G.prosody;
-
 module:set_global(); -- we're a global module
 
-local umask = module:get_option_string("umask", "027");
-pposix.umask(umask);
-
--- Don't even think about it!
-if not prosody.start_time then -- server-starting
-	if pposix.getuid() == 0 and not module:get_option_boolean("run_as_root") then
-		module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
-		module:log("error", "For more information on running Prosody as root, see https://prosody.im/doc/root");
-		prosody.shutdown("Refusing to run as root", 1);
-	end
-end
-
-local pidfile;
-local pidfile_handle;
-
-local function remove_pidfile()
-	if pidfile_handle then
-		pidfile_handle:close();
-		os.remove(pidfile);
-		pidfile, pidfile_handle = nil, nil;
-	end
-end
-
-local function write_pidfile()
-	if pidfile_handle then
-		remove_pidfile();
-	end
-	pidfile = module:get_option_path("pidfile", nil, "data");
-	if pidfile then
-		local err;
-		local mode = stat(pidfile) and "r+" or "w+";
-		pidfile_handle, err = io.open(pidfile, mode);
-		if not pidfile_handle then
-			module:log("error", "Couldn't write pidfile at %s; %s", pidfile, err);
-			prosody.shutdown("Couldn't write pidfile", 1);
-		else
-			if not lfs.lock(pidfile_handle, "w") then -- Exclusive lock
-				local other_pid = pidfile_handle:read("*a");
-				module:log("error", "Another Prosody instance seems to be running with PID %s, quitting", other_pid);
-				pidfile_handle = nil;
-				prosody.shutdown("Prosody already running", 1);
-			else
-				pidfile_handle:close();
-				pidfile_handle, err = io.open(pidfile, "w+");
-				if not pidfile_handle then
-					module:log("error", "Couldn't write pidfile at %s; %s", pidfile, err);
-					prosody.shutdown("Couldn't write pidfile", 1);
-				else
-					if lfs.lock(pidfile_handle, "w") then
-						pidfile_handle:write(tostring(pposix.getpid()));
-						pidfile_handle:flush();
-					end
-				end
-			end
-		end
-	end
-end
-
-local daemonize = prosody.opts.daemonize;
-
-if daemonize == nil then
-	-- Fall back to config file if not specified on command-line
-	daemonize = module:get_option_boolean("daemonize", nil);
-	if daemonize ~= nil then
-		module:log("warn", "The 'daemonize' option has been deprecated, specify -D or -F on the command line instead.");
-		-- TODO: Write some docs and include a link in the warning.
-	end
-end
-
-local function remove_log_sinks()
-	local lm = require "prosody.core.loggingmanager";
-	lm.register_sink_type("console", nil);
-	lm.register_sink_type("stdout", nil);
-	lm.reload_logging();
-end
-
-if daemonize then
-	local function daemonize_server()
-		module:log("info", "Prosody is about to detach from the console, disabling further console output");
-		remove_log_sinks();
-		local ok, ret = pposix.daemonize();
-		if not ok then
-			module:log("error", "Failed to daemonize: %s", ret);
-		elseif ret and ret > 0 then
-			os.exit(0);
-		else
-			module:log("info", "Successfully daemonized to PID %d", pposix.getpid());
-			write_pidfile();
-		end
-	end
-	module:hook("server-started", daemonize_server)
-else
-	-- Not going to daemonize, so write the pid of this process
-	write_pidfile();
-end
-
-module:hook("server-stopped", remove_pidfile);
+-- TODO delete this whole concept
--- a/util/startup.lua	Sun Mar 17 10:10:24 2024 +0000
+++ b/util/startup.lua	Sat Mar 23 20:48:19 2024 +0100
@@ -531,21 +531,30 @@
 	config.set("*", "log", { { levels = { min = log_level or "info" }, to = "console" } });
 end
 
+local function check_posix()
+	if prosody.platform ~= "posix" then return end
+
+	local want_pposix_version = "0.4.0";
+	local have_pposix, pposix = pcall(require, "prosody.util.pposix");
+
+	if pposix._VERSION ~= want_pposix_version then
+		print(string.format("Unknown version (%s) of binary pposix module, expected %s",
+			tostring(pposix._VERSION), want_pposix_version));
+		os.exit(1);
+	end
+	if have_pposix and pposix then
+		return pposix;
+	end
+end
+
 function startup.switch_user()
 	-- Switch away from root and into the prosody user --
 	-- NOTE: This function is only used by prosodyctl.
 	-- The prosody process is built with the assumption that
 	-- it is already started as the appropriate user.
 
-	local want_pposix_version = "0.4.0";
-	local have_pposix, pposix = pcall(require, "prosody.util.pposix");
-
-	if have_pposix and pposix then
-		if pposix._VERSION ~= want_pposix_version then
-			print(string.format("Unknown version (%s) of binary pposix module, expected %s",
-				tostring(pposix._VERSION), want_pposix_version));
-			os.exit(1);
-		end
+	local pposix = check_posix()
+	if pposix then
 		prosody.current_uid = pposix.getuid();
 		local arg_root = prosody.opts.root;
 		if prosody.current_uid == 0 and config.get("*", "run_as_root") ~= true and not arg_root then
@@ -671,6 +680,93 @@
 	end
 end
 
+function startup.posix_umask()
+	if prosody.platform ~= "posix" then return end
+	local pposix = require "prosody.util.pposix";
+	local umask = config.get("*", "umask") or "027";
+	pposix.umask(umask);
+end
+
+function startup.check_user()
+	local pposix = check_posix();
+	if not pposix then return end
+	-- Don't even think about it!
+	if pposix.getuid() == 0 and not config.get("*", "run_as_root") then
+		log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
+		log("error", "For more information on running Prosody as root, see https://prosody.im/doc/root");
+		prosody.shutdown("Refusing to run as root", 1);
+	end
+end
+
+local function remove_pidfile()
+	local pidfile = prosody.pidfile;
+	if prosody.pidfile_handle then
+		prosody.pidfile_handle:close();
+		os.remove(pidfile);
+		prosody.pidfile, prosody.pidfile_handle = nil, nil;
+	end
+end
+
+function startup.write_pidfile()
+	local pposix = check_posix();
+	if not pposix then return end
+	local lfs = require "lfs";
+	local stat = lfs.attributes;
+	local pidfile = config.get("*", "pidfile") or nil;
+	if not pidfile then return end
+	pidfile = config.resolve_relative_path(prosody.paths.data, pidfile);
+	local mode = stat(pidfile) and "r+" or "w+";
+	local pidfile_handle, err = io.open(pidfile, mode);
+	if not pidfile_handle then
+		log("error", "Couldn't write pidfile at %s; %s", pidfile, err);
+		prosody.shutdown("Couldn't write pidfile", 1);
+	else
+		prosody.pidfile = pidfile;
+		if not lfs.lock(pidfile_handle, "w") then -- Exclusive lock
+			local other_pid = pidfile_handle:read("*a");
+			log("error", "Another Prosody instance seems to be running with PID %s, quitting", other_pid);
+			prosody.pidfile_handle = nil;
+			prosody.shutdown("Prosody already running", 1);
+		else
+			pidfile_handle:close();
+			pidfile_handle, err = io.open(pidfile, "w+");
+			if not pidfile_handle then
+				log("error", "Couldn't write pidfile at %s; %s", pidfile, err);
+				prosody.shutdown("Couldn't write pidfile", 1);
+			else
+				if lfs.lock(pidfile_handle, "w") then
+					pidfile_handle:write(tostring(pposix.getpid()));
+					pidfile_handle:flush();
+					prosody.pidfile_handle = pidfile_handle;
+				end
+			end
+		end
+	end
+	prosody.events.add_handler("server-stopped", remove_pidfile);
+end
+
+local function remove_log_sinks()
+	local lm = require "prosody.core.loggingmanager";
+	lm.register_sink_type("console", nil);
+	lm.register_sink_type("stdout", nil);
+	lm.reload_logging();
+end
+
+function startup.posix_daemonize()
+	if not prosody.opts.daemonize then return end
+	local pposix = check_posix();
+	log("info", "Prosody is about to detach from the console, disabling further console output");
+	remove_log_sinks();
+	local ok, ret = pposix.daemonize();
+	if not ok then
+		log("error", "Failed to daemonize: %s", ret);
+	elseif ret and ret > 0 then
+		os.exit(0);
+	else
+		log("info", "Successfully daemonized to PID %d", pposix.getpid());
+	end
+end
+
 function startup.hook_posix_signals()
 	if prosody.platform ~= "posix" then return end
 	local have_signal, signal = pcall(require, "prosody.util.signal");
@@ -765,6 +861,7 @@
 	-- These actions are in a strict order, as many depend on
 	-- previous steps to have already been performed
 	prosody.process_type = "prosody";
+	startup.check_user();
 	startup.parse_args();
 	startup.init_global_state();
 	startup.read_config();
@@ -790,6 +887,8 @@
 	startup.init_http_client();
 	startup.init_data_store();
 	startup.init_global_protection();
+	startup.posix_daemonize();
+	startup.write_pidfile();
 	startup.hook_posix_signals();
 	startup.prepare_to_start();
 	startup.notify_started();