diff options
| author | Přemysl Janouch <p.janouch@gmail.com> | 2017-07-05 03:09:23 +0200 | 
|---|---|---|
| committer | Přemysl Janouch <p.janouch@gmail.com> | 2017-07-05 03:22:24 +0200 | 
| commit | 5b334e4111794a0de32729f4ebfadba99a3f5dae (patch) | |
| tree | 27ae93003f0b7706b002c2bd9720367dffff072e | |
| parent | cca674789bbaa10f365ca8da87db6263b67af023 (diff) | |
| download | desktop-tools-5b334e4111794a0de32729f4ebfadba99a3f5dae.tar.gz desktop-tools-5b334e4111794a0de32729f4ebfadba99a3f5dae.tar.xz desktop-tools-5b334e4111794a0de32729f4ebfadba99a3f5dae.zip | |
priod: PoC skeleton
| -rw-r--r-- | CMakeLists.txt | 7 | ||||
| -rw-r--r-- | README.adoc | 10 | ||||
| -rw-r--r-- | priod.c | 335 | 
3 files changed, 347 insertions, 5 deletions
| diff --git a/CMakeLists.txt b/CMakeLists.txt index 22afdfd..07b0f51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,9 @@ target_link_libraries (input-switch ${project_libraries})  add_executable (fancontrol-ng fancontrol-ng.c)  target_link_libraries (fancontrol-ng ${project_libraries}) +add_executable (priod priod.c) +target_link_libraries (priod ${project_libraries}) +  if (WITH_GDM)  	include_directories (${gdm_INCLUDE_DIRS})  	add_executable (gdm-switch-user gdm-switch-user.c) @@ -75,11 +78,13 @@ install (FILES ${PROJECT_BINARY_DIR}/fancontrol-ng.service  install (FILES fancontrol-ng.conf.example  	DESTINATION ${CMAKE_INSTALL_DATADIR}/fancontrol-ng) +# TODO: priod is also going to need a systemd unit file +  if (WITH_GDM)  	install (TARGETS gdm-switch-user DESTINATION ${CMAKE_INSTALL_BINDIR})  endif (WITH_GDM) -install (TARGETS wmstatus brightness input-switch fancontrol-ng siprandom +install (TARGETS wmstatus brightness input-switch fancontrol-ng priod siprandom  	DESTINATION ${CMAKE_INSTALL_BINDIR})  install (PROGRAMS shellify DESTINATION ${CMAKE_INSTALL_BINDIR})  install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) diff --git a/README.adoc b/README.adoc index b1c8c06..63c919b 100644 --- a/README.adoc +++ b/README.adoc @@ -5,14 +5,16 @@ desktop-tools  'desktop-tools' is a collection of tools to run my desktop that might be useful  to other people as well: - - 'wmstatus' does literally everything my dwm doesn't but I'd like it to. It + - 'wmstatus' does literally everything my i3 doesn't but I'd like it to. It     includes PulseAudio volume management and hand-written NUT and MPD clients, -   all in the name of liberation from GPL-licensed software of course. - - 'brightness' allows me to change the brightness of w/e display device I have. - - 'input-switch' likewise switches the input source of external displays. +   all in the name of liberation from GPL-licensed software of course + - 'brightness' allows me to change the brightness of w/e display device I have + - 'input-switch' likewise switches the input source of external displays   - 'fancontrol-ng' is a clone of fancontrol that can handle errors on resume     from suspend instead of setting fans to maximum speed and quitting;     in general it doesn't handle everything the original does + - 'priod' sets CPU, I/O and OOM killer priorities for new processes according +   to configuration   - 'shellify' is a simple script that sets up a shell for commands like vgdb     and nmcli that are painfully lacking it   - 'gdm-switch-user' tells the running GDM daemon, if any, to show the switch @@ -0,0 +1,335 @@ +/* + * priod.c: process reprioritizing daemon + * + * Thanks to http://netsplit.com/the-proc-connector-and-socket-filters + * + * Copyright (c) 2017, Přemysl Janouch <p.janouch@gmail.com> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#define _GNU_SOURCE +#define LIBERTY_WANT_POLLER + +#include "config.h" +#undef PROGRAM_NAME +#define PROGRAM_NAME "priod" +#include "liberty/liberty.c" + +#include <linux/cn_proc.h> +#include <linux/netlink.h> +#include <linux/connector.h> + +#include <sys/resource.h> +#include <sys/syscall.h> + +// --- Main program ------------------------------------------------------------ + +struct app_context +{ +	struct poller poller;               ///< Poller +	bool polling;                       ///< The event loop is running + +	int proc_fd;                        ///< Proc connector FD +	struct poller_fd proc_event;        ///< Proc connector read event +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static void +log_message_custom (void *user_data, const char *quote, const char *fmt, +	va_list ap) +{ +	(void) user_data; +	FILE *stream = stdout; + +	// TODO: sd-daemon.h log level prefixes? +	fputs (quote, stream); +	vfprintf (stream, fmt, ap); +	fputs ("\n", stream); +} + +// --- Signals ----------------------------------------------------------------- + +static int g_signal_pipe[2];            ///< A pipe used to signal... signals + +static void +sigterm_handler (int signum) +{ +	(void) signum; + +	int original_errno = errno; +	if (write (g_signal_pipe[1], "", 1) == -1) +		soft_assert (errno == EAGAIN); +	errno = original_errno; +} + +static void +setup_signal_handlers (void) +{ +	if (pipe (g_signal_pipe) == -1) +		exit_fatal ("%s: %s", "pipe", strerror (errno)); + +	set_cloexec (g_signal_pipe[0]); +	set_cloexec (g_signal_pipe[1]); + +	// So that the pipe cannot overflow; it would make write() block within +	// the signal handler, which is something we really don't want to happen. +	// The same holds true for read(). +	set_blocking (g_signal_pipe[0], false); +	set_blocking (g_signal_pipe[1], false); + +	(void) signal (SIGPIPE, SIG_IGN); + +	struct sigaction sa; +	sa.sa_flags = SA_RESTART; +	sa.sa_handler = sigterm_handler; +	sigemptyset (&sa.sa_mask); + +	if (sigaction (SIGINT,  &sa, NULL) == -1 +	 || sigaction (SIGTERM, &sa, NULL) == -1) +		exit_fatal ("sigaction: %s", strerror (errno)); +} + +// --- Main program ------------------------------------------------------------ + +// IO priorities are a sort-of-private kernel API with no proper headers + +enum +{ +	IOPRIO_CLASS_NONE, +	IOPRIO_CLASS_RT, +	IOPRIO_CLASS_BE, +	IOPRIO_CLASS_IDLE, +}; + +enum +{ +	IOPRIO_WHO_PROCESS = 1, +	IOPRIO_WHO_PGRP, +	IOPRIO_WHO_USER, +}; + +#define IOPRIO_CLASS_SHIFT 13 + +static void +on_exec_name (struct app_context *ctx, int pid, const char *name) +{ +	print_status ("exec %d %s", pid, name); + +	if (true) +		return; + +	setpriority (PRIO_PROCESS, pid, 0 /* TODO -20..20 */); + +	// TODO: this is per thread, and there's an inherent race condition; +	//   keep going through /proc/%d/task and reprioritize all threads; +	//   stop trying after N-th try +	syscall (SYS_ioprio_set, IOPRIO_WHO_PROCESS, +		IOPRIO_CLASS_BE << IOPRIO_CLASS_SHIFT | 0 /* TODO 0..7 */); + +	char *path = xstrdup_printf ("/proc/%d/oom_score_adj", pid); +	struct error *e = NULL; +	// TODO: figure out the contents +	if (!write_file (path, "", 0, &e)) +	{ +		print_error ("%s", e->message); +		error_free (e); +	} +	free (path); +} + +static void +on_exec (struct app_context *ctx, int pid) +{ +	char *path = xstrdup_printf ("/proc/%d/cmdline", pid); +	struct str cmdline; +	str_init (&cmdline); + +	struct error *e = NULL; +	if (read_file (path, &cmdline, &e)) +		on_exec_name (ctx, pid, cmdline.str); +	else +	{ +		print_debug ("%s", e->message); +		error_free (e); +	} + +	free (path); +	str_free (&cmdline); +} + +static void +on_netlink_message (struct app_context *ctx, struct nlmsghdr *mh) +{ +	if (mh->nlmsg_type == NLMSG_ERROR +	 || mh->nlmsg_type == NLMSG_NOOP) +		return; + +	struct cn_msg *m = NLMSG_DATA (mh); +	if (m->id.idx != CN_IDX_PROC +	 || m->id.val != CN_VAL_PROC) +		return; + +	struct proc_event *e = (struct proc_event *) m->data; +	if (e->what == PROC_EVENT_EXEC) +		on_exec (ctx, e->event_data.exit.process_tgid); +} + +static void +on_event (const struct pollfd *pfd, struct app_context *ctx) +{ +	char buf[4096]; +	struct sockaddr_nl addr; +	while (true) +	{ +		socklen_t addr_len = sizeof addr; +		ssize_t len = recvfrom (pfd->fd, buf, sizeof buf, 0, +			(struct sockaddr *) &addr, &addr_len); +		if (len == 0) +			exit_fatal ("socket closed"); +		if (len < 0 && errno == EAGAIN) +			return; +		if (len < 0) +			exit_fatal ("recvfrom: %s", strerror (errno)); + +		// Make sure it comes from the kernel +		if (addr.nl_pid) +			continue; + +		for (struct nlmsghdr *mh = (struct nlmsghdr *) buf; +			NLMSG_OK (mh, len); mh = NLMSG_NEXT (mh, len)) +			on_netlink_message (ctx, mh); +	} +} + +static void +on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx) +{ +	char id = 0; +	(void) read (fd->fd, &id, 1); + +	ctx->polling = false; +} + +static const char * +parse_program_arguments (int argc, char **argv) +{ +	static const struct opt opts[] = +	{ +		{ 'd', "debug", NULL, 0, "run in debug mode" }, +		{ 'h', "help", NULL, 0, "display this help and exit" }, +		{ 'V', "version", NULL, 0, "output version information and exit" }, +		{ 0, NULL, NULL, 0, NULL } +	}; + +	struct opt_handler oh; +	opt_handler_init (&oh, argc, argv, opts, "CONFIG", "Fan controller."); + +	int c; +	while ((c = opt_handler_get (&oh)) != -1) +	switch (c) +	{ +	case 'd': +		g_debug_mode = true; +		break; +	case 'h': +		opt_handler_usage (&oh, stdout); +		exit (EXIT_SUCCESS); +	case 'V': +		printf (PROGRAM_NAME " " PROGRAM_VERSION "\n"); +		exit (EXIT_SUCCESS); +	default: +		print_error ("wrong options"); +		opt_handler_usage (&oh, stderr); +		exit (EXIT_FAILURE); +	} + +	argc -= optind; +	argv += optind; + +	if (argc != 1) +	{ +		opt_handler_usage (&oh, stderr); +		exit (EXIT_FAILURE); +	} + +	opt_handler_free (&oh); +	return argv[0]; +} + +int +main (int argc, char *argv[]) +{ +	g_log_message_real = log_message_custom; +	const char *config_path = parse_program_arguments (argc, argv); + +	struct app_context ctx; +	memset (&ctx, 0, sizeof ctx); +	poller_init (&ctx.poller); + +	setup_signal_handlers (); + +	struct poller_fd signal_event; +	poller_fd_init (&signal_event, &ctx.poller, g_signal_pipe[0]); +	signal_event.dispatcher = (poller_fd_fn) on_signal_pipe_readable; +	signal_event.user_data = &ctx; +	poller_fd_set (&signal_event, POLLIN); + +	// TODO: load configuration so that we know what to do with the events + +	ctx.proc_fd = socket (PF_NETLINK, +		SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, NETLINK_CONNECTOR); +	if (ctx.proc_fd < 0) +		exit_fatal ("cannot make a proc connector: %s", strerror (errno)); + +	struct sockaddr_nl addr = { .nl_family = AF_NETLINK, .nl_pid = getpid (), +		.nl_groups = CN_IDX_PROC }; +	if (bind (ctx.proc_fd, (struct sockaddr *) &addr, sizeof addr) < 0) +		exit_fatal ("cannot make a proc connector: %s", strerror (errno)); + +	struct +	{ +		// Beware of padding between fields, shouldn't be any on x86-64 +		union { struct nlmsghdr netlink; char align[NLMSG_HDRLEN]; }; +		struct cn_msg connector; +		enum proc_cn_mcast_op op; +	} +	subscription = +	{ +		.netlink.nlmsg_len = sizeof subscription, +		.netlink.nlmsg_type = NLMSG_DONE, +		.netlink.nlmsg_pid = getpid (), +		.connector.id.idx = CN_IDX_PROC, +		.connector.id.val = CN_VAL_PROC, +		.connector.len = sizeof subscription.op, +		.op = PROC_CN_MCAST_LISTEN, +	}; + +	if (write (ctx.proc_fd, &subscription, sizeof subscription) < 0) +		exit_fatal ("failed to subscribe for events: %s", strerror (errno)); + +	poller_fd_init (&ctx.proc_event, &ctx.poller, ctx.proc_fd); +	ctx.proc_event.dispatcher = (poller_fd_fn) on_event; +	ctx.proc_event.user_data = &ctx; +	poller_fd_set (&ctx.proc_event, POLLIN); + +	ctx.polling = true; +	while (ctx.polling) +		poller_run (&ctx.poller); + +	poller_free (&ctx.poller); +	xclose (ctx.proc_fd); +	return EXIT_SUCCESS; +} | 
