priod: finish basic operation
This commit is contained in:
		
							parent
							
								
									7713850987
								
							
						
					
					
						commit
						456c362811
					
				
							
								
								
									
										190
									
								
								priod.c
									
									
									
									
									
								
							
							
						
						
									
										190
									
								
								priod.c
									
									
									
									
									
								
							| @ -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; | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user