From 7866eee9c87c8890f05b41339ad225ae1de90779 Mon Sep 17 00:00:00 2001 From: Daniel Poelzleithner Date: Sun, 20 Feb 2011 10:03:40 +0100 Subject: [PATCH] implement simple rule file filter simplerules is a module that parses simple text files which allows the admin to configure simple rules without the need to write boilerplate lua code. The simplerules module is powerfull enough to add simple flags to everything that can be matched based on command line, executable path or basename of a process. --- conf/simple.conf | 19 +++ conf/simple.d/media.conf | 14 ++ modules/CMakeLists.txt | 2 +- modules/simplerules.c | 348 +++++++++++++++++++++++++++++++++++++++ rules/media.lua | 35 ---- 5 files changed, 382 insertions(+), 36 deletions(-) create mode 100644 conf/simple.conf create mode 100644 conf/simple.d/media.conf create mode 100644 modules/simplerules.c delete mode 100644 rules/media.lua diff --git a/conf/simple.conf b/conf/simple.conf new file mode 100644 index 0000000..8e957dd --- /dev/null +++ b/conf/simple.conf @@ -0,0 +1,19 @@ +# This are simple rule files that allow you to add flags to processes +# Each rule is a line +# +# [match] [flagename] [[attribute]=[value]|...] +# +# match: +# +# /full/path path starting with / are absolute exe paths +# basename matches the last part of the command +# re_exe:[regexp] regular expression used on the path of the executable +# re_cmd:[regexp] regular expression on full command line +# re_base:[regexp] regular expression on basename +# +# Example: +# /path/to/my/exe user.media +# python test.useless value=1 threshold=10212 +# +# See https://github.com/poelzi/ulatencyd/wiki/flags.md for the convention +# on the flag names diff --git a/conf/simple.d/media.conf b/conf/simple.d/media.conf new file mode 100644 index 0000000..9a93bad --- /dev/null +++ b/conf/simple.d/media.conf @@ -0,0 +1,14 @@ +# video players + +re_exe:/usr/bin/(g?)mplayer.* user.media +/usr/bin/xine user.media +re_exe:/usr/bin/(c?)vlc user.media + +# audio players +clementine user.media + + +# mixing processes +/usr/bin/jackd sched.rt +/usr/bin/pulseaudio sched.rt + diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 9911217..92aea69 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -9,7 +9,7 @@ macro(add_module LNAME) SET_TARGET_PROPERTIES(${LNAME} PROPERTIES COMPILE_FLAGS "${ADD_COMPILE_FLAGS}") endmacro(add_module) -add_module(test test.c) +add_module(simplerules simplerules.c) pkg_check_modules(XCB xcb) pkg_check_modules(XAU xau) diff --git a/modules/simplerules.c b/modules/simplerules.c new file mode 100644 index 0000000..a001fe5 --- /dev/null +++ b/modules/simplerules.c @@ -0,0 +1,348 @@ +/* + Copyright 2011 Daniel Poelzleithner + + This file is part of ulatencyd. + + ulatencyd is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + ulatencyd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ulatencyd. If not, see http://www.gnu.org/licenses/. +*/ +#define _GNU_SOURCE + + +#ifndef G_LOG_DOMAIN +#define G_LOG_DOMAIN "simplerules" +#endif + +#include "config.h" +#include "ulatency.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int simplerules_id; + +static GList *target_rules; + +struct simple_rule { + gid_t gid; + uid_t uid; + char *cmdline; + char *exe; + char *basename; + GRegex *re_exe; + GRegex *re_cmd; + GRegex *re_basename; + u_flag *template; +}; + + +int parse_line(char *line, int lineno) { + char **chunks; + GError *error = NULL; + gint chunk_len; + struct simple_rule *rule = NULL; + int i; + char *value, *key; + int tmp; + + + if(line[0] == '#') + return TRUE; + if(strlen(line) == 0) + return TRUE; + + g_shell_parse_argv(line, + &chunk_len, + &chunks, + &error); + if(error) { + g_warning("can't parse line %d: %s", lineno, error->message); + goto error; + } + + if(chunk_len && chunk_len < 2) { + g_warning("not enough arguments in line %d: %s", lineno, line); + goto error; + } + + rule = g_slice_new0(struct simple_rule); + + if(chunks[0][0] == '/') { + rule->exe = g_strdup(chunks[0]); + + } else if(!strncmp(chunks[0], "re_exe:", 7)) { + rule->re_exe = g_regex_new(chunks[0] + 7, G_REGEX_OPTIMIZE, 0, &error); + if(error && error->code) { + g_warning("Error compiling regular expression in %s: %s", chunks[0], error->message); + goto error; + } + } else if(!strncmp(chunks[0], "re_cmd:", 7)) { + rule->re_cmd = g_regex_new(chunks[0] + 7, G_REGEX_OPTIMIZE, 0, &error); + if(error && error->code) { + g_warning("Error compiling regular expression in %s: %s", chunks[0], error->message); + goto error; + } + } else if(!strncmp(chunks[0], "re_base:", 8)) { + rule->re_cmd = g_regex_new(chunks[0] + 7, G_REGEX_OPTIMIZE, 0, &error); + if(error && error->code) { + g_warning("Error compiling regular expression in %s: %s", chunks[0], error->message); + goto error; + } + } else { + rule->basename = g_strdup(chunks[0]); + } + rule->template = g_slice_new0(u_flag); + rule->template->name = g_strdup(chunks[1]); + + for(i = 2; chunks[i]; i++) { + key = chunks[i]; + value = strstr(chunks[i], "="); + + if(!value) { + g_error("invalid argument in line %d: '=' missing", lineno); + goto error; + } + // split by replacing = with null byte + *value = 0; + value++; + + if(strcmp(key, "reason") == 0) { + rule->template->reason = g_strdup(value); + } else if(strcmp(key, "timeout") == 0) { + rule->template->timeout = (time_t)atoll(value); + } else if(strcmp(key, "priority") == 0) { + rule->template->priority = (int32_t)atoi(value); + } else if(strcmp(key, "value") == 0) { + rule->template->value = (int64_t)atoll(value); + } else if(strcmp(key, "threshold") == 0) { + rule->template->threshold = (int64_t)atoll(value); + } else if(strcmp(key, "value") == 0) { + rule->template->value = (int64_t)atoll(value); + } else if(strcmp(key, "value") == 0) { + tmp = atoi(value); + rule->template->inherit = tmp; + } + } + + target_rules = g_list_append(target_rules, rule); + + g_strfreev(chunks); + return TRUE; +error: + g_slice_free(struct simple_rule, rule); + g_error_free(error); + return FALSE; + +} + + +int load_simple_file(const char *path) { + char *content, **lines, *line; + gsize length; + int i; + GError *error = NULL; + + if(!g_file_get_contents(path, + &content, + &length, + &error)) { + g_warning("can't load simple rule file %s: %s", path, error->message); + return FALSE; + } + + g_debug("load simple rule file: %s", path); + + printf("%s\n", content); + + lines = g_strsplit_set(content, "\n", -1); + for(i = 0; lines[i]; i++) { + line = lines[i]; + + parse_line(line, i+1); + + } + g_strfreev(lines); + g_free(content); + + return TRUE; +} + + +int load_simple_directory(char *path) { + char rpath[PATH_MAX+1]; + gsize disabled_len; + int i, j; + char **disabled; + char *rule_name = NULL; + struct stat sb; + + disabled = g_key_file_get_string_list(config_data, "simplerules", + "disabled_rules", &disabled_len, NULL); + + + g_message("load simple rules directory: %s", path); + + + struct dirent **namelist; + int n; + + n = scandir(path, &namelist, 0, versionsort); + if (n < 0) { + g_warning("cant't load directory %s", path); + return FALSE; + } else { + for(i = 0; i < n; i++) { + + if(fnmatch("*.conf", namelist[i]->d_name, 0)) + continue; + rule_name = g_strndup(namelist[i]->d_name,strlen(namelist[i]->d_name)-4); + + for(j = 0; j < disabled_len; j++) { + if(!g_strcasecmp(disabled[j], rule_name)) + goto skip; + } + + snprintf(rpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name); + if (stat(rpath, &sb) == -1) + goto skip; + if((sb.st_mode & S_IFMT) != S_IFREG) + goto next; + + load_simple_file(rpath); + + next: + g_free(rule_name); + rule_name = NULL; + + free(namelist[i]); + continue; + skip: + g_debug("skip rule: %s", namelist[i]->d_name); + g_free(rule_name); + rule_name = NULL; + + free(namelist[i]); + } + free(namelist); + } + return TRUE; +} + +void read_rules(void) { + load_simple_directory(QUOTEME(CONFIG_PATH)"/simple.d"); + load_simple_file(QUOTEME(CONFIG_PATH)"/simple.conf"); + + return; +} + +int rule_applies(u_proc *proc, struct simple_rule *rule) { + if(rule->cmdline) { + if(u_proc_ensure(proc, CMDLINE, FALSE) && + !strncmp(proc->cmdline_match, rule->cmdline, strlen(rule->cmdline))) { +// printf("cmdline %s %s\n", proc->cmdline_match, rule->cmdline); + return TRUE; + } + } + if(rule->basename) { + if(u_proc_ensure(proc, CMDLINE, FALSE) && + !strncmp(proc->cmdfile, rule->basename, strlen(rule->basename))) { +// printf("cmdfile %s %s\n", proc->cmdfile, rule->basename); + return TRUE; + } + } + if(rule->exe) { + if(u_proc_ensure(proc, EXE, FALSE) && + !strncmp(proc->exe, rule->exe, strlen(rule->exe))) { +// printf("exe %s %s\n", proc->exe, rule->exe); + return TRUE; + } + } + if(rule->re_exe) { + if(u_proc_ensure(proc, EXE, FALSE) && + g_regex_match(rule->re_exe, proc->exe, 0, NULL)) { +// printf("re_exe %s %p\n", proc->exe, rule->re_exe); + return TRUE; + } + } + if(rule->re_cmd) { + if(u_proc_ensure(proc, CMDLINE, FALSE) && + g_regex_match(rule->re_cmd, proc->cmdline_match, 0, NULL)) { +// printf("re_cmd %s %p\n", proc->cmdline_match, rule->re_cmd); + return TRUE; + } + } + if(rule->re_basename) { + if(u_proc_ensure(proc, CMDLINE, FALSE) && + g_regex_match(rule->re_basename, proc->cmdfile, 0, NULL)) { +// printf("re_base %s %p\n", proc->exe, rule->re_basename); + return TRUE; + } + } + return FALSE; +} + +void simple_add_flag(u_filter *filter, u_proc *proc, struct simple_rule *rule) { + u_flag *t = rule->template; + u_flag *nf = u_flag_new(filter, t->name); + + if(t->reason) + nf->reason = g_strdup(t->reason); + if(t->timeout) + nf->timeout = time(NULL) + t->timeout; + nf->priority = t->priority; + nf->value = t->value; + nf->threshold = t->threshold; + nf->inherit = t->inherit; + +// printf("add flag %s %d\n", nf->name, proc->pid); + + u_flag_add(proc, nf); +} + +int simplerules_run_proc(u_proc *proc, u_filter *filter) { + GList *cur = target_rules; + struct simple_rule *rule; + + while(cur) { + rule = cur->data; + + if(rule_applies(proc, rule)) { + simple_add_flag(filter, proc, rule); + } + cur = g_list_next(cur); + } + return FILTER_MIX(FILTER_STOP, 0); +} + + +int simplerules_init() { + simplerules_id = get_plugin_id(); + u_filter *filter; + target_rules = NULL; + read_rules(); + if(target_rules) { + filter = filter_new(); + filter->type = FILTER_C; + filter->name = g_strdup("simplerules"); + filter->callback = simplerules_run_proc; + filter_register(filter); + } + return 0; +} + diff --git a/rules/media.lua b/rules/media.lua deleted file mode 100644 index 77a4495..0000000 --- a/rules/media.lua +++ /dev/null @@ -1,35 +0,0 @@ ---[[ - Copyright 2010,2011 ulatencyd developers - - This file is part of ulatencyd. - - License: GNU General Public License 3 or later -]]-- - -MediaPlayer = { - name = "MediaPlayer", - re_cmdline = "mplayer|xine|vlc|clementine", - --re_basename = "metacity", - check = function(self, proc) - local flag = ulatency.new_flag("user.media") - proc:add_flag(flag) - rv = ulatency.filter_rv(ulatency.FILTER_STOP) - return rv - end, -} - - -Jackd = { - name = "Jackd", - re_cmdline = "jackd|pulseaudio", - --re_basename = "metacity", - check = function(self, proc) - local flag = ulatency.new_flag("sched.rt") - proc:add_flag(flag) - rv = ulatency.filter_rv(ulatency.FILTER_STOP) - return rv - end, -} - -ulatency.register_filter(Jackd) -ulatency.register_filter(MediaPlayer)