From 19fe2f5a6f56c85a4ca0496fd36a84157811db37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 25 Oct 2020 15:20:28 +0100 Subject: [PATCH 01/51] module/script: wip: new plugin, reads data from a user provided script/binary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This module exec’s a script (or binary), specified by the ‘path’ attribute in the configuration. It then reads tags from the script’s stdout. The format of the output is: tag|type|value tag|type|value I.e. the script writes N tags followed by an empty line. This constitutes a transaction. When a new transaction is received, its tags replaces *all* previous tags. --- modules/meson.build | 1 + modules/script.c | 486 ++++++++++++++++++++++++++++++++++++++++++++ plugin.c | 2 + 3 files changed, 489 insertions(+) create mode 100644 modules/script.c diff --git a/modules/meson.build b/modules/meson.build index e05362f..ed1683e 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -19,6 +19,7 @@ deps = { 'mpd': [[], [mpd]], 'network': [[], []], 'removables': [[], [dynlist, udev]], + 'script': [[], []], 'sway_xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json]], } diff --git a/modules/script.c b/modules/script.c new file mode 100644 index 0000000..840578e --- /dev/null +++ b/modules/script.c @@ -0,0 +1,486 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#define LOG_MODULE "script" +#define LOG_ENABLE_DBG 1 +#include "../log.h" +#include "../config.h" +#include "../config-verify.h" +#include "../module.h" +#include "../plugin.h" + +struct private { + char *path; + struct particle *content; + + struct tag_set tags; + + struct { + char *data; + size_t sz; + size_t idx; + } recv_buf; +}; + +static void +destroy(struct module *mod) +{ + struct private *m = mod->private; + m->content->destroy(m->content); + tag_set_destroy(&m->tags); + free(m->recv_buf.data); + free(m->path); + free(m); + module_default_destroy(mod); +} + +static struct exposable * +content(struct module *mod) +{ + const struct private *m = mod->private; + + mtx_lock(&mod->lock); + struct exposable *e = m->content->instantiate(m->content, &m->tags); + mtx_unlock(&mod->lock); + + return e; +} + +static struct tag * +process_line(struct module *mod, const char *line, size_t len) +{ + const char *_name = line; + LOG_INFO("LINE: %.*s", (int)len, line); + + const char *type = memchr(line, '|', len); + if (type == NULL) + goto bad_tag; + + size_t name_len = type - _name; + type++; + + const char *_value = memchr(type, '|', len - name_len - 1); + if (_value == NULL) + goto bad_tag; + + size_t type_len = _value - type; + _value++; + + size_t value_len = line + len - _value; + + LOG_DBG("%.*s: name=\"%.*s\", type=\"%.*s\", value=\"%.*s\"", + (int)len, line, + (int)name_len, _name, (int)type_len, type, (int)value_len, _value); + + char *name = malloc(name_len + 1); + memcpy(name, _name, name_len); + name[name_len] = '\0'; + + struct tag *tag = NULL; + + if (type_len == 6 && memcmp(type, "string", 6) == 0) + tag = tag_new_string(mod, name, _value); + + else if (type_len == 3 && memcmp(type, "int", 3) == 0) { + long value = strtol(_value, NULL, 0); + tag = tag_new_int(mod, name, value); + } + + else if (type_len == 4 && memcmp(type, "bool", 4) == 0) { + bool value = strtol(_value, NULL, 0); + tag = tag_new_bool(mod, name, value); + } + + else if (type_len == 5 && memcmp(type, "float", 5) == 0) { + double value = strtod(_value, NULL); + tag = tag_new_float(mod, name, value); + } + + else if ((type_len > 6 && memcmp(type, "range:", 6) == 0) || + (type_len > 9 && memcmp(type, "realtime:", 9 == 0))) + { + const char *_start = type + 6; + const char *split = memchr(_start, '-', type_len - 6); + + if (split == NULL || split == _start || (split + 1) - type >= type_len) { + free(name); + goto bad_tag; + } + + const char *_end = split + 1; + + size_t start_len = split - _start; + size_t end_len = type + type_len - _end; + + long start = 0; + for (size_t i = 0; i < start_len; i++) { + if (!(_start[i] >= '0' && _start[i] <= '9')) { + free(name); + goto bad_tag; + } + + start *= 10; + start |= _start[i] - '0'; + } + + long end = 0; + for (size_t i = 0; i < end_len; i++) { + if (!(_end[i] >= '0' && _end[i] < '9')) { + free(name); + goto bad_tag; + } + + end *= 10; + end |= _end[i] - '0'; + } + + if (type_len > 9 && memcmp(type, "realtime:", 9) == 0) { + free(name); + LOG_WARN("unimplemented: realtime tag"); + goto bad_tag; + } + + long value = strtol(_value, NULL, 0); + tag = tag_new_int_range(mod, name, value, start, end); + } + + else { + free(name); + goto bad_tag; + } + + free(name); + return tag; + +bad_tag: + LOG_ERR("invalid: %.*s", (int)len, line); + return NULL; +} + +static void +process_transaction(struct module *mod, size_t size) +{ + struct private *m = mod->private; + mtx_lock(&mod->lock); + + size_t left = size; + const char *line = m->recv_buf.data; + + size_t line_count = 0; + { + const char *p = line; + while ((p = memchr(p, '\n', size - (p - line))) != NULL) { + p++; + line_count++; + } + } + + tag_set_destroy(&m->tags); + m->tags.tags = calloc(line_count, sizeof(m->tags.tags[0])); + m->tags.count = line_count; + + size_t idx = 0; + + while (left > 0) { + char *line_end = memchr(line, '\n', left); + assert(line_end != NULL); + + size_t line_len = line_end - line; + + struct tag *tag = process_line(mod, line, line_len); + if (tag != NULL) + m->tags.tags[idx++] = tag; + + left -= line_len + 1; + line += line_len + 1; + } + + m->tags.count = idx; + + mtx_unlock(&mod->lock); + mod->bar->refresh(mod->bar); +} + +static bool +data_received(struct module *mod, const char *data, size_t len) +{ + struct private *m = mod->private; + + if (len > m->recv_buf.sz - m->recv_buf.idx) { + size_t new_sz = m->recv_buf.sz == 0 ? 1024 : m->recv_buf.sz * 2; + char *new_buf = realloc(m->recv_buf.data, new_sz); + + if (new_buf == NULL) + return false; + + m->recv_buf.data = new_buf; + m->recv_buf.sz = new_sz; + } + + assert(m->recv_buf.sz >= m->recv_buf.idx); + assert(m->recv_buf.sz - m->recv_buf.idx >= len); + + memcpy(&m->recv_buf.data[m->recv_buf.idx], data, len); + m->recv_buf.idx += len; + + const char *eot = memmem(m->recv_buf.data, m->recv_buf.idx, "\n\n", 2); + if (eot == NULL) { + /* End of transaction not yet available */ + return true; + } + + const size_t transaction_size = eot - m->recv_buf.data + 1; + process_transaction(mod, transaction_size); + + assert(m->recv_buf.idx >= transaction_size + 1); + memmove(m->recv_buf.data, + &m->recv_buf.data[transaction_size + 1], + m->recv_buf.idx - (transaction_size + 1)); + m->recv_buf.idx -= transaction_size + 1; + + return true; +} + +static int +run_loop(struct module *mod, int comm_fd) +{ + //struct private *m = mod; + + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + + /* Block normal signal handling - we're using a signalfd instead */ + sigset_t original_mask; + if (pthread_sigmask(SIG_BLOCK, &mask, &original_mask) < 0) { + LOG_ERRNO("failed to block SIGCHLD"); + return -1; + } + + int sig_fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); + if (sig_fd < 0) { + LOG_ERRNO("failed to create signal FD"); + pthread_sigmask(SIG_SETMASK, &original_mask, NULL); + return -1; + } + + int ret = 0; + + while (true) { + struct pollfd fds[] = { + {.fd = mod->abort_fd, .events = POLLIN}, + {.fd = sig_fd, .events = POLLIN}, + {.fd = comm_fd, .events = POLLIN}, + }; + + int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); + if (r < 0) { + if (errno == EINTR) + continue; + LOG_ERRNO("failed to poll"); + break; + } + + if (fds[2].revents & POLLIN) { + char data[4096]; + ssize_t amount = read(comm_fd, data, sizeof(data)); + if (amount < 0) { + LOG_ERRNO("failed to read from script"); + break; + } + + data_received(mod, data, amount); + } + + if (fds[0].revents & POLLHUP) { + /* Aborted */ + break; + } + + if (fds[1].revents & POLLHUP) { + LOG_ERR("signal FD closed unexpectedly"); + ret = 1; + break; + } + + if (fds[2].revents & POLLHUP) { + /* Child's stdout closed */ + break; + } + + if (fds[0].revents & POLLIN) + break; + + if (fds[1].revents & POLLIN) { + struct signalfd_siginfo info; + ssize_t amount = read(sig_fd, &info, sizeof(info)); + + if (amount < 0) { + LOG_ERRNO("failed to read from signal FD"); + break; + } + + assert(info.ssi_signo == SIGCHLD); + LOG_WARN("script died"); + break; + } + } + + close(sig_fd); + pthread_sigmask(SIG_SETMASK, &original_mask, NULL); + return ret; +} + +static int +run(struct module *mod) +{ + struct private *m = mod->private; + + int exec_pipe[2]; + if (pipe2(exec_pipe, O_CLOEXEC) < 0) { + LOG_ERRNO("failed to create pipe"); + return -1; + } + + int comm_pipe[2]; + if (pipe(comm_pipe) < 0) { + LOG_ERRNO("failed to create stdin/stdout redirection pipe"); + close(exec_pipe[0]); + close(exec_pipe[1]); + return -1; + } + + int pid = fork(); + if (pid < 0) { + LOG_ERRNO("failed to fork"); + close(comm_pipe[0]); + close(comm_pipe[1]); + close(exec_pipe[0]); + close(exec_pipe[1]); + return -1; + } + + if (pid == 0) { + /* Child */ + + setsid(); + setpgid(0, 0); + + /* Close pipe read ends */ + close(exec_pipe[0]); + close(comm_pipe[0]); + + /* Re-direct stdin/stdout/stderr */ + int dev_null = open("/dev/null", O_RDWR); + if (dev_null < 0) + goto fail; + + if (dup2(dev_null, STDIN_FILENO) < 0 || + dup2(dev_null, STDERR_FILENO) < 0 || + dup2(comm_pipe[1], STDOUT_FILENO) < 0) + { + goto fail; + } + + close(comm_pipe[1]); + + char *const argv[] = {NULL}; + execvp(m->path, argv); + + fail: + write(exec_pipe[1], &errno, sizeof(errno)); + close(exec_pipe[1]); + close(comm_pipe[1]); + _exit(errno); + } + + /* Close pipe write ends */ + close(exec_pipe[1]); + close(comm_pipe[1]); + + int _errno; + static_assert(sizeof(_errno) == sizeof(errno), "errno size mismatch"); + + int r = read(exec_pipe[0], &_errno, sizeof(_errno)); + close(exec_pipe[0]); + + if (r < 0) { + LOG_ERRNO("failed to read from pipe"); + return -1; + } + + if (r > 0) { + LOG_ERRNO_P("%s: failed to start", _errno, m->path); + waitpid(pid, NULL, 0); + return -1; + } + + LOG_WARN("child running under PID=%u", pid); + + int ret = run_loop(mod, comm_pipe[0]); + + close(comm_pipe[0]); + if (waitpid(pid, NULL, WNOHANG) == 0) { + LOG_WARN("sending SIGTERM to PGRP=%u", pid); + killpg(pid, SIGTERM); + + /* TODO: send SIGKILL after X seconds */ + waitpid(pid, NULL, 0); + } + + return ret; +} + +static struct module * +script_new(const char *path, struct particle *_content) +{ + struct private *m = calloc(1, sizeof(*m)); + m->path = strdup(path); + m->content = _content; + + struct module *mod = module_common_new(); + mod->private = m; + mod->run = &run; + mod->destroy = &destroy; + mod->content = &content; + return mod; +} + +static struct module * +from_conf(const struct yml_node *node, struct conf_inherit inherited) +{ + const struct yml_node *run = yml_get_value(node, "path"); + const struct yml_node *c = yml_get_value(node, "content"); + return script_new(yml_value_as_string(run), conf_to_particle(c, inherited)); +} + +static bool +verify_conf(keychain_t *chain, const struct yml_node *node) +{ + static const struct attr_info attrs[] = { + {"path", true, &conf_verify_string}, + MODULE_COMMON_ATTRS, + }; + + return conf_verify_dict(chain, node, attrs); +} + +const struct module_iface module_script_iface = { + .verify_conf = &verify_conf, + .from_conf = &from_conf, +}; + +#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) +extern const struct module_iface iface __attribute__((weak, alias("module_script_iface"))); +#endif diff --git a/plugin.c b/plugin.c index 7ae2fb4..05a2c1a 100644 --- a/plugin.c +++ b/plugin.c @@ -43,6 +43,7 @@ EXTERN_MODULE(network); EXTERN_MODULE(removables); EXTERN_MODULE(river); EXTERN_MODULE(sway_xkb); +EXTERN_MODULE(script); EXTERN_MODULE(xkb); EXTERN_MODULE(xwindow); @@ -119,6 +120,7 @@ init(void) REGISTER_CORE_MODULE(river, river); #endif REGISTER_CORE_MODULE(sway-xkb, sway_xkb); + REGISTER_CORE_MODULE(script, script); #if defined(HAVE_PLUGIN_xkb) REGISTER_CORE_MODULE(xkb, xkb); #endif From 99aa8dea820229c1d365b648fa08e9059f28e2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 25 Oct 2020 15:24:06 +0100 Subject: [PATCH 02/51] main: block SIGCHLD globally This allows threads (modules) to create signal FDs for SIGCHLD. --- main.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/main.c b/main.c index d93b869..61a89f1 100644 --- a/main.c +++ b/main.c @@ -279,6 +279,11 @@ main(int argc, char *const *argv) sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); + sigset_t proc_signal_mask; + sigemptyset(&proc_signal_mask); + sigaddset(&proc_signal_mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &proc_signal_mask, NULL); + /* Block SIGINT (this is under the assumption that threads inherit * the signal mask */ sigset_t signal_mask; @@ -326,7 +331,7 @@ main(int argc, char *const *argv) thrd_t bar_thread; thrd_create(&bar_thread, (int (*)(void *))bar->run, bar); - /* Now unblock. We should be only thread receiving SIGINT */ + /* Now unblock. We should be only thread receiving SIGINT/SIGTERM */ pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL); if (pid_file != NULL) { From 80d0025e6480bab31c7db10566acdd3bdd6b019b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 25 Oct 2020 15:41:02 +0100 Subject: [PATCH 03/51] module/script: drop setsid() call --- modules/script.c | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/script.c b/modules/script.c index 840578e..c27633f 100644 --- a/modules/script.c +++ b/modules/script.c @@ -374,7 +374,6 @@ run(struct module *mod) if (pid == 0) { /* Child */ - setsid(); setpgid(0, 0); /* Close pipe read ends */ From fbaa208768d9294cc77ba587504cb06bb11183cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 25 Oct 2020 16:04:17 +0100 Subject: [PATCH 04/51] module/script: disable debug output --- modules/script.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/script.c b/modules/script.c index c27633f..9e5babe 100644 --- a/modules/script.c +++ b/modules/script.c @@ -13,7 +13,7 @@ #include #define LOG_MODULE "script" -#define LOG_ENABLE_DBG 1 +#define LOG_ENABLE_DBG 0 #include "../log.h" #include "../config.h" #include "../config-verify.h" From 430e505bd24f04d4667d01f6da84f04bcdce2bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 25 Oct 2020 16:04:47 +0100 Subject: [PATCH 05/51] =?UTF-8?q?module/script:=20remove=20debug=20output?= =?UTF-8?q?=20that=20wasn=E2=80=99t=20actually=20using=20LOG=5FDBG()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/script.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/script.c b/modules/script.c index 9e5babe..ae92b88 100644 --- a/modules/script.c +++ b/modules/script.c @@ -61,7 +61,6 @@ static struct tag * process_line(struct module *mod, const char *line, size_t len) { const char *_name = line; - LOG_INFO("LINE: %.*s", (int)len, line); const char *type = memchr(line, '|', len); if (type == NULL) @@ -425,13 +424,12 @@ run(struct module *mod) return -1; } - LOG_WARN("child running under PID=%u", pid); + LOG_DBG("script running under PID=%u", pid); int ret = run_loop(mod, comm_pipe[0]); close(comm_pipe[0]); if (waitpid(pid, NULL, WNOHANG) == 0) { - LOG_WARN("sending SIGTERM to PGRP=%u", pid); killpg(pid, SIGTERM); /* TODO: send SIGKILL after X seconds */ From f2814f786e18210b013c0a3d8a1e9bc191a17969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 25 Oct 2020 16:05:20 +0100 Subject: [PATCH 06/51] =?UTF-8?q?module/script:=20copy=20=E2=80=98value?= =?UTF-8?q?=E2=80=99=20to=20a=20NULL-terminated=20string?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This ensures e.g. strtol() doesn’t parse data beyond current tag/value. --- modules/script.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/modules/script.c b/modules/script.c index ae92b88..2235083 100644 --- a/modules/script.c +++ b/modules/script.c @@ -60,6 +60,9 @@ content(struct module *mod) static struct tag * process_line(struct module *mod, const char *line, size_t len) { + char *name = NULL; + char *value = NULL; + const char *_name = line; const char *type = memchr(line, '|', len); @@ -82,10 +85,14 @@ process_line(struct module *mod, const char *line, size_t len) (int)len, line, (int)name_len, _name, (int)type_len, type, (int)value_len, _value); - char *name = malloc(name_len + 1); + name = malloc(name_len + 1); memcpy(name, _name, name_len); name[name_len] = '\0'; + value = malloc(value_len + 1); + memcpy(value, _value, value_len); + value[value_len] = '\0'; + struct tag *tag = NULL; if (type_len == 6 && memcmp(type, "string", 6) == 0) @@ -112,10 +119,8 @@ process_line(struct module *mod, const char *line, size_t len) const char *_start = type + 6; const char *split = memchr(_start, '-', type_len - 6); - if (split == NULL || split == _start || (split + 1) - type >= type_len) { - free(name); + if (split == NULL || split == _start || (split + 1) - type >= type_len) goto bad_tag; - } const char *_end = split + 1; @@ -124,10 +129,8 @@ process_line(struct module *mod, const char *line, size_t len) long start = 0; for (size_t i = 0; i < start_len; i++) { - if (!(_start[i] >= '0' && _start[i] <= '9')) { - free(name); + if (!(_start[i] >= '0' && _start[i] <= '9')) goto bad_tag; - } start *= 10; start |= _start[i] - '0'; @@ -135,17 +138,14 @@ process_line(struct module *mod, const char *line, size_t len) long end = 0; for (size_t i = 0; i < end_len; i++) { - if (!(_end[i] >= '0' && _end[i] < '9')) { - free(name); + if (!(_end[i] >= '0' && _end[i] < '9')) goto bad_tag; - } end *= 10; end |= _end[i] - '0'; } if (type_len > 9 && memcmp(type, "realtime:", 9) == 0) { - free(name); LOG_WARN("unimplemented: realtime tag"); goto bad_tag; } @@ -155,15 +155,17 @@ process_line(struct module *mod, const char *line, size_t len) } else { - free(name); goto bad_tag; } free(name); + free(value); return tag; bad_tag: LOG_ERR("invalid: %.*s", (int)len, line); + free(name); + free(value); return NULL; } From c911d20e73d273f9f2506a3abb63316abab59554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 25 Oct 2020 16:06:01 +0100 Subject: [PATCH 07/51] module/script: add debug logging of raw received data --- modules/script.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/script.c b/modules/script.c index 2235083..25323eb 100644 --- a/modules/script.c +++ b/modules/script.c @@ -301,6 +301,8 @@ run_loop(struct module *mod, int comm_fd) break; } + LOG_DBG("recv: \"%.*s\"", (int)amount, data); + data_received(mod, data, amount); } From 8702378c745980db947bb8d93f3fb2502f142055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 25 Oct 2020 16:06:16 +0100 Subject: [PATCH 08/51] module/script: restore signal handlers and mask in child process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes the issue where `killpg()` didn’t manage to kill the sub-process tree. --- modules/script.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/modules/script.c b/modules/script.c index 25323eb..5c04e70 100644 --- a/modules/script.c +++ b/modules/script.c @@ -377,6 +377,20 @@ run(struct module *mod) if (pid == 0) { /* Child */ + /* Restore signal handlers */ + + sigset_t mask; + sigemptyset(&mask); + + const struct sigaction sa = {.sa_handler = SIG_DFL}; + if (sigaction(SIGINT, &sa, NULL) < 0 || + sigaction(SIGTERM, &sa, NULL) < 0 || + sigaction(SIGCHLD, &sa, NULL) < 0 || + sigprocmask(SIG_SETMASK, &mask, NULL) < 0) + { + goto fail; + } + setpgid(0, 0); /* Close pipe read ends */ From 08bac77907d71acc366e5617ae2b9e7603b71d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 25 Oct 2020 18:29:52 +0100 Subject: [PATCH 09/51] module/script: script arguments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add new config attribute, ‘args’. This is a list of strings, that will be passed as arguments to the script. --- modules/script.c | 47 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/modules/script.c b/modules/script.c index 5c04e70..024654d 100644 --- a/modules/script.c +++ b/modules/script.c @@ -22,6 +22,9 @@ struct private { char *path; + size_t argc; + char **argv; + struct particle *content; struct tag_set tags; @@ -39,6 +42,10 @@ destroy(struct module *mod) struct private *m = mod->private; m->content->destroy(m->content); tag_set_destroy(&m->tags); + + for (size_t i = 0; i < m->argc; i++) + free(m->argv[i]); + free(m->argv); free(m->recv_buf.data); free(m->path); free(m); @@ -377,8 +384,13 @@ run(struct module *mod) if (pid == 0) { /* Child */ - /* Restore signal handlers */ + char *argv[1 + m->argc + 1]; + argv[0] = m->path; + for (size_t i = 0; i < m->argc; i++) + argv[i + 1] = m->argv[i]; + argv[1 + m->argc] = NULL; + /* Restore signal handlers */ sigset_t mask; sigemptyset(&mask); @@ -411,7 +423,6 @@ run(struct module *mod) close(comm_pipe[1]); - char *const argv[] = {NULL}; execvp(m->path, argv); fail: @@ -458,11 +469,16 @@ run(struct module *mod) } static struct module * -script_new(const char *path, struct particle *_content) +script_new(const char *path, size_t argc, const char *const argv[static argc], + struct particle *_content) { struct private *m = calloc(1, sizeof(*m)); m->path = strdup(path); m->content = _content; + m->argc = argc; + m->argv = malloc(argc * sizeof(m->argv[0])); + for (size_t i = 0; i < argc; i++) + m->argv[i] = strdup(argv[i]); struct module *mod = module_common_new(); mod->private = m; @@ -476,8 +492,30 @@ static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *run = yml_get_value(node, "path"); + const struct yml_node *args = yml_get_value(node, "args"); const struct yml_node *c = yml_get_value(node, "content"); - return script_new(yml_value_as_string(run), conf_to_particle(c, inherited)); + + size_t argc = args != NULL ? yml_list_length(args) : 0; + const char *argv[argc]; + + if (args != NULL) { + size_t i = 0; + for (struct yml_list_iter iter = yml_list_iter(args); + iter.node != NULL; + yml_list_next(&iter), i++) + { + argv[i] = yml_value_as_string(iter.node); + } + } + + return script_new( + yml_value_as_string(run), argc, argv, conf_to_particle(c, inherited)); +} + +static bool +conf_verify_args(keychain_t *chain, const struct yml_node *node) +{ + return conf_verify_list(chain, node, &conf_verify_string); } static bool @@ -485,6 +523,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"path", true, &conf_verify_string}, + {"args", false, &conf_verify_args}, MODULE_COMMON_ATTRS, }; From 73407853e4a3c1b3cbfec41b8eabb5f71fbe677f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Oct 2020 19:05:03 +0100 Subject: [PATCH 10/51] =?UTF-8?q?module/script:=20can=E2=80=99t=20use=20lo?= =?UTF-8?q?gical=20OR=20when=20building=20a=20base=2010=20number?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/script.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/script.c b/modules/script.c index 024654d..b518b9f 100644 --- a/modules/script.c +++ b/modules/script.c @@ -140,7 +140,7 @@ process_line(struct module *mod, const char *line, size_t len) goto bad_tag; start *= 10; - start |= _start[i] - '0'; + start += _start[i] - '0'; } long end = 0; @@ -149,7 +149,7 @@ process_line(struct module *mod, const char *line, size_t len) goto bad_tag; end *= 10; - end |= _end[i] - '0'; + end += _end[i] - '0'; } if (type_len > 9 && memcmp(type, "realtime:", 9) == 0) { From 328ebe8fe94cdcf016ade4122d098f28786528ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Oct 2020 18:02:45 +0100 Subject: [PATCH 11/51] bar/wayland: plug memory leak: free seat name --- bar/wayland.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bar/wayland.c b/bar/wayland.c index 32bd87a..35355c4 100644 --- a/bar/wayland.c +++ b/bar/wayland.c @@ -121,6 +121,8 @@ seat_destroy(struct seat *seat) if (seat == NULL) return; + free(seat->name); + if (seat->pointer.theme != NULL) wl_cursor_theme_destroy(seat->pointer.theme); if (seat->wl_pointer != NULL) From 008235d904801466a6458c07162f2d77022bfce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Oct 2020 18:03:14 +0100 Subject: [PATCH 12/51] bar/wayland: close command pipe FDs in cleanup --- bar/wayland.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bar/wayland.c b/bar/wayland.c index 35355c4..f4b3cce 100644 --- a/bar/wayland.c +++ b/bar/wayland.c @@ -136,7 +136,9 @@ seat_destroy(struct seat *seat) void * bar_backend_wayland_new(void) { - return calloc(1, sizeof(struct wayland_backend)); + struct wayland_backend *backend = calloc(1, sizeof(struct wayland_backend)); + backend->pipe_fds[0] = backend->pipe_fds[1] = -1; + return backend; } static void @@ -949,6 +951,11 @@ cleanup(struct bar *_bar) struct private *bar = _bar->private; struct wayland_backend *backend = bar->backend.data; + if (backend->pipe_fds[0] >= 0) + close(backend->pipe_fds[0]); + if (backend->pipe_fds[1] >= 0) + close(backend->pipe_fds[1]); + tll_foreach(backend->buffers, it) { if (it->item.wl_buf != NULL) wl_buffer_destroy(it->item.wl_buf); From fb0d443e1d04e930f6c8be96888e49370d4a1983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Oct 2020 18:03:32 +0100 Subject: [PATCH 13/51] =?UTF-8?q?module/script:=20plug=20memory=20leak:=20?= =?UTF-8?q?free=20=E2=80=98tags=E2=80=99=20array?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/script.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/script.c b/modules/script.c index b518b9f..5f21df8 100644 --- a/modules/script.c +++ b/modules/script.c @@ -41,7 +41,10 @@ destroy(struct module *mod) { struct private *m = mod->private; m->content->destroy(m->content); + + struct tag **tag_array = m->tags.tags; tag_set_destroy(&m->tags); + free(tag_array); for (size_t i = 0; i < m->argc; i++) free(m->argv[i]); @@ -194,7 +197,10 @@ process_transaction(struct module *mod, size_t size) } } + struct tag **old_tag_array = m->tags.tags; tag_set_destroy(&m->tags); + free(old_tag_array); + m->tags.tags = calloc(line_count, sizeof(m->tags.tags[0])); m->tags.count = line_count; From 7f1ffd126b3eb8156324405a0a1c5b1be74e2218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Oct 2020 18:03:52 +0100 Subject: [PATCH 14/51] module/script: close script communication pipe FD on error --- modules/script.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/script.c b/modules/script.c index 5f21df8..851f153 100644 --- a/modules/script.c +++ b/modules/script.c @@ -450,11 +450,13 @@ run(struct module *mod) if (r < 0) { LOG_ERRNO("failed to read from pipe"); + close(comm_pipe[0]); return -1; } if (r > 0) { LOG_ERRNO_P("%s: failed to start", _errno, m->path); + close(comm_pipe[0]); waitpid(pid, NULL, 0); return -1; } From 9945fce2d2454ac6733a84769bba5705b31e5641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 29 Oct 2020 18:04:13 +0100 Subject: [PATCH 15/51] module/script: explicitly ignore return value of write(3) --- modules/script.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/script.c b/modules/script.c index 851f153..a005283 100644 --- a/modules/script.c +++ b/modules/script.c @@ -432,7 +432,7 @@ run(struct module *mod) execvp(m->path, argv); fail: - write(exec_pipe[1], &errno, sizeof(errno)); + (void)!write(exec_pipe[1], &errno, sizeof(errno)); close(exec_pipe[1]); close(comm_pipe[1]); _exit(errno); From 37447cd955fa42f5fc7a0767ff53c4ebfc82239c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Oct 2020 10:55:27 +0100 Subject: [PATCH 16/51] =?UTF-8?q?module/script:=20initialize=20string=20ta?= =?UTF-8?q?gs=20from=20our=20NULL-terminated=20=E2=80=98value=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_value` is a pointer into the receive buffer, and may not be NULL terminated, and may also contain data belonging to the next tag. --- modules/script.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/script.c b/modules/script.c index a005283..b3e3fd4 100644 --- a/modules/script.c +++ b/modules/script.c @@ -106,7 +106,7 @@ process_line(struct module *mod, const char *line, size_t len) struct tag *tag = NULL; if (type_len == 6 && memcmp(type, "string", 6) == 0) - tag = tag_new_string(mod, name, _value); + tag = tag_new_string(mod, name, value); else if (type_len == 3 && memcmp(type, "int", 3) == 0) { long value = strtol(_value, NULL, 0); From 5c9030129dd438d32d9bc9a0351540238dc57356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Oct 2020 11:00:40 +0100 Subject: [PATCH 17/51] =?UTF-8?q?module/script:=20use=20NULL=20terminated?= =?UTF-8?q?=20=E2=80=98value=E2=80=99=20when=20converting=20to=20int/bool/?= =?UTF-8?q?float?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/script.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/modules/script.c b/modules/script.c index b3e3fd4..1ee4b72 100644 --- a/modules/script.c +++ b/modules/script.c @@ -108,20 +108,14 @@ process_line(struct module *mod, const char *line, size_t len) if (type_len == 6 && memcmp(type, "string", 6) == 0) tag = tag_new_string(mod, name, value); - else if (type_len == 3 && memcmp(type, "int", 3) == 0) { - long value = strtol(_value, NULL, 0); - tag = tag_new_int(mod, name, value); - } + else if (type_len == 3 && memcmp(type, "int", 3) == 0) + tag = tag_new_int(mod, name, strtol(value, NULL, 0)); - else if (type_len == 4 && memcmp(type, "bool", 4) == 0) { - bool value = strtol(_value, NULL, 0); - tag = tag_new_bool(mod, name, value); - } + else if (type_len == 4 && memcmp(type, "bool", 4) == 0) + tag = tag_new_bool(mod, name, strtol(value, NULL, 0)); - else if (type_len == 5 && memcmp(type, "float", 5) == 0) { - double value = strtod(_value, NULL); - tag = tag_new_float(mod, name, value); - } + else if (type_len == 5 && memcmp(type, "float", 5) == 0) + tag = tag_new_float(mod, name, strtod(value, NULL)); else if ((type_len > 6 && memcmp(type, "range:", 6) == 0) || (type_len > 9 && memcmp(type, "realtime:", 9 == 0))) @@ -160,8 +154,7 @@ process_line(struct module *mod, const char *line, size_t len) goto bad_tag; } - long value = strtol(_value, NULL, 0); - tag = tag_new_int_range(mod, name, value, start, end); + tag = tag_new_int_range(mod, name, strtol(value, NULL, 0), start, end); } else { From 2bb70c6fcbc9d0332715b521b59f65d07817e0c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Oct 2020 11:53:25 +0100 Subject: [PATCH 18/51] =?UTF-8?q?module/script:=20require=20=E2=80=98path?= =?UTF-8?q?=E2=80=99=20to=20be=20an=20absolute=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/script.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/modules/script.c b/modules/script.c index 1ee4b72..b4e165b 100644 --- a/modules/script.c +++ b/modules/script.c @@ -492,7 +492,7 @@ script_new(const char *path, size_t argc, const char *const argv[static argc], static struct module * from_conf(const struct yml_node *node, struct conf_inherit inherited) { - const struct yml_node *run = yml_get_value(node, "path"); + const struct yml_node *path = yml_get_value(node, "path"); const struct yml_node *args = yml_get_value(node, "args"); const struct yml_node *c = yml_get_value(node, "content"); @@ -510,7 +510,22 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited) } return script_new( - yml_value_as_string(run), argc, argv, conf_to_particle(c, inherited)); + yml_value_as_string(path), argc, argv, conf_to_particle(c, inherited)); +} + +static bool +conf_verify_path(keychain_t *chain, const struct yml_node *node) +{ + if (!conf_verify_string(chain, node)) + return false; + + const char *path = yml_value_as_string(node); + if (strlen(path) < 1 || path[0] != '/') { + LOG_ERR("%s: path must be absolute", conf_err_prefix(chain, node)); + return false; + } + + return true; } static bool @@ -523,7 +538,7 @@ static bool verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { - {"path", true, &conf_verify_string}, + {"path", true, &conf_verify_path}, {"args", false, &conf_verify_args}, MODULE_COMMON_ATTRS, }; From 4d05947985815a7e7e743c891268115bcc6cf377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Oct 2020 16:25:12 +0100 Subject: [PATCH 19/51] bar: deal with NULL exposables Allow modules to return NULL in begin_expose() --- bar/bar.c | 15 ++++++++++++--- module.c | 3 ++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/bar/bar.c b/bar/bar.c index d3abd14..8dbda4b 100644 --- a/bar/bar.c +++ b/bar/bar.c @@ -36,19 +36,22 @@ calculate_widths(const struct private *b, int *left, int *center, int *right) for (size_t i = 0; i < b->left.count; i++) { struct exposable *e = b->left.exps[i]; - assert(e != NULL); + if (e == NULL) + continue; *left += b->left_spacing + e->width + b->right_spacing; } for (size_t i = 0; i < b->center.count; i++) { struct exposable *e = b->center.exps[i]; - assert(e != NULL); + if (e == NULL) + continue; *center += b->left_spacing + e->width + b->right_spacing; } for (size_t i = 0; i < b->right.count; i++) { struct exposable *e = b->right.exps[i]; - assert(e != NULL); + if (e == NULL) + continue; *right += b->left_spacing + e->width + b->right_spacing; } @@ -116,6 +119,8 @@ expose(const struct bar *_bar) int x = bar->border.width + bar->left_margin - bar->left_spacing; for (size_t i = 0; i < bar->left.count; i++) { const struct exposable *e = bar->left.exps[i]; + if (e == NULL) + continue; e->expose(e, pix, x + bar->left_spacing, y, bar->height); x += bar->left_spacing + e->width + bar->right_spacing; } @@ -123,6 +128,8 @@ expose(const struct bar *_bar) x = bar->width / 2 - center_width / 2 - bar->left_spacing; for (size_t i = 0; i < bar->center.count; i++) { const struct exposable *e = bar->center.exps[i]; + if (e == NULL) + continue; e->expose(e, pix, x + bar->left_spacing, y, bar->height); x += bar->left_spacing + e->width + bar->right_spacing; } @@ -135,6 +142,8 @@ expose(const struct bar *_bar) for (size_t i = 0; i < bar->right.count; i++) { const struct exposable *e = bar->right.exps[i]; + if (e == NULL) + continue; e->expose(e, pix, x + bar->left_spacing, y, bar->height); x += bar->left_spacing + e->width + bar->right_spacing; } diff --git a/module.c b/module.c index 1e80c32..6d30839 100644 --- a/module.c +++ b/module.c @@ -23,6 +23,7 @@ struct exposable * module_begin_expose(struct module *mod) { struct exposable *e = mod->content(mod); - e->begin_expose(e); + if (e != NULL) + e->begin_expose(e); return e; } From 1e5a1d034152bfa418db310ab6b9cdf8bc0403f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Oct 2020 16:25:55 +0100 Subject: [PATCH 20/51] particle/map: return NULL if we neither find a matching tag, nor have a default tag --- particles/map.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/particles/map.c b/particles/map.c index 2f5c460..6a65a72 100644 --- a/particles/map.c +++ b/particles/map.c @@ -87,11 +87,13 @@ instantiate(const struct particle *particle, const struct tag_set *tags) { const struct private *p = particle->private; const struct tag *tag = tag_for_name(tags, p->tag); - assert(tag != NULL || p->default_particle != NULL); - - if (tag == NULL) - return p->default_particle->instantiate(p->default_particle, tags); + if (tag == NULL) { + if (p->default_particle != NULL) + return p->default_particle->instantiate(p->default_particle, tags); + else + return NULL; + } const char *tag_value = tag->as_string(tag); struct particle *pp = NULL; From f0a34d00553379611bd2eee8da09b347cd1474ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 30 Oct 2020 16:28:48 +0100 Subject: [PATCH 21/51] module/script: parse booleans correctly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User is expected to send ‘false’ or ‘true’. But we were parsing the value using `strtol()`. This caused all bools to be false, since `strtol()` would always return 0. --- modules/script.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/script.c b/modules/script.c index b4e165b..ebb55df 100644 --- a/modules/script.c +++ b/modules/script.c @@ -67,6 +67,16 @@ content(struct module *mod) return e; } + +static bool +str_to_bool(const char *s) +{ + return strcasecmp(s, "on") == 0 || + strcasecmp(s, "true") == 0 || + strcasecmp(s, "yes") == 0 || + strtoul(s, NULL, 0) > 0; +} + static struct tag * process_line(struct module *mod, const char *line, size_t len) { @@ -112,7 +122,7 @@ process_line(struct module *mod, const char *line, size_t len) tag = tag_new_int(mod, name, strtol(value, NULL, 0)); else if (type_len == 4 && memcmp(type, "bool", 4) == 0) - tag = tag_new_bool(mod, name, strtol(value, NULL, 0)); + tag = tag_new_bool(mod, name, str_to_bool(value)); else if (type_len == 5 && memcmp(type, "float", 5) == 0) tag = tag_new_float(mod, name, strtod(value, NULL)); From d10ad8924bbedd25fdab6026bb8b09d1f813fa87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 31 Oct 2020 11:15:12 +0100 Subject: [PATCH 22/51] =?UTF-8?q?doc/yambar-modules:=20document=20the=20?= =?UTF-8?q?=E2=80=98script=E2=80=99=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/yambar-modules.5.scd | 100 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/doc/yambar-modules.5.scd b/doc/yambar-modules.5.scd index 330a9f8..0e02495 100644 --- a/doc/yambar-modules.5.scd +++ b/doc/yambar-modules.5.scd @@ -714,11 +714,107 @@ bar: true: string: margin: 5 - text: "{id}: {state}"``` + text: "{id}: {state}" +``` + +# SCRIPT + +This module executes a user-provided script (or binary!) that writes +tags on its stdout. + +The script can either exit immediately after writing a set of tags, in +which case yambar will display those tags until yambar is +terminated. Or, the script can continue executing and update yambar +with new tag sets, either periodically, or when there is new data to +feed to yambar. + +Tag sets, or _transactions_, are separated by an empty line. Each +_tag_ is a single line on the format: + +``` +name|type|value +``` + +Where _name_ is what you also use to refer to the tag in the yambar +configuration, _type_ is one of the tag types defined in +*yambar-tags*(5), and _value_ is the tag’s value. + +Example: + +``` +var1|string|hello +var2|int|13 + +var1|string|world +var2|int|37 +``` + +The example above consists of two transactions. Each transaction has +two tags: one string tag and one integer tag. The second transaction +replaces the tags from the first transaction. + +Supported _types_ are: + +- string +- int +- bool +- float +- range:n-m (e.g. *var|range:0-100|57*) + +## TAGS + +User defined. + +## CONFIGURATION + +[[ *Name* +:[ *Type* +:[ *Req* +:[ *Description* +| path +: string +: yes +: Path to script/binary to execute. Must be an absolute path. +| args +: list of strings +: no +: Arguments to pass to the script/binary. + +## EXAMPLES + +Here is an "hello world" example script: + +``` +#!/bin/sh + +while true; do + echo "test|string|hello" + echo "" + sleep 3 + + echo "test|string|world" + echo "" + sleep 3 +done +``` + +This script will emit a single string tag, _test_, and alternate its +value between *hello* and *world* every three seconds. + +A corresponding yambar configuration could look like this: + +``` +bar: + left: + - script: + path: /path/to/script.sh + args: [] + content: {string: {text: "{test}"}} +``` # SWAY-XKB -This module uses *Sway* extenions to the I3 IPC API to monitor input +This module uses *Sway* extensions to the I3 IPC API to monitor input devices' active XKB layout. As such, it requires Sway to be running. *Note* that the _content_ configuration option is a *template*; From 74754b0ab951e936848663ba2c5151c04534c131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 31 Oct 2020 12:08:43 +0100 Subject: [PATCH 23/51] module/script: improved verification of tag type parameters and tag values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Verify int/float/bool values are that, and nothing else * Verify tag ranges are integers * Verify a range tag value is inside its range * Don’t allow anything but false|true for booleans --- modules/script.c | 89 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 22 deletions(-) diff --git a/modules/script.c b/modules/script.c index ebb55df..ed41bfd 100644 --- a/modules/script.c +++ b/modules/script.c @@ -67,16 +67,6 @@ content(struct module *mod) return e; } - -static bool -str_to_bool(const char *s) -{ - return strcasecmp(s, "on") == 0 || - strcasecmp(s, "true") == 0 || - strcasecmp(s, "yes") == 0 || - strtoul(s, NULL, 0) > 0; -} - static struct tag * process_line(struct module *mod, const char *line, size_t len) { @@ -118,14 +108,43 @@ process_line(struct module *mod, const char *line, size_t len) if (type_len == 6 && memcmp(type, "string", 6) == 0) tag = tag_new_string(mod, name, value); - else if (type_len == 3 && memcmp(type, "int", 3) == 0) - tag = tag_new_int(mod, name, strtol(value, NULL, 0)); + else if (type_len == 3 && memcmp(type, "int", 3) == 0) { + errno = 0; + char *end; + long v = strtol(value, &end, 0); - else if (type_len == 4 && memcmp(type, "bool", 4) == 0) - tag = tag_new_bool(mod, name, str_to_bool(value)); + if (errno != 0 || *end != '\0') { + LOG_ERR("tag value is not an integer: %s", value); + goto bad_tag; + } + tag = tag_new_int(mod, name, v); + } - else if (type_len == 5 && memcmp(type, "float", 5) == 0) - tag = tag_new_float(mod, name, strtod(value, NULL)); + else if (type_len == 4 && memcmp(type, "bool", 4) == 0) { + bool v; + if (strcmp(value, "true") == 0) + v = true; + else if (strcmp(value, "false") == 0) + v = false; + else { + LOG_ERR("tag value is not a boolean: %s", value); + goto bad_tag; + } + tag = tag_new_bool(mod, name, v); + } + + else if (type_len == 5 && memcmp(type, "float", 5) == 0) { + errno = 0; + char *end; + double v = strtod(value, &end); + + if (errno != 0 || *end != '\0') { + LOG_ERR("tag value is not a float: %s", value); + goto bad_tag; + } + + tag = tag_new_float(mod, name, v); + } else if ((type_len > 6 && memcmp(type, "range:", 6) == 0) || (type_len > 9 && memcmp(type, "realtime:", 9 == 0))) @@ -133,8 +152,12 @@ process_line(struct module *mod, const char *line, size_t len) const char *_start = type + 6; const char *split = memchr(_start, '-', type_len - 6); - if (split == NULL || split == _start || (split + 1) - type >= type_len) + if (split == NULL || split == _start || (split + 1) - type >= type_len) { + LOG_ERR( + "tag range delimiter ('-') not found in type: %.*s", + (int)type_len, type); goto bad_tag; + } const char *_end = split + 1; @@ -143,8 +166,12 @@ process_line(struct module *mod, const char *line, size_t len) long start = 0; for (size_t i = 0; i < start_len; i++) { - if (!(_start[i] >= '0' && _start[i] <= '9')) + if (!(_start[i] >= '0' && _start[i] <= '9')) { + LOG_ERR( + "tag range start is not an integer: %.*s", + (int)start_len, _start); goto bad_tag; + } start *= 10; start += _start[i] - '0'; @@ -152,19 +179,37 @@ process_line(struct module *mod, const char *line, size_t len) long end = 0; for (size_t i = 0; i < end_len; i++) { - if (!(_end[i] >= '0' && _end[i] < '9')) + if (!(_end[i] >= '0' && _end[i] < '9')) { + LOG_ERR( + "tag range end is not an integer: %.*s", + (int)end_len, _end); goto bad_tag; + } end *= 10; end += _end[i] - '0'; } if (type_len > 9 && memcmp(type, "realtime:", 9) == 0) { - LOG_WARN("unimplemented: realtime tag"); + LOG_ERR("unimplemented: realtime tag"); goto bad_tag; } - tag = tag_new_int_range(mod, name, strtol(value, NULL, 0), start, end); + errno = 0; + char *vend; + long v = strtol(value, &vend, 0); + if (errno != 0 || *vend != '\0') { + LOG_ERR("tag value is not an integer: %s", value); + goto bad_tag; + } + + if (v < start || v > end) { + LOG_ERR("tag value is outside range: %ld <= %ld <= %ld", + start, v, end); + goto bad_tag; + } + + tag = tag_new_int_range(mod, name, v, start, end); } else { @@ -176,7 +221,7 @@ process_line(struct module *mod, const char *line, size_t len) return tag; bad_tag: - LOG_ERR("invalid: %.*s", (int)len, line); + LOG_ERR("invalid tag: %.*s", (int)len, line); free(name); free(value); return NULL; From 074af015fbd7eb2fb6ba20bd832c77f647e26b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:01:46 +0100 Subject: [PATCH 24/51] module/river: exclude seats while river is starting up This is mainly to fix a race when river is *not* running; sometimes we ended up allocating particles for N seats in content(), but then when iterating the seats, run() had destroyed all, or some of the seats, causing us to feed NULL pointers to dynlist, which crashed. --- modules/river.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/river.c b/modules/river.c index bb6a03b..b6e3db1 100644 --- a/modules/river.c +++ b/modules/river.c @@ -90,7 +90,8 @@ content(struct module *mod) } } - const size_t seat_count = m->title != NULL ? tll_length(m->seats) : 0; + const size_t seat_count = m->title != NULL && !m->is_starting_up + ? tll_length(m->seats) : 0; struct exposable *tag_parts[32 + seat_count]; for (unsigned i = 0; i < 32; i++) { @@ -122,7 +123,7 @@ content(struct module *mod) tag_set_destroy(&tags); } - if (m->title != NULL) { + if (m->title != NULL && !m->is_starting_up) { size_t i = 32; tll_foreach(m->seats, it) { const struct seat *seat = &it->item; @@ -565,6 +566,10 @@ run(struct module *mod) } wl_display_roundtrip(display); + + bool unlock_at_exit = true; + mtx_lock(&mod->lock); + m->is_starting_up = false; tll_foreach(m->outputs, it) @@ -572,6 +577,9 @@ run(struct module *mod) tll_foreach(m->seats, it) instantiate_seat(&it->item); + unlock_at_exit = false; + mtx_unlock(&mod->lock); + while (true) { wl_display_flush(display); @@ -618,6 +626,9 @@ out: wl_registry_destroy(registry); if (display != NULL) wl_display_disconnect(display); + + if (unlock_at_exit) + mtx_unlock(&mod->lock); return ret; } From d0360f2de18d1a6597772f74acd2fd4dc2a6715e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:03:33 +0100 Subject: [PATCH 25/51] particle: reset signals and signal mask when executing an on-click handler --- particle.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/particle.c b/particle.c index be98e0e..ffe330f 100644 --- a/particle.c +++ b/particle.c @@ -219,14 +219,25 @@ exposable_default_on_mouse(struct exposable *exposable, struct bar *bar, LOG_DBG("executing on-click handler: %s", cmd); + sigset_t mask; + sigemptyset(&mask); + + const struct sigaction sa = {.sa_handler = SIG_DFL}; + if (sigaction(SIGINT, &sa, NULL) < 0 || + sigaction(SIGTERM, &sa, NULL) < 0 || + sigaction(SIGCHLD, &sa, NULL) < 0 || + sigprocmask(SIG_SETMASK, &mask, NULL) < 0) + { + goto fail; + } + /* Redirect stdin/stdout/stderr to /dev/null */ int dev_null_r = open("/dev/null", O_RDONLY | O_CLOEXEC); int dev_null_w = open("/dev/null", O_WRONLY | O_CLOEXEC); if (dev_null_r == -1 || dev_null_w == -1) { LOG_ERRNO("/dev/null: failed to open"); - (void)!write(pipe_fds[1], &errno, sizeof(errno)); - _exit(1); + goto fail; } if (dup2(dev_null_r, STDIN_FILENO) == -1 || @@ -234,15 +245,20 @@ exposable_default_on_mouse(struct exposable *exposable, struct bar *bar, dup2(dev_null_w, STDERR_FILENO) == -1) { LOG_ERRNO("failed to redirect stdin/stdout/stderr"); - (void)!write(pipe_fds[1], &errno, sizeof(errno)); - _exit(1); + goto fail; } + /* Close *all* other FDs (e.g. script modules' FDs) */ + for (int i = STDERR_FILENO + 1; i < 65536; i++) + close(i); + execvp(argv[0], argv); + fail: /* Signal failure to parent process */ (void)!write(pipe_fds[1], &errno, sizeof(errno)); - _exit(1); + close(pipe_fds[1]); + _exit(errno); break; default: From 198a351c7c5ad5fd13168a2b4aede4c65537d1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:03:58 +0100 Subject: [PATCH 26/51] meson: particles: data-driven foreach loop, and link map against dynlist --- particles/meson.build | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/particles/meson.build b/particles/meson.build index f571f12..6b31081 100644 --- a/particles/meson.build +++ b/particles/meson.build @@ -1,21 +1,5 @@ particle_sdk = declare_dependency(dependencies: [pixman, tllist, fcft]) -particles = [] -foreach particle : ['empty', 'list', 'map', 'progress-bar', 'ramp', 'string'] - if plugs_as_libs - shared_module('@0@'.format(particle), '@0@.c'.format(particle), - dependencies: particle_sdk, - name_prefix: 'particle_', - install: true, - install_dir: join_paths(get_option('libdir'), 'yambar')) - else - particles += [declare_dependency( - sources: '@0@.c'.format(particle), - dependencies: particle_sdk, - compile_args: '-DHAVE_PLUGIN_@0@'.format(particle.underscorify()))] - endif -endforeach - dynlist_lib = build_target( 'dynlist', 'dynlist.c', 'dynlist.h', dependencies: particle_sdk, target_type: plugs_as_libs ? 'shared_library' : 'static_library', @@ -25,3 +9,29 @@ dynlist_lib = build_target( ) dynlist = declare_dependency(link_with: dynlist_lib) + +# Particle name -> dep-list +deps = { + 'empty': [], + 'list': [], + 'map': [dynlist], + 'progress-bar': [], + 'ramp': [], + 'string': [], +} + +particles = [] +foreach particle, particle_deps : deps + if plugs_as_libs + shared_module('@0@'.format(particle), '@0@.c'.format(particle), + dependencies: [particle_sdk] + particle_deps, + name_prefix: 'particle_', + install: true, + install_dir: join_paths(get_option('libdir'), 'yambar')) + else + particles += [declare_dependency( + sources: '@0@.c'.format(particle), + dependencies: [particle_sdk] + particle_deps, + compile_args: '-DHAVE_PLUGIN_@0@'.format(particle.underscorify()))] + endif +endforeach From 86ef9dcc028e1ca900a75fd810dc689bfba30627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:04:32 +0100 Subject: [PATCH 27/51] =?UTF-8?q?particle/map:=20don=E2=80=99t=20return=20?= =?UTF-8?q?NULL=20from=20instantiate()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead return an empty dynlist. --- particles/map.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/particles/map.c b/particles/map.c index 6a65a72..57bb0fb 100644 --- a/particles/map.c +++ b/particles/map.c @@ -8,6 +8,7 @@ #include "../config-verify.h" #include "../particle.h" #include "../plugin.h" +#include "dynlist.h" struct particle_map { const char *tag_value; @@ -89,10 +90,9 @@ instantiate(const struct particle *particle, const struct tag_set *tags) const struct tag *tag = tag_for_name(tags, p->tag); if (tag == NULL) { - if (p->default_particle != NULL) - return p->default_particle->instantiate(p->default_particle, tags); - else - return NULL; + return p->default_particle != NULL + ? p->default_particle->instantiate(p->default_particle, tags) + : dynlist_exposable_new(NULL, 0, 0, 0); } const char *tag_value = tag->as_string(tag); @@ -108,13 +108,16 @@ instantiate(const struct particle *particle, const struct tag_set *tags) break; } - if (pp == NULL) { - assert(p->default_particle != NULL); - pp = p->default_particle; - } - struct eprivate *e = calloc(1, sizeof(*e)); - e->exposable = pp->instantiate(pp, tags); + + if (pp != NULL) + e->exposable = pp->instantiate(pp, tags); + else if (p->default_particle != NULL) + e->exposable = p->default_particle->instantiate(p->default_particle, tags); + else + e->exposable = dynlist_exposable_new(NULL, 0, 0, 0); + + assert(e->exposable != NULL); char *on_click = tags_expand_template(particle->on_click_template, tags); struct exposable *exposable = exposable_common_new(particle, on_click); From c3cfae13e80bc00b0a0b1678601f28477cda4916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:04:55 +0100 Subject: [PATCH 28/51] particle/progress-bar: assert sub particles where instantiated correctly --- particles/progress-bar.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/particles/progress-bar.c b/particles/progress-bar.c index 5c16802..d03931c 100644 --- a/particles/progress-bar.c +++ b/particles/progress-bar.c @@ -210,6 +210,8 @@ instantiate(const struct particle *particle, const struct tag_set *tags) epriv->exposables[idx++] = p->end_marker->instantiate(p->end_marker, tags); assert(idx == epriv->count); + for (size_t i = 0; i < epriv->count; i++) + assert(epriv->exposables[i] != NULL); char *on_click = tags_expand_template(particle->on_click_template, tags); From 44db9304c5596b2c43e6bd127012c0451a431812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:05:14 +0100 Subject: [PATCH 29/51] particle/ramp: assert sub particles where instantiated correctly --- particles/ramp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/particles/ramp.c b/particles/ramp.c index b513681..db63aff 100644 --- a/particles/ramp.c +++ b/particles/ramp.c @@ -122,6 +122,7 @@ instantiate(const struct particle *particle, const struct tag_set *tags) struct eprivate *e = calloc(1, sizeof(*e)); e->exposable = pp->instantiate(pp, tags); + assert(e->exposable != NULL); char *on_click = tags_expand_template(particle->on_click_template, tags); struct exposable *exposable = exposable_common_new(particle, on_click); From df2d8fec36c39a3f3a6302886d44c01d33bcaced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:05:23 +0100 Subject: [PATCH 30/51] particle/list: assert sub particles where instantiated correctly --- particles/list.c | 1 + 1 file changed, 1 insertion(+) diff --git a/particles/list.c b/particles/list.c index 720e8a5..6f51152 100644 --- a/particles/list.c +++ b/particles/list.c @@ -118,6 +118,7 @@ instantiate(const struct particle *particle, const struct tag_set *tags) for (size_t i = 0; i < p->count; i++) { const struct particle *pp = p->particles[i]; e->exposables[i] = pp->instantiate(pp, tags); + assert(e->exposables[i] != NULL); } char *on_click = tags_expand_template(particle->on_click_template, tags); From fef40d18e1bdd9202f8f5ffd2cbc31fc58317230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:06:52 +0100 Subject: [PATCH 31/51] module/sway-xkb: fix name of .so file, fixes load failures when built as shared module --- modules/meson.build | 20 ++++++++++---------- modules/{sway_xkb.c => sway-xkb.c} | 0 2 files changed, 10 insertions(+), 10 deletions(-) rename modules/{sway_xkb.c => sway-xkb.c} (100%) diff --git a/modules/meson.build b/modules/meson.build index ed1683e..5b480d7 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -9,7 +9,7 @@ mpd = dependency('libmpdclient') xcb_xkb = dependency('xcb-xkb', required: get_option('backend-x11')) # Module name -> (source-list, dep-list) -deps = { +mod_data = { 'alsa': [[], [m, alsa]], 'backlight': [[], [m, udev]], 'battery': [[], [udev]], @@ -20,11 +20,11 @@ deps = { 'network': [[], []], 'removables': [[], [dynlist, udev]], 'script': [[], []], - 'sway_xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json]], + 'sway-xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json]], } if backend_x11 - deps += { + mod_data += { 'xkb': [[], [xcb_stuff, xcb_xkb]], 'xwindow': [[], [xcb_stuff]], } @@ -49,25 +49,25 @@ if backend_wayland command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@']) endforeach - deps += { - 'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], []], + mod_data += { + 'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], [dynlist]], } endif -foreach mod, data : deps +foreach mod, data : mod_data sources = data[0] - dep = data[1] + deps = data[1] if plugs_as_libs shared_module(mod, '@0@.c'.format(mod), sources, - dependencies: [module_sdk] + dep, + dependencies: [module_sdk] + deps, name_prefix: 'module_', install: true, install_dir: join_paths(get_option('libdir'), 'yambar')) else modules += [declare_dependency( sources: ['@0@.c'.format(mod)] + sources, - dependencies: [module_sdk] + dep, - compile_args: '-DHAVE_PLUGIN_@0@'.format(mod))] + dependencies: [module_sdk] + deps, + compile_args: '-DHAVE_PLUGIN_@0@'.format(mod.underscorify()))] endif endforeach diff --git a/modules/sway_xkb.c b/modules/sway-xkb.c similarity index 100% rename from modules/sway_xkb.c rename to modules/sway-xkb.c From c11a79c98df124fb33705f5c173f98b93bc0fb81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:09:58 +0100 Subject: [PATCH 32/51] bar, module: particles may no longer return NULL in instantiate() --- bar/bar.c | 21 --------------------- module.c | 3 +-- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/bar/bar.c b/bar/bar.c index 8dbda4b..526f80f 100644 --- a/bar/bar.c +++ b/bar/bar.c @@ -36,22 +36,16 @@ calculate_widths(const struct private *b, int *left, int *center, int *right) for (size_t i = 0; i < b->left.count; i++) { struct exposable *e = b->left.exps[i]; - if (e == NULL) - continue; *left += b->left_spacing + e->width + b->right_spacing; } for (size_t i = 0; i < b->center.count; i++) { struct exposable *e = b->center.exps[i]; - if (e == NULL) - continue; *center += b->left_spacing + e->width + b->right_spacing; } for (size_t i = 0; i < b->right.count; i++) { struct exposable *e = b->right.exps[i]; - if (e == NULL) - continue; *right += b->left_spacing + e->width + b->right_spacing; } @@ -85,30 +79,24 @@ expose(const struct bar *_bar) for (size_t i = 0; i < bar->left.count; i++) { struct module *m = bar->left.mods[i]; struct exposable *e = bar->left.exps[i]; - if (e != NULL) e->destroy(e); - bar->left.exps[i] = module_begin_expose(m); } for (size_t i = 0; i < bar->center.count; i++) { struct module *m = bar->center.mods[i]; struct exposable *e = bar->center.exps[i]; - if (e != NULL) e->destroy(e); - bar->center.exps[i] = module_begin_expose(m); } for (size_t i = 0; i < bar->right.count; i++) { struct module *m = bar->right.mods[i]; struct exposable *e = bar->right.exps[i]; - if (e != NULL) e->destroy(e); - bar->right.exps[i] = module_begin_expose(m); } @@ -119,8 +107,6 @@ expose(const struct bar *_bar) int x = bar->border.width + bar->left_margin - bar->left_spacing; for (size_t i = 0; i < bar->left.count; i++) { const struct exposable *e = bar->left.exps[i]; - if (e == NULL) - continue; e->expose(e, pix, x + bar->left_spacing, y, bar->height); x += bar->left_spacing + e->width + bar->right_spacing; } @@ -128,8 +114,6 @@ expose(const struct bar *_bar) x = bar->width / 2 - center_width / 2 - bar->left_spacing; for (size_t i = 0; i < bar->center.count; i++) { const struct exposable *e = bar->center.exps[i]; - if (e == NULL) - continue; e->expose(e, pix, x + bar->left_spacing, y, bar->height); x += bar->left_spacing + e->width + bar->right_spacing; } @@ -142,8 +126,6 @@ expose(const struct bar *_bar) for (size_t i = 0; i < bar->right.count; i++) { const struct exposable *e = bar->right.exps[i]; - if (e == NULL) - continue; e->expose(e, pix, x + bar->left_spacing, y, bar->height); x += bar->left_spacing + e->width + bar->right_spacing; } @@ -316,7 +298,6 @@ destroy(struct bar *bar) for (size_t i = 0; i < b->left.count; i++) { struct module *m = b->left.mods[i]; struct exposable *e = b->left.exps[i]; - if (e != NULL) e->destroy(e); m->destroy(m); @@ -324,7 +305,6 @@ destroy(struct bar *bar) for (size_t i = 0; i < b->center.count; i++) { struct module *m = b->center.mods[i]; struct exposable *e = b->center.exps[i]; - if (e != NULL) e->destroy(e); m->destroy(m); @@ -332,7 +312,6 @@ destroy(struct bar *bar) for (size_t i = 0; i < b->right.count; i++) { struct module *m = b->right.mods[i]; struct exposable *e = b->right.exps[i]; - if (e != NULL) e->destroy(e); m->destroy(m); diff --git a/module.c b/module.c index 6d30839..1e80c32 100644 --- a/module.c +++ b/module.c @@ -23,7 +23,6 @@ struct exposable * module_begin_expose(struct module *mod) { struct exposable *e = mod->content(mod); - if (e != NULL) - e->begin_expose(e); + e->begin_expose(e); return e; } From 31f6a4a6a004f62726a66a1b089b1a5612f168ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:12:12 +0100 Subject: [PATCH 33/51] =?UTF-8?q?module/script:=20don=E2=80=99t=20re-close?= =?UTF-8?q?=20comm-pipe=20on=20failure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/script.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/script.c b/modules/script.c index ed41bfd..da11a80 100644 --- a/modules/script.c +++ b/modules/script.c @@ -475,14 +475,17 @@ run(struct module *mod) goto fail; } + /* We're done with the redirection pipe */ close(comm_pipe[1]); + comm_pipe[1] = -1; execvp(m->path, argv); fail: (void)!write(exec_pipe[1], &errno, sizeof(errno)); close(exec_pipe[1]); - close(comm_pipe[1]); + if (comm_pipe[1] >= 0) + close(comm_pipe[1]); _exit(errno); } From aa34925f54754b5a6535ca0e062723825863f88d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:12:26 +0100 Subject: [PATCH 34/51] module/script: close all unrelated FDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While most FDs are CLOEXEC, not all are. For example, other script modules’ re-direction pipes. --- modules/script.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/script.c b/modules/script.c index da11a80..e18570e 100644 --- a/modules/script.c +++ b/modules/script.c @@ -479,6 +479,16 @@ run(struct module *mod) close(comm_pipe[1]); comm_pipe[1] = -1; + /* Close *all* other FDs */ + for (int i = STDERR_FILENO + 1; i < 65536; i++) { + if (i == exec_pipe[1]) { + /* Needed for error reporting. Automatically closed + * when execvp() succeeds */ + continue; + } + close(i); + } + execvp(m->path, argv); fail: From e0169d38f3258026f6cc498aff4080764bd16c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:13:11 +0100 Subject: [PATCH 35/51] =?UTF-8?q?module/script:=20don=E2=80=99t=20re-direc?= =?UTF-8?q?t=20stderr=20to=20/dev/null?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/script.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/script.c b/modules/script.c index e18570e..a904387 100644 --- a/modules/script.c +++ b/modules/script.c @@ -463,13 +463,12 @@ run(struct module *mod) close(exec_pipe[0]); close(comm_pipe[0]); - /* Re-direct stdin/stdout/stderr */ - int dev_null = open("/dev/null", O_RDWR); + /* Re-direct stdin/stdout */ + int dev_null = open("/dev/null", O_RDONLY); if (dev_null < 0) goto fail; if (dup2(dev_null, STDIN_FILENO) < 0 || - dup2(dev_null, STDERR_FILENO) < 0 || dup2(comm_pipe[1], STDOUT_FILENO) < 0) { goto fail; From ba54e709eea46c540e4b97d0e73b2f6a6ae9fe42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:14:15 +0100 Subject: [PATCH 36/51] module/script: no need to handle SIGCHLD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assume that a closed pipe means the child died. Even if it hasn’t, we can’t read anymore from it. We’ll end up killing it anyway before returning from run(). --- modules/script.c | 63 +++++++++++------------------------------------- 1 file changed, 14 insertions(+), 49 deletions(-) diff --git a/modules/script.c b/modules/script.c index a904387..70c889f 100644 --- a/modules/script.c +++ b/modules/script.c @@ -10,7 +10,6 @@ #include #include -#include #define LOG_MODULE "script" #define LOG_ENABLE_DBG 0 @@ -315,34 +314,13 @@ data_received(struct module *mod, const char *data, size_t len) } static int -run_loop(struct module *mod, int comm_fd) +run_loop(struct module *mod, pid_t pid, int comm_fd) { - //struct private *m = mod; - - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGCHLD); - - /* Block normal signal handling - we're using a signalfd instead */ - sigset_t original_mask; - if (pthread_sigmask(SIG_BLOCK, &mask, &original_mask) < 0) { - LOG_ERRNO("failed to block SIGCHLD"); - return -1; - } - - int sig_fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); - if (sig_fd < 0) { - LOG_ERRNO("failed to create signal FD"); - pthread_sigmask(SIG_SETMASK, &original_mask, NULL); - return -1; - } - int ret = 0; while (true) { struct pollfd fds[] = { {.fd = mod->abort_fd, .events = POLLIN}, - {.fd = sig_fd, .events = POLLIN}, {.fd = comm_fd, .events = POLLIN}, }; @@ -354,7 +332,7 @@ run_loop(struct module *mod, int comm_fd) break; } - if (fds[2].revents & POLLIN) { + if (fds[1].revents & POLLIN) { char data[4096]; ssize_t amount = read(comm_fd, data, sizeof(data)); if (amount < 0) { @@ -373,36 +351,17 @@ run_loop(struct module *mod, int comm_fd) } if (fds[1].revents & POLLHUP) { - LOG_ERR("signal FD closed unexpectedly"); - ret = 1; - break; - } - - if (fds[2].revents & POLLHUP) { /* Child's stdout closed */ + LOG_DBG("script pipe closed (script terminated?)"); break; } - if (fds[0].revents & POLLIN) - break; - - if (fds[1].revents & POLLIN) { - struct signalfd_siginfo info; - ssize_t amount = read(sig_fd, &info, sizeof(info)); - - if (amount < 0) { - LOG_ERRNO("failed to read from signal FD"); - break; - } - - assert(info.ssi_signo == SIGCHLD); - LOG_WARN("script died"); + if (fds[0].revents & POLLIN) { + /* Aborted */ break; } } - close(sig_fd); - pthread_sigmask(SIG_SETMASK, &original_mask, NULL); return ret; } @@ -411,12 +370,14 @@ run(struct module *mod) { struct private *m = mod->private; + /* Pipe to detect exec() failures */ int exec_pipe[2]; if (pipe2(exec_pipe, O_CLOEXEC) < 0) { LOG_ERRNO("failed to create pipe"); return -1; } + /* Stdout redirection pipe */ int comm_pipe[2]; if (pipe(comm_pipe) < 0) { LOG_ERRNO("failed to create stdin/stdout redirection pipe"); @@ -438,13 +399,14 @@ run(struct module *mod) if (pid == 0) { /* Child */ + /* Construct argv for execvp() */ char *argv[1 + m->argc + 1]; argv[0] = m->path; for (size_t i = 0; i < m->argc; i++) argv[i + 1] = m->argv[i]; argv[1 + m->argc] = NULL; - /* Restore signal handlers */ + /* Restore signal handlers and signal mask */ sigset_t mask; sigemptyset(&mask); @@ -457,6 +419,7 @@ run(struct module *mod) goto fail; } + /* New process group, so that we can use killpg() */ setpgid(0, 0); /* Close pipe read ends */ @@ -505,6 +468,7 @@ run(struct module *mod) int _errno; static_assert(sizeof(_errno) == sizeof(errno), "errno size mismatch"); + /* Wait for errno from child, or FD being closed in execvp() */ int r = read(exec_pipe[0], &_errno, sizeof(_errno)); close(exec_pipe[0]); @@ -521,10 +485,11 @@ run(struct module *mod) return -1; } + /* Pipe was closed. I.e. execvp() succeeded */ + assert(r == 0); LOG_DBG("script running under PID=%u", pid); - int ret = run_loop(mod, comm_pipe[0]); - + int ret = run_loop(mod, pid, comm_pipe[0]); close(comm_pipe[0]); if (waitpid(pid, NULL, WNOHANG) == 0) { killpg(pid, SIGTERM); From 2fe602a6a2b3816b2bf9be991ae52211dcd961bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:15:42 +0100 Subject: [PATCH 37/51] =?UTF-8?q?main:=20no=20need=20to=20block=20SIGCHLD?= =?UTF-8?q?=20anymore,=20we=20don=E2=80=99t=20use=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/main.c b/main.c index 61a89f1..f8a8453 100644 --- a/main.c +++ b/main.c @@ -279,11 +279,6 @@ main(int argc, char *const *argv) sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); - sigset_t proc_signal_mask; - sigemptyset(&proc_signal_mask); - sigaddset(&proc_signal_mask, SIGCHLD); - sigprocmask(SIG_BLOCK, &proc_signal_mask, NULL); - /* Block SIGINT (this is under the assumption that threads inherit * the signal mask */ sigset_t signal_mask; @@ -341,15 +336,17 @@ main(int argc, char *const *argv) while (!aborted) { struct pollfd fds[] = {{.fd = abort_fd, .events = POLLIN}}; - int r __attribute__((unused)) = poll(fds, 1, -1); + int r __attribute__((unused)) = poll(fds, sizeof(fds) / sizeof(fds[0]), -1); - /* - * Either the bar aborted (triggering the abort_fd), or user - * killed us (triggering the signal handler which sets - * 'aborted') - */ - assert(aborted || r == 1); - break; + if (fds[0].revents & (POLLIN | POLLHUP)) { + /* + * Either the bar aborted (triggering the abort_fd), or user + * killed us (triggering the signal handler which sets + * 'aborted') + */ + assert(aborted || r == 1); + break; + } } if (aborted) From f438ad9b4468f8c2f2f926bfddf3fe3aef357ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:15:58 +0100 Subject: [PATCH 38/51] module/script: send SIGINT, SIGTERM, SIGKILL, until child has died --- modules/script.c | 61 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/modules/script.c b/modules/script.c index 70c889f..766ea96 100644 --- a/modules/script.c +++ b/modules/script.c @@ -9,7 +9,9 @@ #include #include +#include #include +#include #define LOG_MODULE "script" #define LOG_ENABLE_DBG 0 @@ -491,12 +493,61 @@ run(struct module *mod) int ret = run_loop(mod, pid, comm_pipe[0]); close(comm_pipe[0]); - if (waitpid(pid, NULL, WNOHANG) == 0) { - killpg(pid, SIGTERM); - /* TODO: send SIGKILL after X seconds */ - waitpid(pid, NULL, 0); - } + if (waitpid(pid, NULL, WNOHANG) == 0) { + static const struct { + int signo; + int timeout; + const char *name; + } sig_info[] = { + {SIGINT, 2, "SIGINT"}, + {SIGTERM, 5, "SIGTERM"}, + {SIGKILL, 0, "SIGKILL"}, + }; + + for (size_t i = 0; i < sizeof(sig_info) / sizeof(sig_info[0]); i++) { + struct timeval start; + gettimeofday(&start, NULL); + + const int signo = sig_info[i].signo; + const int timeout = sig_info[i].timeout; + const char *const name __attribute__((unused)) = sig_info[i].name; + + LOG_DBG("sending %s to PID=%u (timeout=%ds)", name, pid, timeout); + killpg(pid, signo); + + /* + * Child is unlikely to terminate *immediately*. Wait a + * *short* period of time before checking waitpid() the + * first time + */ + usleep(10000); + + pid_t waited_pid; + while ((waited_pid = waitpid( + pid, NULL, timeout > 0 ? WNOHANG : 0)) == 0) + { + struct timeval now; + gettimeofday(&now, NULL); + + struct timeval elapsed; + timersub(&now, &start, &elapsed); + + if (elapsed.tv_sec >= timeout) + break; + + /* Don't spinning */ + thrd_yield(); + usleep(100000); /* 100ms */ + } + + if (waited_pid == pid) { + /* Child finally dead */ + break; + } + } + } else + LOG_DBG("PID=%u already terminated", pid); return ret; } From 05aa44f1abb42de1408888b92858494348e201cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:55:01 +0100 Subject: [PATCH 39/51] examples: move laptop.conf -> configurations/laptop.conf --- examples/{ => configurations}/laptop.conf | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{ => configurations}/laptop.conf (100%) diff --git a/examples/laptop.conf b/examples/configurations/laptop.conf similarity index 100% rename from examples/laptop.conf rename to examples/configurations/laptop.conf From 31c015c68051b7ca612bc41f1a7bff387cdd3df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:57:31 +0100 Subject: [PATCH 40/51] particle/ramp: handle tag_for_name() failing --- particles/ramp.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/particles/ramp.c b/particles/ramp.c index db63aff..45cc277 100644 --- a/particles/ramp.c +++ b/particles/ramp.c @@ -95,13 +95,12 @@ instantiate(const struct particle *particle, const struct tag_set *tags) { const struct private *p = particle->private; const struct tag *tag = tag_for_name(tags, p->tag); - assert(tag != NULL); assert(p->count > 0); - long value = tag->as_int(tag); - long min = tag->min(tag); - long max = tag->max(tag); + long value = tag != NULL ? tag->as_int(tag) : 0; + long min = tag != NULL ? tag->min(tag) : 0; + long max = tag != NULL ? tag->max(tag) : 0; assert(value >= min && value <= max); assert(max >= min); From 321d1cdc7d1c8259a65983861755962650daff32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 19:59:24 +0100 Subject: [PATCH 41/51] particle/progress-bar: handle tag_for_name() failing --- particles/progress-bar.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/particles/progress-bar.c b/particles/progress-bar.c index d03931c..ad5c4cd 100644 --- a/particles/progress-bar.c +++ b/particles/progress-bar.c @@ -179,13 +179,13 @@ instantiate(const struct particle *particle, const struct tag_set *tags) { const struct private *p = particle->private; const struct tag *tag = tag_for_name(tags, p->tag); - assert(tag != NULL); - long value = tag->as_int(tag); - long min = tag->min(tag); - long max = tag->max(tag); + long value = tag != NULL ? tag->as_int(tag) : 0; + long min = tag != NULL ? tag->min(tag) : 0; + long max = tag != NULL ? tag->max(tag) : 0; - LOG_DBG("%s: value=%ld, min=%ld, max=%ld", tag->name(tag), value, min, max); + LOG_DBG("%s: value=%ld, min=%ld, max=%ld", + tag != NULL ? tag->name(tag) : "", value, min, max); long fill_count = max == min ? 0 : p->width * value / (max - min); long empty_count = p->width - fill_count; @@ -224,6 +224,9 @@ instantiate(const struct particle *particle, const struct tag_set *tags) exposable->expose = &expose; exposable->on_mouse = &on_mouse; + if (tag == NULL) + return exposable; + enum tag_realtime_unit rt = tag->realtime(tag); if (rt == TAG_REALTIME_NONE) From 58e53b80a907d1c0d7e9ba11a5ec7ca07219276c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Nov 2020 20:31:35 +0100 Subject: [PATCH 42/51] changelog: new module: script --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa81d03..95c1007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ (https://codeberg.org/dnkl/yambar/issues/10). * river: added documentation (https://codeberg.org/dnkl/yambar/issues/9). +* script: new module, adds support for custom user scripts + (https://codeberg.org/dnkl/yambar/issues/11). ### Deprecated @@ -30,6 +32,8 @@ ### Security ### Contributors +* [JorwLNKwpH](https://codeberg.org/JorwLNKwpH) + ## 1.5.0 From d5a92cbf5f74c63285bc555e1964766381f40586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 3 Nov 2020 21:25:40 +0100 Subject: [PATCH 43/51] examples: script: cpu.sh - measures CPU usage at a configurable interval --- examples/scripts/cpu.sh | 117 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100755 examples/scripts/cpu.sh diff --git a/examples/scripts/cpu.sh b/examples/scripts/cpu.sh new file mode 100755 index 0000000..1d5cdb5 --- /dev/null +++ b/examples/scripts/cpu.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +# cpu.sh - measures CPU usage at a configurable sample interval +# +# Usage: cpu.sh INTERVAL_IN_SECONDS +# +# This script will emit the following tags on stdout (N is the number +# of logical CPUs): +# +# Name Type +# -------------------- +# cpu range 0-100 +# cpu0 range 0-100 +# cpu1 range 0-100 +# ... +# cpuN-1 range 0-100 +# +# I.e. ‘cpu’ is the average (or aggregated) CPU usage, while cpuX is a +# specific CPUs usage. +# +# Example configuration (update every second): +# +# - script: +# path: /path/to/cpu.sh +# args: [1] +# content: {string: {text: "{cpu}%"}} +# + +interval=${1} + +case ${interval} in + ''|*[!0-9]*) + echo "interval must be an integer" + exit 1 + ;; + *) + ;; +esac + +# Get number of CPUs, by reading /proc/stat +# The output looks like: +# +# cpu A B C D ... +# cpu0 A B C D ... +# cpu1 A B C D ... +# cpuN A B C D ... +# +# The first line is a summary line, accounting *all* CPUs +IFS=$'\n' readarray -t all_cpu_stats < <(grep -e "^cpu" /proc/stat) +cpu_count=$((${#all_cpu_stats[@]} - 1)) + +# Arrays of ‘previous’ idle and total stats, needed to calculate the +# difference between each sample. +prev_idle=() +prev_total=() +for i in $(seq ${cpu_count}); do + prev_idle+=(0) + prev_total+=(0) +done + +prev_average_idle=0 +prev_average_total=0 + +while true; do + IFS=$'\n' readarray -t all_cpu_stats < <(grep -e "^cpu" /proc/stat) + + usage=() # CPU usage in percent, 0 <= x <= 100 + idle=() # idle time since boot, in jiffies + total=() # total time since boot, in jiffies + + average_idle=0 # All CPUs idle time since boot + average_total=0 # All CPUs total time since boot + + for i in $(seq 0 $((cpu_count - 1))); do + # Split this CPUs stats into an array + stats=($(echo "${all_cpu_stats[$((i + 1))]}")) + + # Clear (zero out) “cpuN” + unset "stats[0]" + + # CPU idle time since boot + idle[i]=${stats[4]} + average_idle=$((average_idle + idle[i])) + + # CPU total time since boot + total[i]=0 + for v in "${stats[@]}"; do + total[i]=$((total[i] + v)) + done + average_total=$((average_total + total[i])) + + # Diff since last sample + diff_idle=$((idle[i] - prev_idle[i])) + diff_total=$((total[i] - prev_total[i])) + + usage[i]=$((100 * (diff_total - diff_idle) / diff_total)) + + prev_idle[i]=${idle[i]} + prev_total[i]=${total[i]} + done + + diff_average_idle=$((average_idle - prev_average_idle)) + diff_average_total=$((average_total - prev_average_total)) + + average_usage=$((100 * (diff_average_total - diff_average_idle) / diff_average_total)) + + prev_average_idle=${average_idle} + prev_average_total=${average_total} + + echo "cpu|range:0-100|${average_usage}" + for i in $(seq 0 $((cpu_count - 1))); do + echo "cpu${i}|range:0-100|${usage[i]}" + done + + echo "" + sleep "${interval}" +done From b1ee1ba403c0c8e15b049f61cedbc03e599e804e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 3 Nov 2020 21:32:08 +0100 Subject: [PATCH 44/51] =?UTF-8?q?examples:=20script:=20cpu.sh=20add=20miss?= =?UTF-8?q?ing=20=E2=80=9C=E2=80=98=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/scripts/cpu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/scripts/cpu.sh b/examples/scripts/cpu.sh index 1d5cdb5..0bf7813 100755 --- a/examples/scripts/cpu.sh +++ b/examples/scripts/cpu.sh @@ -16,7 +16,7 @@ # cpuN-1 range 0-100 # # I.e. ‘cpu’ is the average (or aggregated) CPU usage, while cpuX is a -# specific CPUs usage. +# specific CPU’s usage. # # Example configuration (update every second): # From ae983b63c241b99927717f06f7e53118e377545b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 3 Nov 2020 21:37:57 +0100 Subject: [PATCH 45/51] readme: add river and sway-xkb to list of modules --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8395ad4..14522d1 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,8 @@ Available modules: * mpd * network * removables +* river +* sway-xkb * xkb (_XCB backend only_) * xwindow (_XCB backend only_) From f735bc5bd9d3958f22ee50077db689f07c3c4ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 3 Nov 2020 21:38:10 +0100 Subject: [PATCH 46/51] readme: add link to configuration examples --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 14522d1..d4993c2 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,8 @@ bar: For details, see the man pages (**yambar**(5) is a good start). +Example configurations can be found in [examples](examples/configuration). + ## Modules From 9d37697c4f01ded8be2ab3bb8a0d2d9cf5158c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 3 Nov 2020 21:38:16 +0100 Subject: [PATCH 47/51] readme: add script to list of modules, with link to example scripts --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d4993c2..24b7989 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Available modules: * network * removables * river +* script (see script [examples](examples/scripts)) * sway-xkb * xkb (_XCB backend only_) * xwindow (_XCB backend only_) From 220e43526c43622f6cae014af7ede63a11cdd39a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 3 Nov 2020 21:38:59 +0100 Subject: [PATCH 48/51] readme: fix link to configuration examples --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 24b7989..8052784 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ bar: For details, see the man pages (**yambar**(5) is a good start). -Example configurations can be found in [examples](examples/configuration). +Example configurations can be found in [examples](examples/configurations). ## Modules From f49652130dbe7d7f1cea770c501f32d3664a72c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 5 Nov 2020 21:14:42 +0100 Subject: [PATCH 49/51] =?UTF-8?q?config:=20don=E2=80=99t=20crash=20(div-by?= =?UTF-8?q?-zero)=20if=20the=20alpha=20component=20is=200?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + config.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95c1007..115c7a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ (https://codeberg.org/dnkl/yambar/issues/12). * mpd: fix compilation with clang (https://codeberg.org/dnkl/yambar/issues/16). +* Crash when the alpha component in a color value was 0. ### Security diff --git a/config.c b/config.c index 556891c..743a1a3 100644 --- a/config.c +++ b/config.c @@ -53,6 +53,9 @@ conf_to_color(const struct yml_node *node) uint16_t blue = hex_byte(&hex[4]); uint16_t alpha = hex_byte(&hex[6]); + if (alpha == 0) + return (pixman_color_t){0, 0, 0, 0}; + alpha |= alpha << 8; int alpha_div = 0xffff / alpha; From 4a9f5500691a2a90ced56598bc8185d05fe416a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 10 Nov 2020 22:26:49 +0100 Subject: [PATCH 50/51] =?UTF-8?q?module/battery:=20don=E2=80=99t=20crash?= =?UTF-8?q?=20if=20we=20fail=20to=20read=20from=20=E2=80=98status=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/battery.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/battery.c b/modules/battery.c index 4759a99..99bad3c 100644 --- a/modules/battery.c +++ b/modules/battery.c @@ -291,7 +291,10 @@ update_status(struct module *mod, int capacity_fd, int energy_fd, int power_fd, const char *status = readline_from_fd(status_fd); enum state state; - if (strcmp(status, "Full") == 0) + if (status == NULL) { + LOG_WARN("failed to read battery state"); + state = STATE_DISCHARGING; + } else if (strcmp(status, "Full") == 0) state = STATE_FULL; else if (strcmp(status, "Charging") == 0) state = STATE_CHARGING; From 9718c6f31ebf59771de2ace786804ac0a8ce8dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 15 Nov 2020 12:15:16 +0100 Subject: [PATCH 51/51] examples: scripts: cpu: fix idle and total calculation * include iowait in idle * guest/guestnice is accounted in user/nice, so need to subtract them to not count them twice --- examples/scripts/cpu.sh | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/examples/scripts/cpu.sh b/examples/scripts/cpu.sh index 0bf7813..66615c5 100755 --- a/examples/scripts/cpu.sh +++ b/examples/scripts/cpu.sh @@ -65,8 +65,6 @@ while true; do IFS=$'\n' readarray -t all_cpu_stats < <(grep -e "^cpu" /proc/stat) usage=() # CPU usage in percent, 0 <= x <= 100 - idle=() # idle time since boot, in jiffies - total=() # total time since boot, in jiffies average_idle=0 # All CPUs idle time since boot average_total=0 # All CPUs total time since boot @@ -75,28 +73,37 @@ while true; do # Split this CPUs stats into an array stats=($(echo "${all_cpu_stats[$((i + 1))]}")) - # Clear (zero out) “cpuN” - unset "stats[0]" + # man procfs(5) + user=${stats[1]} + nice=${stats[2]} + system=${stats[3]} + idle=${stats[4]} + iowait=${stats[5]} + irq=${stats[6]} + softirq=${stats[7]} + steal=${stats[8]} + guest=${stats[9]} + guestnice=${stats[10]} - # CPU idle time since boot - idle[i]=${stats[4]} - average_idle=$((average_idle + idle[i])) + # Guest time already accounted for in user + user=$((user - guest)) + nice=$((nice - guestnice)) - # CPU total time since boot - total[i]=0 - for v in "${stats[@]}"; do - total[i]=$((total[i] + v)) - done - average_total=$((average_total + total[i])) + idle=$((idle + iowait)) + + total=$((user + nice + system + irq + softirq + idle + steal + guest + guestnice)) + + average_idle=$((average_idle + idle)) + average_total=$((average_total + total)) # Diff since last sample - diff_idle=$((idle[i] - prev_idle[i])) - diff_total=$((total[i] - prev_total[i])) + diff_idle=$((idle - prev_idle[i])) + diff_total=$((total - prev_total[i])) usage[i]=$((100 * (diff_total - diff_idle) / diff_total)) - prev_idle[i]=${idle[i]} - prev_total[i]=${total[i]} + prev_idle[i]=${idle} + prev_total[i]=${total} done diff_average_idle=$((average_idle - prev_average_idle))