diff options
| author | Přemysl Janouch <p.janouch@gmail.com> | 2017-07-05 19:47:09 +0200 | 
|---|---|---|
| committer | Přemysl Janouch <p.janouch@gmail.com> | 2017-07-05 20:46:14 +0200 | 
| commit | 456c362811ce8a1eb06f154d70cb2dbb0266d3c4 (patch) | |
| tree | 2dfe221f3748bb3574554ce190b59a00346cd35d | |
| parent | 7713850987dea2472e0ad24d023c7e7b765ed60b (diff) | |
| download | desktop-tools-456c362811ce8a1eb06f154d70cb2dbb0266d3c4.tar.gz desktop-tools-456c362811ce8a1eb06f154d70cb2dbb0266d3c4.tar.xz desktop-tools-456c362811ce8a1eb06f154d70cb2dbb0266d3c4.zip | |
priod: finish basic operation
| -rw-r--r-- | priod.c | 190 | 
1 files changed, 171 insertions, 19 deletions
| @@ -2,6 +2,7 @@   * priod.c: process reprioritizing daemon   *   * Thanks to http://netsplit.com/the-proc-connector-and-socket-filters + * for showing the way around the proc connector and BPF.   *   * Copyright (c) 2017, Přemysl Janouch <p.janouch@gmail.com>   * @@ -27,6 +28,8 @@  #define PROGRAM_NAME "priod"  #include "liberty/liberty.c" +#include <dirent.h> +  #include <linux/cn_proc.h>  #include <linux/netlink.h>  #include <linux/connector.h> @@ -37,6 +40,17 @@  // --- Main program ------------------------------------------------------------ +#define RULE_UNSET INT_MIN + +struct rule +{ +	char *program_name;                 ///< Program name to match against + +	int oom_score_adj;                  ///< For /proc/%/oom_score_adj +	int prio;                           ///< For setpriority() +	int ioprio;                         ///< For SYS_ioprio_set +}; +  struct app_context  {  	struct poller poller;               ///< Poller @@ -44,6 +58,9 @@ struct app_context  	int proc_fd;                        ///< Proc connector FD  	struct poller_fd proc_event;        ///< Proc connector read event + +	struct rule *rules;                 ///< Rules +	size_t rules_len;                   ///< Number of rules  };  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -61,6 +78,77 @@ log_message_custom (void *user_data, const char *quote, const char *fmt,  	fputs ("\n", stream);  } +// --- Configuration ----------------------------------------------------------- + +static bool +load_integer (struct str_map *root, const char *key, int min, int max, +	int *value, struct error **e) +{ +	*value = RULE_UNSET; + +	struct config_item *item; +	if (!(item = str_map_find (root, key))) +		return true; + +	if (item->type != CONFIG_ITEM_INTEGER +	 || item->value.integer < min || item->value.integer > max) +		return error_set (e, "%s: must be an integer (%d..%d)", key, min, max); + +	*value = item->value.integer; +	return true; +} + +static bool +load_rule (const char *name, struct str_map *m, struct rule *r, +	struct error **e) +{ +	r->program_name = xstrdup (name); +	if (!load_integer (m, "oom_score_adj", -1000, 1000, &r->oom_score_adj, e) +	 || !load_integer (m, "prio",            -20,   19, &r->prio,          e) +	 || !load_integer (m, "ioprio",            0,    7, &r->ioprio,        e)) +		return false; +	return true; +} + +static struct rule * +find_rule (struct app_context *ctx, const char *program_name) +{ +	for (size_t i = 0; i < ctx->rules_len; i++) +		if (!strcmp (ctx->rules[i].program_name, program_name)) +			return ctx->rules + i; +	return NULL; +} + +static void +load_configuration (struct app_context *ctx, const char *config_path) +{ +	struct error *e = NULL; +	struct config_item *root = config_read_from_file (config_path, &e); + +	if (e) +	{ +		print_error ("error loading configuration: %s", e->message); +		error_free (e); +		exit (EXIT_FAILURE); +	} + +	struct str_map_iter iter; +	str_map_iter_init (&iter, &root->value.object); +	ctx->rules = xcalloc (iter.map->len, sizeof *ctx->rules); +	ctx->rules_len = 0; + +	struct config_item *subtree; +	while ((subtree = str_map_iter_next (&iter))) +	{ +		const char *path = iter.link->key; +		if (subtree->type != CONFIG_ITEM_OBJECT) +			exit_fatal ("rule `%s' in configuration is not an object", path); +		if (!load_rule (path, &subtree->value.object, +			&ctx->rules[ctx->rules_len++], &e)) +			exit_fatal ("rule `%s': %s", path, e->message); +	} +} +  // --- Signals -----------------------------------------------------------------  static int g_signal_pipe[2];            ///< A pipe used to signal... signals @@ -125,35 +213,95 @@ enum  #define IOPRIO_CLASS_SHIFT 13  static void -on_exec_name (struct app_context *ctx, int pid, const char *name) +adj_oom_score (int pid, const char *program_name, int score)  { -	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 buf[16]; snprintf (buf, sizeof buf, "%d\n", score);  	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)) +	if (!write_file (path, buf, strlen (buf), &e))  	{ -		print_error ("%s", e->message); +		print_error ("%d (%s): %s", pid, program_name, e->message);  		error_free (e);  	}  	free (path);  } +static bool +reprioritize (int pid, const char *program_name, DIR *dir, struct rule *rule, +	struct str_map *set) +{ +	size_t not_previously_visited = 0; +	struct dirent *iter; +	while ((errno = 0, iter = readdir (dir))) +	{ +		int tid = atoi (iter->d_name); +		if (!tid || str_map_find (set, iter->d_name)) +			continue; + +		print_debug (" - thread %d", tid); +		str_map_set (set, iter->d_name, (void *) ++not_previously_visited); + +		if (RULE_UNSET != rule->prio +		 && setpriority (PRIO_PROCESS, pid, rule->prio)) +			print_error ("%d (%s): thread %d: setpriority: %s", +				pid, program_name, tid, strerror (errno)); +		if (RULE_UNSET != rule->ioprio +		 && syscall (SYS_ioprio_set, IOPRIO_WHO_PROCESS, tid, +				IOPRIO_CLASS_BE << IOPRIO_CLASS_SHIFT | rule->ioprio)) +			print_error ("%d (%s): thread %d: ioprio_set: %s", +				pid, program_name, tid, strerror (errno)); +	} +	if (errno) +	{ +		print_error ("%d (%s): readdir: %s", +			pid, program_name, strerror (errno)); +	} +	return not_previously_visited == 0; +} + +static void +on_exec_name (struct app_context *ctx, int pid, const char *program_name) +{ +	// TODO: we might want to at least provide more criteria to match on, +	//   so as to not blindly trust everything, despite these priorities being +	//   relatively harmless if you overlook possible "denial of service" +	struct rule *rule = find_rule (ctx, program_name); +	const char *slash = strrchr (program_name, '/'); +	if (!rule && (!slash || !(rule = find_rule (ctx, slash + 1)))) +		return; + +	print_debug ("%d (%s) matched", pid, program_name); +	if (RULE_UNSET != rule->oom_score_adj) +		adj_oom_score (pid, program_name, rule->oom_score_adj); + +	// Priority APIs are strictly per-thread (i.e. Linux "task"), so we must +	// iterate through all tasks within a thread group +	char *path = xstrdup_printf ("/proc/%d/task", pid); +	DIR *dir = opendir (path); +	free (path); +	if (!dir) +	{ +		print_error ("%d (%s): opendir: %s", +			pid, program_name, strerror (errno)); +		return; +	} + +	struct str_map set; +	str_map_init (&set); + +	// This has an inherent race condition, but let's give it a try +	for (size_t retries = 3; retries--; ) +		if (reprioritize (pid, program_name, dir, rule, &set)) +			break; + +	str_map_free (&set); +	closedir (dir); +} +  static void  on_exec (struct app_context *ctx, int pid)  { +	// This is inherently racy but there seems to be no better way to do it  	char *path = xstrdup_printf ("/proc/%d/cmdline", pid);  	struct str cmdline;  	str_init (&cmdline); @@ -240,7 +388,8 @@ parse_program_arguments (int argc, char **argv)  	};  	struct opt_handler oh; -	opt_handler_init (&oh, argc, argv, opts, "CONFIG", "Fan controller."); +	opt_handler_init (&oh, argc, argv, opts, "CONFIG", +		"Process reprioritizing daemon.");  	int c;  	while ((c = opt_handler_get (&oh)) != -1) @@ -334,7 +483,7 @@ main (int argc, char *argv[])  	signal_event.user_data = &ctx;  	poller_fd_set (&signal_event, POLLIN); -	// TODO: load configuration so that we know what to do with the events +	load_configuration (&ctx, config_path);  	ctx.proc_fd = socket (PF_NETLINK,  		SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, NETLINK_CONNECTOR); @@ -379,5 +528,8 @@ main (int argc, char *argv[])  	poller_free (&ctx.poller);  	xclose (ctx.proc_fd); +	for (size_t i = 0; i < ctx.rules_len; i++) +		free (ctx.rules[i].program_name); +	free (ctx.rules);  	return EXIT_SUCCESS;  } | 
