Mercurial > irccd
changeset 975:5ffc8350e84b
irccdctl: add support for rule editing
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 09 Feb 2021 13:00:32 +0100 |
parents | 342fb90f2512 |
children | f5a07c0768c8 |
files | CMakeLists.txt MIGRATING.md extern/libcompat/CMakeLists.txt extern/libcompat/src/compat.h.in extern/libcompat/src/strtonum.c extern/libketopt/CMakeLists.txt extern/libketopt/ketopt.h irccd/peer.c irccdctl/CMakeLists.txt irccdctl/main.c lib/irccd/irccd.c lib/irccd/irccd.h |
diffstat | 12 files changed, 755 insertions(+), 25 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Sun Feb 07 14:36:28 2021 +0100 +++ b/CMakeLists.txt Tue Feb 09 13:00:32 2021 +0100 @@ -45,6 +45,7 @@ endif () add_subdirectory(extern/libcompat) +add_subdirectory(extern/libketopt) if (IRCCD_WITH_JS) add_subdirectory(extern/libduktape)
--- a/MIGRATING.md Sun Feb 07 14:36:28 2021 +0100 +++ b/MIGRATING.md Tue Feb 09 13:00:32 2021 +0100 @@ -23,6 +23,8 @@ - The `watch` command no longer produce JSON output but only the original "human" format but may be used for scripts as it is honored through the semantic versioning. +- The command `rule-info` has been removed because it is mostly the same as + `rule-list`. Platform support ----------------
--- a/extern/libcompat/CMakeLists.txt Sun Feb 07 14:36:28 2021 +0100 +++ b/extern/libcompat/CMakeLists.txt Tue Feb 09 13:00:32 2021 +0100 @@ -45,6 +45,7 @@ strlcat strlcpy strsep + strtonum verr verrc verrx
--- a/extern/libcompat/src/compat.h.in Sun Feb 07 14:36:28 2021 +0100 +++ b/extern/libcompat/src/compat.h.in Tue Feb 09 13:00:32 2021 +0100 @@ -19,6 +19,7 @@ #cmakedefine COMPAT_HAVE_STRNLEN #cmakedefine COMPAT_HAVE_STRSEP #cmakedefine COMPAT_HAVE_STRTOK_R +#cmakedefine COMPAT_HAVE_STRTONUM #cmakedefine COMPAT_HAVE_VERR #cmakedefine COMPAT_HAVE_VERRC #cmakedefine COMPAT_HAVE_VERRX @@ -187,4 +188,9 @@ strtok_r(char *, const char *, char **); #endif +#ifndef COMPAT_HAVE_STRTONUM +long long +strtonum(const char *, long long, long long, const char **); +#endif + #endif /* !LIBCOMPAT_COMPAT_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extern/libcompat/src/strtonum.c Tue Feb 09 13:00:32 2021 +0100 @@ -0,0 +1,65 @@ +/* $OpenBSD: strtonum.c,v 1.8 2015/09/13 08:31:48 guenther Exp $ */ + +/* + * Copyright (c) 2004 Ted Unangst and Todd Miller + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <errno.h> +#include <limits.h> +#include <stdlib.h> + +#define INVALID 1 +#define TOOSMALL 2 +#define TOOLARGE 3 + +long long +strtonum(const char *numstr, long long minval, long long maxval, + const char **errstrp) +{ + long long ll = 0; + int error = 0; + char *ep; + struct errval { + const char *errstr; + int err; + } ev[4] = { + { NULL, 0 }, + { "invalid", EINVAL }, + { "too small", ERANGE }, + { "too large", ERANGE }, + }; + + ev[0].err = errno; + errno = 0; + if (minval > maxval) { + error = INVALID; + } else { + ll = strtoll(numstr, &ep, 10); + if (numstr == ep || *ep != '\0') + error = INVALID; + else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) + error = TOOSMALL; + else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) + error = TOOLARGE; + } + if (errstrp != NULL) + *errstrp = ev[error].errstr; + errno = ev[error].err; + if (error) + ll = 0; + + return (ll); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extern/libketopt/CMakeLists.txt Tue Feb 09 13:00:32 2021 +0100 @@ -0,0 +1,26 @@ +# +# CMakeLists.txt -- CMake build system for ketopt +# +# Copyright (c) 2016-2021 David Demelier <markand@malikania.fr> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +cmake_minimum_required(VERSION 3.0) +project(libirccd-ketopt) +add_library(libirccd-ketopt INTERFACE ketopt.h) +target_include_directories( + libirccd-ketopt + INTERFACE + $<BUILD_INTERFACE:${libirccd-ketopt_SOURCE_DIR}> +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extern/libketopt/ketopt.h Tue Feb 09 13:00:32 2021 +0100 @@ -0,0 +1,120 @@ +#ifndef KETOPT_H +#define KETOPT_H + +#include <string.h> /* for strchr() and strncmp() */ + +#define ko_no_argument 0 +#define ko_required_argument 1 +#define ko_optional_argument 2 + +typedef struct { + int ind; /* equivalent to optind */ + int opt; /* equivalent to optopt */ + char *arg; /* equivalent to optarg */ + int longidx; /* index of a long option; or -1 if short */ + /* private variables not intended for external uses */ + int i, pos, n_args; +} ketopt_t; + +typedef struct { + char *name; + int has_arg; + int val; +} ko_longopt_t; + +static ketopt_t KETOPT_INIT = { 0, 0, 0, -1, 0, 0, 0 }; + +static void ketopt_permute(char *argv[], int j, int n) /* move argv[j] over n elements to the left */ +{ + int k; + char *p = argv[j]; + for (k = 0; k < n; ++k) + argv[j - k] = argv[j - k - 1]; + argv[j - k] = p; +} + +/** + * Parse command-line options and arguments + * + * This fuction has a similar interface to GNU's getopt_long(). Each call + * parses one option and returns the option name. s->arg points to the option + * argument if present. The function returns -1 when all command-line arguments + * are parsed. In this case, s->ind is the index of the first non-option + * argument. + * + * @param s status; shall be initialized to KETOPT_INIT on the first call + * @param argc length of argv[] + * @param argv list of command-line arguments; argv[0] is ignored + * @param permute non-zero to move options ahead of non-option arguments + * @param ostr option string + * @param longopts long options + * + * @return ASCII for a short option; ko_longopt_t::val for a long option; -1 if + * argv[] is fully processed; '?' for an unknown option or an ambiguous + * long option; ':' if an option argument is missing + */ +static int ketopt(ketopt_t *s, int argc, char *argv[], int permute, const char *ostr, const ko_longopt_t *longopts) +{ + int opt = -1, i0, j; + if (permute) { + while (s->i < argc && (argv[s->i][0] != '-' || argv[s->i][1] == '\0')) + ++s->i, ++s->n_args; + } + s->arg = 0, s->longidx = -1, i0 = s->i; + if (s->i >= argc || argv[s->i][0] != '-' || argv[s->i][1] == '\0') { + s->ind = s->i - s->n_args; + return -1; + } + if (argv[s->i][0] == '-' && argv[s->i][1] == '-') { /* "--" or a long option */ + if (argv[s->i][2] == '\0') { /* a bare "--" */ + ketopt_permute(argv, s->i, s->n_args); + ++s->i, s->ind = s->i - s->n_args; + return -1; + } + s->opt = 0, opt = '?', s->pos = -1; + if (longopts) { /* parse long options */ + int k, n_exact = 0, n_partial = 0; + const ko_longopt_t *o = 0, *o_exact = 0, *o_partial = 0; + for (j = 2; argv[s->i][j] != '\0' && argv[s->i][j] != '='; ++j) {} /* find the end of the option name */ + for (k = 0; longopts[k].name != 0; ++k) + if (strncmp(&argv[s->i][2], longopts[k].name, j - 2) == 0) { + if (longopts[k].name[j - 2] == 0) ++n_exact, o_exact = &longopts[k]; + else ++n_partial, o_partial = &longopts[k]; + } + if (n_exact > 1 || (n_exact == 0 && n_partial > 1)) return '?'; + o = n_exact == 1? o_exact : n_partial == 1? o_partial : 0; + if (o) { + s->opt = opt = o->val, s->longidx = o - longopts; + if (argv[s->i][j] == '=') s->arg = &argv[s->i][j + 1]; + if (o->has_arg == 1 && argv[s->i][j] == '\0') { + if (s->i < argc - 1) s->arg = argv[++s->i]; + else opt = ':'; /* missing option argument */ + } + } + } + } else { /* a short option */ + const char *p; + if (s->pos == 0) s->pos = 1; + opt = s->opt = argv[s->i][s->pos++]; + p = strchr((char*)ostr, opt); + if (p == 0) { + opt = '?'; /* unknown option */ + } else if (p[1] == ':') { + if (argv[s->i][s->pos] == 0) { + if (s->i < argc - 1) s->arg = argv[++s->i]; + else opt = ':'; /* missing option argument */ + } else s->arg = &argv[s->i][s->pos]; + s->pos = -1; + } + } + if (s->pos < 0 || argv[s->i][s->pos] == 0) { + ++s->i, s->pos = 0; + if (s->n_args > 0) /* permute */ + for (j = i0; j < s->i; ++j) + ketopt_permute(argv, j, s->n_args); + } + s->ind = s->i - s->n_args; + return opt; +} + +#endif
--- a/irccd/peer.c Sun Feb 07 14:36:28 2021 +0100 +++ b/irccd/peer.c Tue Feb 09 13:00:32 2021 +0100 @@ -93,7 +93,7 @@ return plg; } -static int +static inline int ok(struct peer *p) { peer_send(p, "OK"); @@ -101,6 +101,22 @@ return 0; } +static inline int +error(struct peer *p, const char *fmt, ...) +{ + char buf[IRC_BUF_LEN] = {0}; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof (buf), fmt, ap); + va_end(ap); + + if (buf[0]) + peer_send(p, "ERROR %s", buf); + + return 0; +} + static int plugin_list_set(struct peer *p, char *line, @@ -150,6 +166,20 @@ return 0; } +static const char * +rule_list_to_spaces(const char *value) +{ + static char buf[IRC_RULE_LEN]; + + strlcpy(buf, value, sizeof (buf)); + + for (char *p = buf; *p; ++p) + if (*p == ':') + *p = ' '; + + return buf; +} + /* * PLUGIN-CONFIG plugin [var [value]] */ @@ -280,6 +310,254 @@ } /* + * RULE-ADD accept|drop [(ceiops)=value ...] + */ +static int +cmd_rule_add(struct peer *p, char *line) +{ + const char *errstr; + char *token, *ptr, *dst, key; + enum irc_rule_action act; + struct irc_rule *rule; + size_t index = -1; + + if (sscanf(line, "RULE-ADD %*s") == EOF) + return EINVAL; + + line += strlen("RULE-ADD "); + + if (strncmp(line, "accept", 6) == 0) + act = IRC_RULE_ACCEPT; + else if (strncmp(line, "drop", 4) == 0) + act = IRC_RULE_DROP; + else + return error(p, "invalid action"); + + rule = irc_rule_new(act); + + /* Skip action value. */ + while (*line && !isspace(*line)) + ++line; + while (*line && isspace(*line)) + ++line; + + for (ptr = line; (token = strtok_r(ptr, " ", &ptr)); ) { + dst = NULL; + + if (sscanf(token, "%c=%*s", &key) != 1) { + errno = EINVAL; + goto fail; + } + + switch (*token) { + case 'c': + dst = rule->channels; + break; + case 'e': + dst = rule->events; + break; + case 'i': + if ((index = strtonum(token + 2, 0, LLONG_MAX, &errstr)) == 0 && errstr) + goto fail; + break; + case 'o': + dst = rule->origins; + break; + case 'p': + dst = rule->plugins; + break; + case 's': + dst = rule->servers; + break; + default: + /* TODO: error here. */ + break; + } + + if (dst && irc_rule_add(dst, token + 2) < 0) + goto fail; + } + + irc_bot_rule_insert(rule, index); + + return ok(p); + +fail: + irc_rule_finish(rule); + + return error(p, strerror(errno)); +} + +/* + * RULE-EDIT index [((ceops)(+-)value)|(a=accept|drop) ...] + */ +static int +cmd_rule_edit(struct peer *p, char *line) +{ + char *token, *ptr, *dst, key, attr; + struct irc_rule *rule; + size_t index = -1; + + /* + * Looks like strtonum does not accept when there is text after the + * number. + */ + if (sscanf(line, "RULE-EDIT %zu", &index) != 1) + return EINVAL; + + if (index >= irc_bot_rule_size()) + return ERANGE; + + /* Skip command and index value. */ + line += strlen("RULE-EDIT "); + + while (*line && !isspace(*line)) + ++line; + while (*line && isspace(*line)) + ++line; + + rule = irc_bot_rule_get(index); + + for (ptr = line; (token = strtok_r(ptr, " ", &ptr)); ) { + key = attr = 0; + + if (sscanf(token, "%c%c%*s", &key, &attr) != 2) + return EINVAL; + + if (key == 'a') { + if (attr != '=') + return EINVAL; + + if (strncmp(token + 2, "accept", 6) == 0) + rule->action = IRC_RULE_ACCEPT; + else if (strncmp(token + 2, "drop", 4) == 0) + rule->action = IRC_RULE_DROP; + else + return error(p, "invalid action"); + } else { + dst = NULL; + + switch (key) { + case 'c': + dst = rule->channels; + break; + case 'e': + dst = rule->events; + break; + case 'o': + dst = rule->origins; + break; + case 'p': + dst = rule->plugins; + break; + case 's': + dst = rule->servers; + break; + default: + return EINVAL; + } + + if (attr == '+') { + if (irc_rule_add(dst, token + 2) < 0) + return errno; + } else if (attr == '-') + irc_rule_remove(dst, token + 2); + else + return EINVAL; + } + } + + return ok(p); +} + +/* + * RULE-LIST + */ +static int +cmd_rule_list(struct peer *p, char *line) +{ + (void)line; + + struct irc_rule *rule; + char out[IRC_BUF_LEN]; + FILE *fp; + size_t rulesz = 0; + + if (!(fp = fmemopen(out, sizeof (out), "w"))) + return error(p, "%s", strerror(errno)); + + TAILQ_FOREACH(rule, &irc.rules, link) + rulesz++; + + fprintf(fp, "OK %zu\n", rulesz); + + TAILQ_FOREACH(rule, &irc.rules, link) { + /* Convert : to spaces. */ + fprintf(fp, "%s\n", rule->action == IRC_RULE_ACCEPT ? "accept" : "drop"); + fprintf(fp, "%s\n", rule_list_to_spaces(rule->servers)); + fprintf(fp, "%s\n", rule_list_to_spaces(rule->channels)); + fprintf(fp, "%s\n", rule_list_to_spaces(rule->origins)); + fprintf(fp, "%s\n", rule_list_to_spaces(rule->plugins)); + fprintf(fp, "%s\n", rule_list_to_spaces(rule->events)); + } + + if (feof(fp) || ferror(fp)) { + fclose(fp); + return EMSGSIZE; + } + + fclose(fp); + peer_send(p, "%s", out); + + return 0; +} + +/* + * RULE-MOVE from to + */ +static int +cmd_rule_move(struct peer *p, char *line) +{ + const char *args[2], *errstr; + unsigned long long from, to; + + if (parse(line, args, 2) != 2) + return EINVAL; + if ((from = strtonum(args[0], 0, LLONG_MAX, &errstr)) == 0 && errstr) + return ERANGE; + if ((to = strtonum(args[1], 0, LLONG_MAX, &errstr)) == 0 && errstr) + return ERANGE; + if (from >= irc_bot_rule_size()) + return ERANGE; + + irc_bot_rule_move(from, to); + + return ok(p); +} + +/* + * RULE-REMOVE index + */ +static int +cmd_rule_remove(struct peer *p, char *line) +{ + const char *args[1] = {0}; + size_t index; + + if (parse(line, args, 1) != 1) + return EINVAL; + + index = strtoull(args[0], NULL, 10); + + if (index >= irc_bot_rule_size()) + return ERANGE; + + irc_bot_rule_remove(index); + + return ok(p); +} + +/* * SERVER-DISCONNECT [server] */ static int @@ -564,6 +842,11 @@ { "PLUGIN-RELOAD", cmd_plugin_reload }, { "PLUGIN-TEMPLATE", cmd_plugin_template }, { "PLUGIN-UNLOAD", cmd_plugin_unload }, + { "RULE-ADD", cmd_rule_add }, + { "RULE-EDIT", cmd_rule_edit }, + { "RULE-LIST", cmd_rule_list }, + { "RULE-MOVE", cmd_rule_move }, + { "RULE-REMOVE", cmd_rule_remove }, { "SERVER-DISCONNECT", cmd_server_disconnect }, { "SERVER-INFO", cmd_server_info }, { "SERVER-INVITE", cmd_server_invite }, @@ -601,7 +884,7 @@ if (!c) peer_send(p, "command not found"); else if ((er = c->call(p, line)) != 0) - peer_send(p, "%s", strerror(errno)); + peer_send(p, "%s", strerror(er)); } static void
--- a/irccdctl/CMakeLists.txt Sun Feb 07 14:36:28 2021 +0100 +++ b/irccdctl/CMakeLists.txt Tue Feb 09 13:00:32 2021 +0100 @@ -18,4 +18,4 @@ project(irccdctl) add_executable(irccdctl main.c) -target_link_libraries(irccdctl libirccd) +target_link_libraries(irccdctl libirccd libirccd-ketopt)
--- a/irccdctl/main.c Sun Feb 07 14:36:28 2021 +0100 +++ b/irccdctl/main.c Tue Feb 09 13:00:32 2021 +0100 @@ -27,12 +27,13 @@ #include <errno.h> #include <limits.h> #include <stdarg.h> +#include <stdio.h> #include <stdlib.h> #include <stdnoreturn.h> #include <string.h> #include <unistd.h> -#include <stdio.h> +#include <ketopt.h> #include <irccd/limits.h> #include <irccd/util.h> @@ -488,6 +489,131 @@ } static void +cmd_rule_add(int argc, char **argv) +{ + ketopt_t ko = KETOPT_INIT; + FILE *fp; + char out[IRC_BUF_LEN]; + + if (!(fp = fmemopen(out, sizeof (out) - 1, "w"))) + err(1, "fmemopen"); + + /* TODO: invalid option. */ + for (int ch; (ch = ketopt(&ko, argc, argv, 0, "c:e:i:o:p:s:", NULL)) != -1; ) + fprintf(fp, "%c=%s ", ch, ko.arg); + + argc -= ko.ind; + argv += ko.ind; + + if (argc < 1) + errx(1, "missing accept or drop rule action"); + + fprintf(fp, "%s", argv[0]); + + if (ferror(fp) || feof(fp)) + err(1, "fprintf"); + + fclose(fp); + req("RULE-ADD %s %s", argv[0], out); + ok(); +} + +static void +cmd_rule_edit(int argc, char **argv) +{ + ketopt_t ko = KETOPT_INIT; + FILE *fp; + char out[IRC_BUF_LEN]; + + if (!(fp = fmemopen(out, sizeof (out) - 1, "w"))) + err(1, "fmemopen"); + + /* TODO: invalid option. */ + for (int ch; (ch = ketopt(&ko, argc, argv, 0, "a:C:c:E:e:O:o:P:p:S:s:", NULL)) != -1; ) { + if (ch == 'a') + fprintf(fp, "a=%s ", ko.arg); + else + fprintf(fp, "%c%c%s ", tolower(ch), isupper(ch) ? '-' : '+', ko.arg); + } + + argc -= ko.ind; + argv += ko.ind; + + if (argc < 1) + errx(1, "missing rule index"); + + if (ferror(fp) || feof(fp)) + err(1, "fprintf"); + + fclose(fp); + req("RULE-EDIT %s %s", argv[0], out); + ok(); +} + +/* + * Response: + * + * OK <n> + * accept + * server1 server2 server3 ... + * channel1 channel2 channel3 ... + * origin1 origin2 origin3 ... + * plugin1 plugin2 plugin3 ... + * event1 event2 plugin3 ... + * (repeat for every rule in <n>) + */ +static void +cmd_rule_list(int argc, char **argv) +{ + (void)argc; + (void)argv; + + size_t num = 0; + + req("RULE-LIST"); + + if (sscanf(ok(), "%zu", &num) != 1) + errx(1, "could not retrieve rule list"); + + for (size_t i = 0; i < num; ++i) { + printf("%-16s: %zu\n", "index", i); + printf("%-16s: %s\n", "action", poll()); + printf("%-16s: %s\n", "servers", poll()); + printf("%-16s: %s\n", "channels", poll()); + printf("%-16s: %s\n", "origins", poll()); + printf("%-16s: %s\n", "plugins", poll()); + printf("%-16s: %s\n", "events", poll()); + printf("\n"); + } +} + +static void +cmd_rule_move(int argc, char **argv) +{ + (void)argc; + + long long from, to; + const char *errstr; + + if ((from = strtonum(argv[0], 0, LLONG_MAX, &errstr)) == 0 && errstr) + err(1, "%s", argv[0]); + if ((to = strtonum(argv[1], 0, LLONG_MAX, &errstr)) == 0 && errstr) + err(1, "%s", argv[1]); + + req("RULE-MOVE %lld %lld", from, to); + ok(); +} + +static void +cmd_rule_remove(int argc, char **argv) +{ + (void)argc; + + req("RULE-REMOVE %s", argv[0]); + ok(); +} + +static void cmd_server_disconnect(int argc, char **argv) { if (argc == 1) @@ -679,6 +805,11 @@ { "plugin-reload", 0, 1, cmd_plugin_reload }, { "plugin-template", 1, 3, cmd_plugin_template }, { "plugin-unload", 0, 1, cmd_plugin_unload }, + { "rule-add", -1, -1, cmd_rule_add }, + { "rule-edit", -1, -1, cmd_rule_edit }, + { "rule-list", 0, 0, cmd_rule_list }, + { "rule-move", 2, 2, cmd_rule_move }, + { "rule-remove", 1, 1, cmd_rule_remove }, { "server-disconnect", 0, 1, cmd_server_disconnect }, { "server-info", 1, 1, cmd_server_info }, { "server-join", 2, 3, cmd_server_join }, @@ -716,7 +847,7 @@ --argc; ++argv; - if (argc < c->minargs || argc > c->maxargs) + if ((c->minargs != -1 && argc < c->minargs) || (c->minargs != -1 && argc > c->maxargs)) errx(1, "abort: invalid number of arguments"); c->exec(argc, argv); @@ -725,16 +856,59 @@ noreturn static void usage(void) { - fprintf(stderr, "usage: %s [-v] [-s sock] command [arguments...]\n", getprogname()); + fprintf(stderr, "usage: %s [-v] [-s sock] command [options...] [arguments...]\n", getprogname()); + exit(1); +} + +noreturn static void +help(void) +{ + fprintf(stderr, "usage: %s hook-add name path\n", getprogname()); + fprintf(stderr, " %s hook-list\n", getprogname()); + fprintf(stderr, " %s hook-remove id\n", getprogname()); + fprintf(stderr, " %s plugin-config id [variable [value]]\n", getprogname()); + fprintf(stderr, " %s plugin-info id\n", getprogname()); + fprintf(stderr, " %s plugin-list\n", getprogname()); + fprintf(stderr, " %s plugin-load name\n", getprogname()); + fprintf(stderr, " %s plugin-path [variable [value]]\n", getprogname()); + fprintf(stderr, " %s plugin-template [variable [value]]\n", getprogname()); + fprintf(stderr, " %s plugin-reload [plugin]\n", getprogname()); + fprintf(stderr, " %s plugin-unload [plugin]\n", getprogname()); + fprintf(stderr, " %s rule-add [-c channel] [-e event] [-i index] [-o origin] [-p plugin] [-s server] accept|drop\n", getprogname()); + fprintf(stderr, " %s rule-edit [-a accept|drop] [-c|C channel] [-e|E event] [-o|O origin] [-s|S server] index\n", getprogname()); + fprintf(stderr, " %s rule-list\n", getprogname()); + fprintf(stderr, " %s rule-move from to\n", getprogname()); + fprintf(stderr, " %s rule-remove index\n", getprogname()); + fprintf(stderr, " %s server-connect [-n nickname] [-r realname] [-u username] [-p port] id hostname\n", getprogname()); + fprintf(stderr, " %s server-disconnect [server]\n", getprogname()); + fprintf(stderr, " %s server-info server\n", getprogname()); + fprintf(stderr, " %s server-invite server target channel\n", getprogname()); + fprintf(stderr, " %s server-join server channel [password]\n", getprogname()); + fprintf(stderr, " %s server-kick server target channel [reason]\n", getprogname()); + fprintf(stderr, " %s server-list\n", getprogname()); + fprintf(stderr, " %s server-me server target message\n", getprogname()); + fprintf(stderr, " %s server-message server target message\n", getprogname()); + fprintf(stderr, " %s server-mode server target mode [limit] [user] [mask]\n", getprogname()); + fprintf(stderr, " %s server-nick server nickname\n", getprogname()); + fprintf(stderr, " %s server-notice server target message\n", getprogname()); + fprintf(stderr, " %s server-part server channel [reason]\n", getprogname()); + fprintf(stderr, " %s server-reconnect [server]\n", getprogname()); + fprintf(stderr, " %s server-topic server channel topic\n", getprogname()); + fprintf(stderr, " %s watch\n", getprogname()); exit(1); } int main(int argc, char **argv) { + ketopt_t ko = KETOPT_INIT; + setprogname("irccdctl"); - for (int ch; (ch = getopt(argc, argv, "s:v")) != -1; ) { + --argc; + ++argv; + + for (int ch; (ch = ketopt(&ko, argc, argv, 0, "s:v", NULL)) != -1; ) { switch (ch) { case 's': strlcpy(sockaddr.sun_path, optarg, sizeof (sockaddr.sun_path)); @@ -743,15 +917,18 @@ verbose = 1; break; default: + usage(); break; } } - argc -= optind; - argv += optind; + argc -= ko.ind; + argv += ko.ind; if (argc < 1) usage(); + else if (strcmp(argv[0], "help") == 0) + help(); dial(); check();
--- a/lib/irccd/irccd.c Sun Feb 07 14:36:28 2021 +0100 +++ b/lib/irccd/irccd.c Tue Feb 09 13:00:32 2021 +0100 @@ -209,18 +209,6 @@ return irc_plugin_loader_open(ldr, path); } -static inline size_t -rulescount(void) -{ - const struct irc_rule *r; - size_t total = 0; - - TAILQ_FOREACH(r, &irc.rules, link) - total++; - - return total; -} - void irc_bot_init(void) { @@ -399,22 +387,62 @@ if (index == 0) TAILQ_INSERT_HEAD(&irc.rules, rule, link); - else if (index >= rulescount()) + else if (index >= irc_bot_rule_size()) TAILQ_INSERT_TAIL(&irc.rules, rule, link); else { - struct irc_rule *pos = TAILQ_FIRST(&irc.rules); + struct irc_rule *pos; - for (size_t i = 0; i < index; ++i) + for (pos = TAILQ_FIRST(&irc.rules); --index; ) pos = TAILQ_NEXT(pos, link); TAILQ_INSERT_AFTER(&irc.rules, pos, rule, link); } } +struct irc_rule * +irc_bot_rule_get(size_t index) +{ + assert(index < irc_bot_rule_size()); + + struct irc_rule *rule; + + for (rule = TAILQ_FIRST(&irc.rules); index-- != 0; ) + rule = TAILQ_NEXT(rule, link); + + return rule; +} + +void +irc_bot_rule_move(size_t from, size_t to) +{ + assert(from < irc_bot_rule_size()); + + struct irc_rule *f, *t; + + if (from == to) + return; + + f = t = TAILQ_FIRST(&irc.rules); + + while (from--) + f = TAILQ_NEXT(f, link); + + TAILQ_REMOVE(&irc.rules, f, link); + + if (to == 0) + TAILQ_INSERT_HEAD(&irc.rules, f, link); + else { + while (TAILQ_NEXT(t, link) && to--) + t = TAILQ_NEXT(t, link); + + TAILQ_INSERT_AFTER(&irc.rules, t, f, link); + } +} + void irc_bot_rule_remove(size_t index) { - assert(index < rulescount()); + assert(index < irc_bot_rule_size()); struct irc_rule *pos = TAILQ_FIRST(&irc.rules); @@ -424,6 +452,18 @@ TAILQ_REMOVE(&irc.rules, pos, link); } +size_t +irc_bot_rule_size(void) +{ + const struct irc_rule *r; + size_t total = 0; + + TAILQ_FOREACH(r, &irc.rules, link) + total++; + + return total; +} + void irc_bot_rule_clear(void) {
--- a/lib/irccd/irccd.h Sun Feb 07 14:36:28 2021 +0100 +++ b/lib/irccd/irccd.h Tue Feb 09 13:00:32 2021 +0100 @@ -68,9 +68,18 @@ void irc_bot_rule_insert(struct irc_rule *, size_t); +struct irc_rule * +irc_bot_rule_get(size_t); + +void +irc_bot_rule_move(size_t, size_t); + void irc_bot_rule_remove(size_t); +size_t +irc_bot_rule_size(void); + void irc_bot_rule_clear(void);