[monitoring-plugins] Refactor check_users

Lorenz Kästle git at monitoring-plugins.org
Mon Aug 11 23:30:12 CEST 2025


 Module: monitoring-plugins
 Branch: master
 Commit: 6ac236c1ef06ef17541d3919aed2a008aabaa7f4
 Author: Lorenz Kästle <12514511+RincewindsHat at users.noreply.github.com>
   Date: Wed Mar 12 22:01:46 2025 +0100
    URL: https://www.monitoring-plugins.org/repositories/monitoring-plugins/commit/?id=6ac236c1

Refactor check_users

---

 plugins/Makefile.am            |   2 +
 plugins/check_users.c          | 250 +++++++++++++++++++----------------------
 plugins/check_users.d/config.h |  20 ++++
 plugins/check_users.d/users.c  | 166 +++++++++++++++++++++++++++
 plugins/check_users.d/users.h  |  18 +++
 5 files changed, 319 insertions(+), 137 deletions(-)

diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 5cd20319..0a2a91aa 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -55,6 +55,7 @@ EXTRA_DIST = t \
 			 check_game.d \
 			 check_radius.d \
 			 check_time.d \
+			 check_users.d \
 			 check_nagios.d \
 			 check_dbi.d \
 			 check_real.d \
@@ -152,6 +153,7 @@ check_tcp_LDADD = $(SSLOBJS)
 check_time_LDADD = $(NETLIBS)
 check_ntp_time_LDADD = $(NETLIBS) $(MATHLIBS)
 check_ups_LDADD = $(NETLIBS)
+check_users_SOURCES = check_users.c check_users.d/users.c
 check_users_LDADD = $(BASEOBJS) $(WTSAPI32LIBS) $(SYSTEMDLIBS)
 check_by_ssh_LDADD = $(NETLIBS)
 check_ide_smart_LDADD = $(BASEOBJS)
diff --git a/plugins/check_users.c b/plugins/check_users.c
index e91ed4a0..61427f97 100644
--- a/plugins/check_users.c
+++ b/plugins/check_users.c
@@ -34,8 +34,15 @@ const char *progname = "check_users";
 const char *copyright = "2000-2024";
 const char *email = "devel at monitoring-plugins.org";
 
-#include "common.h"
-#include "utils.h"
+#include "check_users.d/users.h"
+#include "output.h"
+#include "perfdata.h"
+#include "states.h"
+#include "utils_base.h"
+#include "./common.h"
+#include "./utils.h"
+#include "check_users.d/config.h"
+#include "thresholds.h"
 
 #if HAVE_WTSAPI32_H
 #	include <windows.h>
@@ -53,29 +60,16 @@ const char *email = "devel at monitoring-plugins.org";
 #	include <systemd/sd-login.h>
 #endif
 
-#define possibly_set(a, b) ((a) == 0 ? (b) : 0)
+typedef struct process_argument_wrapper {
+	int errorcode;
+	check_users_config config;
+} check_users_config_wrapper;
+check_users_config_wrapper process_arguments(int /*argc*/, char ** /*argv*/);
 
-static int process_arguments(int, char **);
-static void print_help(void);
+void print_help(void);
 void print_usage(void);
 
-static char *warning_range = NULL;
-static char *critical_range = NULL;
-static thresholds *thlds = NULL;
-
 int main(int argc, char **argv) {
-	int users = -1;
-	int result = STATE_UNKNOWN;
-#if HAVE_WTSAPI32_H
-	WTS_SESSION_INFO *wtsinfo;
-	DWORD wtscount;
-	DWORD index;
-#elif HAVE_UTMPX_H
-	struct utmpx *putmpx;
-#else
-	char input_buffer[MAX_INPUT_BUFFER];
-#endif
-
 	setlocale(LC_ALL, "");
 	bindtextdomain(PACKAGE, LOCALEDIR);
 	textdomain(PACKAGE);
@@ -83,133 +77,100 @@ int main(int argc, char **argv) {
 	/* Parse extra opts if any */
 	argv = np_extra_opts(&argc, argv, progname);
 
-	if (process_arguments(argc, argv) == ERROR) {
+	check_users_config_wrapper tmp_config = process_arguments(argc, argv);
+
+	if (tmp_config.errorcode == ERROR) {
 		usage4(_("Could not parse arguments"));
 	}
 
-	users = 0;
-
-#ifdef HAVE_LIBSYSTEMD
-	if (sd_booted() > 0) {
-		users = sd_get_sessions(NULL);
-	} else {
-#endif
-#if HAVE_WTSAPI32_H
-		if (!WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &wtsinfo, &wtscount)) {
-			printf(_("Could not enumerate RD sessions: %d\n"), GetLastError());
-			return STATE_UNKNOWN;
-		}
-
-		for (index = 0; index < wtscount; index++) {
-			LPTSTR username;
-			DWORD size;
-			int len;
-
-			if (!WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, wtsinfo[index].SessionId, WTSUserName, &username, &size)) {
-				continue;
-			}
-
-			len = lstrlen(username);
-
-			WTSFreeMemory(username);
-
-			if (len == 0) {
-				continue;
-			}
-
-			if (wtsinfo[index].State == WTSActive || wtsinfo[index].State == WTSDisconnected) {
-				users++;
-			}
-		}
+	check_users_config config = tmp_config.config;
 
-		WTSFreeMemory(wtsinfo);
-#elif HAVE_UTMPX_H
-	/* get currently logged users from utmpx */
-	setutxent();
-
-	while ((putmpx = getutxent()) != NULL) {
-		if (putmpx->ut_type == USER_PROCESS) {
-			users++;
-		}
-	}
-
-	endutxent();
+#ifdef _WIN32
+#	if HAVE_WTSAPI32_H
+	get_num_of_users_wrapper user_wrapper = get_num_of_users_windows();
+#	else
+#		error Did not find WTSAPI32
+#	endif // HAVE_WTSAPI32_H
 #else
-	/* run the command */
-	child_process = spopen(WHO_COMMAND);
-	if (child_process == NULL) {
-		printf(_("Could not open pipe: %s\n"), WHO_COMMAND);
-		return STATE_UNKNOWN;
-	}
-
-	child_stderr = fdopen(child_stderr_array[fileno(child_process)], "r");
-	if (child_stderr == NULL) {
-		printf(_("Could not open stderr for %s\n"), WHO_COMMAND);
-	}
-
-	while (fgets(input_buffer, MAX_INPUT_BUFFER - 1, child_process)) {
-		/* increment 'users' on all lines except total user count */
-		if (input_buffer[0] != '#') {
-			users++;
-			continue;
-		}
-
-		/* get total logged in users */
-		if (sscanf(input_buffer, _("# users=%d"), &users) == 1) {
-			break;
-		}
-	}
-
-	/* check STDERR */
-	if (fgets(input_buffer, MAX_INPUT_BUFFER - 1, child_stderr)) {
-		result = possibly_set(result, STATE_UNKNOWN);
+#	ifdef HAVE_LIBSYSTEMD
+	get_num_of_users_wrapper user_wrapper = get_num_of_users_systemd();
+#	elif HAVE_UTMPX_H
+	get_num_of_users_wrapper user_wrapper = get_num_of_users_utmp();
+#	else  // !HAVE_LIBSYSTEMD && !HAVE_UTMPX_H
+	get_num_of_users_wrapper user_wrapper = get_num_of_users_who_command();
+#	endif // HAVE_LIBSYSTEMD
+#endif     // _WIN32
+
+	mp_check overall = mp_check_init();
+	if (config.output_format_is_set) {
+		mp_set_format(config.output_format);
 	}
-	(void)fclose(child_stderr);
+	mp_subcheck sc_users = mp_subcheck_init();
 
-	/* close the pipe */
-	if (spclose(child_process)) {
-		result = possibly_set(result, STATE_UNKNOWN);
+	if (user_wrapper.errorcode != 0) {
+		sc_users = mp_set_subcheck_state(sc_users, STATE_UNKNOWN);
+		sc_users.output = "Failed to retrieve number of users";
+		mp_add_subcheck_to_check(&overall, sc_users);
+		mp_exit(overall);
 	}
-#endif
-#ifdef HAVE_LIBSYSTEMD
-	}
-#endif
-
 	/* check the user count against warning and critical thresholds */
-	result = get_status((double)users, thlds);
 
-	if (result == STATE_UNKNOWN) {
-		printf("%s\n", _("Unable to read output"));
-	} else {
-		printf(_("USERS %s - %d users currently logged in |%s\n"), state_text(result), users,
-			   sperfdata_int("users", users, "", warning_range, critical_range, true, 0, false, 0));
+	mp_perfdata users_pd = {
+		.label = "users",
+		.value = mp_create_pd_value(user_wrapper.users),
+	};
+
+	users_pd = mp_pd_set_thresholds(users_pd, config.thresholds);
+	mp_add_perfdata_to_subcheck(&sc_users, users_pd);
+
+	int tmp_status = mp_get_pd_status(users_pd);
+	sc_users = mp_set_subcheck_state(sc_users, tmp_status);
+
+	switch (tmp_status) {
+	case STATE_WARNING:
+		xasprintf(&sc_users.output, "%d users currently logged in. This violates the warning threshold", user_wrapper.users);
+		break;
+	case STATE_CRITICAL:
+		xasprintf(&sc_users.output, "%d users currently logged in. This violates the critical threshold", user_wrapper.users);
+		break;
+	default:
+		xasprintf(&sc_users.output, "%d users currently logged in", user_wrapper.users);
 	}
 
-	return result;
+	mp_add_subcheck_to_check(&overall, sc_users);
+	mp_exit(overall);
 }
 
+#define output_format_index CHAR_MAX + 1
+
 /* process command-line arguments */
-int process_arguments(int argc, char **argv) {
+check_users_config_wrapper process_arguments(int argc, char **argv) {
 	static struct option longopts[] = {{"critical", required_argument, 0, 'c'},
 									   {"warning", required_argument, 0, 'w'},
 									   {"version", no_argument, 0, 'V'},
 									   {"help", no_argument, 0, 'h'},
+									   {"output-format", required_argument, 0, output_format_index},
 									   {0, 0, 0, 0}};
 
 	if (argc < 2) {
-		usage("\n");
+		usage(progname);
 	}
 
-	int option_char;
+	char *warning_range = NULL;
+	char *critical_range = NULL;
+	check_users_config_wrapper result = {
+		.config = check_users_config_init(),
+		.errorcode = OK,
+	};
+
 	while (true) {
-		int option = 0;
-		option_char = getopt_long(argc, argv, "+hVvc:w:", longopts, &option);
+		int counter = getopt_long(argc, argv, "+hVvc:w:", longopts, NULL);
 
-		if (option_char == -1 || option_char == EOF || option_char == 1) {
+		if (counter == -1 || counter == EOF || counter == 1) {
 			break;
 		}
 
-		switch (option_char) {
+		switch (counter) {
 		case '?': /* print short usage statement if args not parsable */
 			usage5();
 		case 'h': /* help */
@@ -224,31 +185,45 @@ int process_arguments(int argc, char **argv) {
 		case 'w': /* warning */
 			warning_range = optarg;
 			break;
-		}
-	}
-
-	option_char = optind;
-
-	if (warning_range == NULL && argc > option_char) {
-		warning_range = argv[option_char++];
-	}
+		case output_format_index: {
+			parsed_output_format parser = mp_parse_output_format(optarg);
+			if (!parser.parsing_success) {
+				// TODO List all available formats here, maybe add anothoer usage function
+				printf("Invalid output format: %s\n", optarg);
+				exit(STATE_UNKNOWN);
+			}
 
-	if (critical_range == NULL && argc > option_char) {
-		critical_range = argv[option_char++];
+			result.config.output_format_is_set = true;
+			result.config.output_format = parser.output_format;
+			break;
+		}
+		}
 	}
 
-	/* this will abort in case of invalid ranges */
-	set_thresholds(&thlds, warning_range, critical_range);
-
-	if (!thlds->warning) {
-		usage4(_("Warning threshold must be a valid range expression"));
+	// TODO add proper verification for ranges here!
+	if (warning_range) {
+		mp_range_parsed tmp = mp_parse_range_string(warning_range);
+		if (tmp.error == MP_PARSING_SUCCES) {
+			result.config.thresholds.warning = tmp.range;
+			result.config.thresholds.warning_is_set = true;
+		} else {
+			printf("Failed to parse warning range: %s", warning_range);
+			exit(STATE_UNKNOWN);
+		}
 	}
 
-	if (!thlds->critical) {
-		usage4(_("Critical threshold must be a valid range expression"));
+	if (critical_range) {
+		mp_range_parsed tmp = mp_parse_range_string(critical_range);
+		if (tmp.error == MP_PARSING_SUCCES) {
+			result.config.thresholds.critical = tmp.range;
+			result.config.thresholds.critical_is_set = true;
+		} else {
+			printf("Failed to parse critical range: %s", critical_range);
+			exit(STATE_UNKNOWN);
+		}
 	}
 
-	return OK;
+	return result;
 }
 
 void print_help(void) {
@@ -271,6 +246,7 @@ void print_help(void) {
 	printf("    %s\n", _("Set WARNING status if number of logged in users violates RANGE_EXPRESSION"));
 	printf(" %s\n", "-c, --critical=RANGE_EXPRESSION");
 	printf("    %s\n", _("Set CRITICAL status if number of logged in users violates RANGE_EXPRESSION"));
+	printf(UT_OUTPUT_FORMAT);
 
 	printf(UT_SUPPORT);
 }
diff --git a/plugins/check_users.d/config.h b/plugins/check_users.d/config.h
new file mode 100644
index 00000000..26d3ee70
--- /dev/null
+++ b/plugins/check_users.d/config.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "output.h"
+#include "thresholds.h"
+
+typedef struct check_users_config {
+	mp_thresholds thresholds;
+
+	bool output_format_is_set;
+	mp_output_format output_format;
+} check_users_config;
+
+check_users_config check_users_config_init() {
+	check_users_config tmp = {
+		.thresholds = mp_thresholds_init(),
+
+		.output_format_is_set = false,
+	};
+	return tmp;
+}
diff --git a/plugins/check_users.d/users.c b/plugins/check_users.d/users.c
new file mode 100644
index 00000000..7969ae79
--- /dev/null
+++ b/plugins/check_users.d/users.c
@@ -0,0 +1,166 @@
+#include "./users.h"
+
+#ifdef _WIN32
+#	ifdef HAVE_WTSAPI32_H
+#		include <windows.h>
+#		include <wtsapi32.h>
+#		undef ERROR
+#		define ERROR -1
+
+get_num_of_users_wrapper get_num_of_users_windows() {
+	WTS_SESSION_INFO *wtsinfo;
+	DWORD wtscount;
+
+	get_num_of_users_wrapper result = {};
+
+	if (!WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &wtsinfo, &wtscount)) {
+		// printf(_("Could not enumerate RD sessions: %d\n"), GetLastError());
+		result.error = WINDOWS_COULD_NOT_ENUMERATE_SESSIONS;
+		return result;
+	}
+
+	for (DWORD index = 0; index < wtscount; index++) {
+		LPTSTR username;
+		DWORD size;
+
+		if (!WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, wtsinfo[index].SessionId, WTSUserName, &username, &size)) {
+			continue;
+		}
+
+		int len = lstrlen(username);
+
+		WTSFreeMemory(username);
+
+		if (len == 0) {
+			continue;
+		}
+
+		if (wtsinfo[index].State == WTSActive || wtsinfo[index].State == WTSDisconnected) {
+			result.users++;
+		}
+	}
+
+	WTSFreeMemory(wtsinfo);
+	return result;
+}
+#	else // HAVE_WTSAPI32_H
+#		error On windows but without the WTSAPI32 lib
+#	endif // HAVE_WTSAPI32_H
+
+#else // _WIN32
+
+#	include "../../config.h"
+
+#	ifdef HAVE_LIBSYSTEMD
+#		include <systemd/sd-daemon.h>
+#		include <systemd/sd-login.h>
+
+get_num_of_users_wrapper get_num_of_users_systemd() {
+	get_num_of_users_wrapper result = {};
+
+	// Test whether we booted with systemd
+	if (sd_booted() > 0) {
+		int users = sd_get_sessions(NULL);
+		if (users >= 0) {
+			// Success
+			result.users = users;
+			return result;
+		}
+
+		// Failure! return the error code
+		result.errorcode = users;
+		return result;
+	}
+
+	// Looks like we are not running systemd,
+	// return with error here
+	result.errorcode = NO_SYSTEMD_ERROR;
+	return result;
+}
+#	endif
+
+#	ifdef HAVE_UTMPX_H
+#		include <utmpx.h>
+
+get_num_of_users_wrapper get_num_of_users_utmp() {
+	int users = 0;
+
+	/* get currently logged users from utmpx */
+	setutxent();
+
+	struct utmpx *putmpx;
+	while ((putmpx = getutxent()) != NULL) {
+		if (putmpx->ut_type == USER_PROCESS) {
+			users++;
+		}
+	}
+
+	endutxent();
+
+	get_num_of_users_wrapper result = {
+		.errorcode = 0,
+		.users = users,
+	};
+
+	return result;
+}
+#	endif
+
+#	ifndef HAVE_WTSAPI32_H
+#		ifndef HAVE_LIBSYSTEMD
+#			ifndef HAVE_UTMPX_H
+//  Fall back option here for the others (probably still not on windows)
+
+#				include "../popen.h"
+#				include "../common.h"
+#				include "../utils.h"
+
+get_num_of_users_wrapper get_num_of_users_who_command() {
+	/* run the command */
+	child_process = spopen(WHO_COMMAND);
+	if (child_process == NULL) {
+		// printf(_("Could not open pipe: %s\n"), WHO_COMMAND);
+		get_num_of_users_wrapper result = {
+			.errorcode = COULD_NOT_OPEN_PIPE,
+		};
+		return result;
+	}
+
+	child_stderr = fdopen(child_stderr_array[fileno(child_process)], "r");
+	if (child_stderr == NULL) {
+		// printf(_("Could not open stderr for %s\n"), WHO_COMMAND);
+		//  TODO this error should probably be reported
+	}
+
+	get_num_of_users_wrapper result = {};
+	char input_buffer[MAX_INPUT_BUFFER];
+	while (fgets(input_buffer, MAX_INPUT_BUFFER - 1, child_process)) {
+		/* increment 'users' on all lines except total user count */
+		if (input_buffer[0] != '#') {
+			result.users++;
+			continue;
+		}
+
+		/* get total logged in users */
+		if (sscanf(input_buffer, _("# users=%d"), &result.users) == 1) {
+			break;
+		}
+	}
+
+	/* check STDERR */
+	if (fgets(input_buffer, MAX_INPUT_BUFFER - 1, child_stderr)) {
+		// if this fails, something broke and the result can not be relied upon or so is the theorie here
+		result.errorcode = STDERR_COULD_NOT_BE_READ;
+	}
+	(void)fclose(child_stderr);
+
+	/* close the pipe */
+	spclose(child_process);
+
+	return result;
+}
+
+#			endif
+#		endif
+#	endif
+#endif
diff --git a/plugins/check_users.d/users.h b/plugins/check_users.d/users.h
new file mode 100644
index 00000000..aacba775
--- /dev/null
+++ b/plugins/check_users.d/users.h
@@ -0,0 +1,18 @@
+#pragma once
+
+typedef struct get_num_of_users_wrapper {
+	int errorcode;
+	int users;
+} get_num_of_users_wrapper;
+
+enum {
+	NO_SYSTEMD_ERROR = 64,
+	WINDOWS_COULD_NOT_ENUMERATE_SESSIONS,
+	COULD_NOT_OPEN_PIPE,
+	STDERR_COULD_NOT_BE_READ,
+};
+
+get_num_of_users_wrapper get_num_of_users_systemd();
+get_num_of_users_wrapper get_num_of_users_utmp();
+get_num_of_users_wrapper get_num_of_users_windows();
+get_num_of_users_wrapper get_num_of_users_who_command();



More information about the Commits mailing list