From 4928f9ed62b62c3704bce093369ee476268b435f Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sun, 3 May 2015 16:47:16 +0200
Subject: degesch: add a read-only /set command
---
degesch.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 176 insertions(+)
diff --git a/degesch.c b/degesch.c
index 55fb325..b0a3e04 100644
--- a/degesch.c
+++ b/degesch.c
@@ -3547,6 +3547,106 @@ log_outcoming_notice (struct server *s,
send_autosplit_message ((s), (struct send_autosplit_args) \
{ "NOTICE", (target), (message), log_outcoming_notice, "", "" })
+// --- Configuration dumper ----------------------------------------------------
+
+struct config_dump_level
+{
+ struct config_dump_level *next; ///< Next print level
+ const char *name; ///< Name of the object
+};
+
+struct config_dump_data
+{
+ struct config_dump_level *head; ///< The first level
+ struct config_dump_level **tail; ///< Where to place further levels
+ struct str_vector *output; ///< Where to place new entries
+};
+
+static void config_dump_item
+ (struct config_item_ *item, struct config_dump_data *data);
+
+static void
+config_dump_children
+ (struct config_item_ *object, struct config_dump_data *data)
+{
+ hard_assert (object->type = CONFIG_ITEM_OBJECT);
+
+ struct config_dump_level level;
+ level.next = NULL;
+
+ struct config_dump_level **prev_tail = data->tail;
+ *data->tail = &level;
+ data->tail = &level.next;
+
+ struct str_map_iter iter;
+ str_map_iter_init (&iter, &object->value.object);
+ struct config_item_ *child;
+ while ((child = str_map_iter_next (&iter)))
+ {
+ level.name = iter.link->key;
+ config_dump_item (child, data);
+ }
+
+ data->tail = prev_tail;
+ *data->tail = NULL;
+}
+
+static void
+config_dump_item (struct config_item_ *item, struct config_dump_data *data)
+{
+ struct str line;
+ str_init (&line);
+
+ struct config_dump_level *iter = data->head;
+ if (iter)
+ {
+ str_append (&line, iter->name);
+ iter = iter->next;
+ }
+ for (; iter; iter = iter->next)
+ str_append_printf (&line, ".%s", iter->name);
+
+ // Empty objects will show as such
+ if (item->type == CONFIG_ITEM_OBJECT
+ && item->value.object.len)
+ {
+ config_dump_children (item, data);
+ return;
+ }
+
+ // Don't bother writing out null values everywhere
+ struct config_schema *schema = item->schema;
+ bool has_default = schema && schema->default_;
+ if (item->type != CONFIG_ITEM_NULL || has_default)
+ {
+ str_append (&line, " = ");
+
+ struct str value;
+ str_init (&value);
+ config_item_write (item, false, &value);
+ str_append_str (&line, &value);
+
+ if (has_default && strcmp (schema->default_, value.str))
+ str_append_printf (&line, " (default: %s)", schema->default_);
+ else if (!has_default && item->type != CONFIG_ITEM_NULL)
+ str_append_printf (&line, " (default: %s)", "null");
+ str_free (&value);
+ }
+
+ str_vector_add_owned (data->output, str_steal (&line));
+}
+
+static void
+config_dump (struct config_item_ *root, struct str_vector *output)
+{
+ struct config_dump_data data;
+ data.head = NULL;
+ data.tail = &data.head;
+ data.output = output;
+
+ config_dump_item (root, &data);
+}
+
// --- User input handling -----------------------------------------------------
static bool handle_command_help (struct app_context *, char *);
@@ -3673,6 +3773,80 @@ handle_command_buffer (struct app_context *ctx, char *arguments)
return true;
}
+static int
+str_vector_sort_cb (const void *a, const void *b)
+{
+ return strcmp (*(const char **) a, *(const char **) b);
+}
+
+static void
+str_vector_sort (struct str_vector *self)
+{
+ qsort (self->vector, self->len, sizeof *self->vector, str_vector_sort_cb);
+}
+
+static bool
+handle_command_set (struct app_context *ctx, char *arguments)
+{
+ struct str_vector all;
+ str_vector_init (&all);
+ config_dump (ctx->config.root, &all);
+ str_vector_sort (&all);
+
+ char *option = "*";
+ if (*arguments)
+ option = cut_word (&arguments);
+
+ // Filter out results by wildcard matching
+ for (size_t i = 0; i < all.len; i++)
+ {
+ char *key = xstrdup (all.vector[i]);
+ char *end = strchr (key, ' ');
+ if (end)
+ *end = '\0';
+ if (fnmatch (option, key, 0))
+ str_vector_remove (&all, i--);
+ free (key);
+ }
+
+ if (!*arguments)
+ {
+ buffer_send_status (ctx, ctx->global_buffer, "%s", "");
+ for (size_t i = 0; i < all.len; i++)
+ buffer_send_status (ctx, ctx->global_buffer, "%s", all.vector[i]);
+ str_vector_free (&all);
+ return true;
+ }
+
+ char *op = cut_word (&arguments);
+ bool add = false;
+ bool remove = false;
+
+ if (!strcmp (op, "+=")) add = true;
+ else if (!strcmp (op, "-=")) remove = true;
+ else if (strcmp (op, "=")) return false;
+
+ if (!arguments)
+ return false;
+
+ char *value = cut_word (&arguments);
+ struct error *e = NULL;
+ struct config_item_ *new_ =
+ config_item_parse (value, strlen (value), true, &e);
+ if (e)
+ {
+ buffer_send_error (ctx, ctx->global_buffer,
+ "Invalid value: %s", e->message);
+ error_free (e);
+ return true;
+ }
+
+ // TODO: try to set the value, or modify the string list
+ buffer_send_error (ctx, ctx->global_buffer, "Not implemented");
+ config_item_destroy (new_);
+ return true;
+}
+
static bool
handle_command_msg (struct app_context *ctx, char *arguments)
{
@@ -3901,6 +4075,8 @@ g_command_handlers[] =
"[]" },
{ "buffer", handle_command_buffer, "Manage buffers",
"list | clear | move | { close [ | ] } | " },
+ { "set", handle_command_set, "Manage configuration",
+ "[