Requested changes from Pari, reviewed by Russell.
authorJeff Peeler <jpeeler@digium.com>
Tue, 12 Feb 2008 00:24:36 +0000 (00:24 +0000)
committerJeff Peeler <jpeeler@digium.com>
Tue, 12 Feb 2008 00:24:36 +0000 (00:24 +0000)
Added ability to retrieve list of categories in a config file.
Added ability to retrieve the content of a particular category.
Added ability to empty a context.
Created new action to create a new file.
Updated delete action to allow deletion by line number with respect to category.
Added new action insert to add new variable to category at specified line.
Updated action newcat to allow new category to be inserted in file above another existing category.

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@103331 65c4cc65-6c06-0410-ace0-fbb531ad65f3

CHANGES
include/asterisk/config.h
main/config.c
main/manager.c

diff --git a/CHANGES b/CHANGES
index 79218ee..1f727f9 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -39,6 +39,14 @@ AMI - The manager (TCP/TLS/HTTP)
      Reporting privilege, instead of only under Call or System.
   * The IAX* commands now require either System or Reporting privilege, to
      mirror the privileges of the SIP* commands.
+  * Added ability to retrieve list of categories in a config file.
+  * Added ability to retrieve the content of a particular category.
+  * Added ability to empty a context.
+  * Created new action to create a new file.
+  * Updated delete action to allow deletion by line number with respect to category.
+  * Added new action insert to add new variable to category at specified line.
+  * Updated action newcat to allow new category to be inserted in file above another
+    existing category.
 
 Dialplan functions
 ------------------
index fe9d49d..4757d51 100644 (file)
@@ -254,7 +254,18 @@ const char *ast_config_option(struct ast_config *cfg, const char *cat, const cha
 
 struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno);
 void ast_category_append(struct ast_config *config, struct ast_category *cat);
+
+/*! 
+ * \brief Inserts new category
+ * \param config which config to use
+ * \param cat newly created category to insert
+ * \param match which category to insert above
+ * This function is used to insert a new category above another category
+ * matching the match parameter.
+ */
+void ast_category_insert(struct ast_config *config, struct ast_category *cat, const char *match);
 int ast_category_delete(struct ast_config *cfg, const char *category);
+int ast_category_empty(struct ast_config *cfg, const char *category);
 void ast_category_destroy(struct ast_category *cat);
 struct ast_variable *ast_category_detach_variables(struct ast_category *cat);
 void ast_category_rename(struct ast_category *cat, const char *name);
@@ -264,7 +275,8 @@ struct ast_config_include *ast_include_new(struct ast_config *conf, const char *
 struct ast_config_include *ast_include_find(struct ast_config *conf, const char *included_file);
 void ast_include_rename(struct ast_config *conf, const char *from_file, const char *to_file);
 void ast_variable_append(struct ast_category *category, struct ast_variable *variable);
-int ast_variable_delete(struct ast_category *category, const char *variable, const char *match);
+void ast_variable_insert(struct ast_category *category, struct ast_variable *variable, const char *line);
+int ast_variable_delete(struct ast_category *category, const char *variable, const char *match, const char *line);
 int ast_variable_update(struct ast_category *category, const char *variable, 
                                                const char *value, const char *match, unsigned int object);
 
index d5b0738..ce7bb50 100644 (file)
@@ -354,6 +354,28 @@ void ast_variable_append(struct ast_category *category, struct ast_variable *var
                category->last = category->last->next;
 }
 
+void ast_variable_insert(struct ast_category *category, struct ast_variable *variable, const char *line)
+{
+       struct ast_variable *cur = category->root;
+       int lineno;
+       int insertline;
+
+       if (!variable || sscanf(line, "%d", &insertline) != 1)
+               return;
+       if (!insertline) {
+               variable->next = category->root;
+               category->root = variable;
+       } else {
+               for (lineno = 1; lineno < insertline; lineno++) {
+                       cur = cur->next;
+                       if (!cur->next)
+                               break;
+               }
+               variable->next = cur->next;
+               cur->next = variable;
+       }
+}
+
 void ast_variables_destroy(struct ast_variable *v)
 {
        struct ast_variable *vn;
@@ -481,6 +503,26 @@ void ast_category_append(struct ast_config *config, struct ast_category *categor
        config->current = category;
 }
 
+void ast_category_insert(struct ast_config *config, struct ast_category *cat, const char *match)
+{
+       struct ast_category *cur_category;
+
+       if (!cat || !match)
+               return;
+       if (!strcasecmp(config->root->name, match)) {
+               cat->next = config->root;
+               config->root = cat;
+               return;
+       } 
+       for (cur_category = config->root; cur_category; cur_category = cur_category->next) {
+               if (!strcasecmp(cur_category->next->name, match)) {
+                       cat->next = cur_category->next;
+                       cur_category->next = cat;
+                       break;
+               }
+       }
+}
+
 static void ast_destroy_comments(struct ast_category *cat)
 {
        struct ast_comment *n, *p;
@@ -629,10 +671,11 @@ struct ast_config *ast_config_new(void)
        return config;
 }
 
-int ast_variable_delete(struct ast_category *category, const char *variable, const char *match)
+int ast_variable_delete(struct ast_category *category, const char *variable, const char *match, const char *line)
 {
        struct ast_variable *cur, *prev=NULL, *curn;
        int res = -1;
+       int lineno = 0;
 
        cur = category->root;
        while (cur) {
@@ -658,7 +701,7 @@ int ast_variable_delete(struct ast_category *category, const char *variable, con
        cur = category->root;
        while (cur) {
                curn = cur->next;
-               if (!strcasecmp(cur->name, variable) && (ast_strlen_zero(match) || !strcasecmp(cur->value, match))) {
+               if ((!ast_strlen_zero(line) && lineno == atoi(line)) || (ast_strlen_zero(line) && !strcasecmp(cur->name, variable) && (ast_strlen_zero(match) || !strcasecmp(cur->value, match)))) {
                        if (prev) {
                                prev->next = cur->next;
                                if (cur == category->last)
@@ -675,6 +718,7 @@ int ast_variable_delete(struct ast_category *category, const char *variable, con
                        prev = cur;
 
                cur = curn;
+               lineno++;
        }
        return res;
 }
@@ -760,6 +804,22 @@ int ast_category_delete(struct ast_config *cfg, const char *category)
        return -1;
 }
 
+int ast_category_empty(struct ast_config *cfg, const char *category)
+{
+       struct ast_category *cat;
+
+       for (cat = cfg->root; cat; cat = cat->next) {
+               if (!strcasecmp(cat->name, category))
+                       continue;
+               ast_variables_destroy(cat->root);
+               cat->root = NULL;
+               cat->last = NULL;
+               return 0;
+       }
+
+       return -1;
+}
+
 void ast_config_destroy(struct ast_config *cfg)
 {
        struct ast_category *cat, *catn;
index 33973f3..a5319e7 100644 (file)
@@ -74,6 +74,20 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/term.h"
 #include "asterisk/astobj2.h"
 
+enum error_type {
+       UNKNOWN_ACTION = 1,
+       UNKNOWN_CATEGORY,
+       UNSPECIFIED_CATEGORY,
+       UNSPECIFIED_ARGUMENT,
+       FAILURE_ALLOCATION,
+       FAILURE_DELCAT,
+       FAILURE_EMPTYCAT,
+       FAILURE_UPDATE,
+       FAILURE_DELETE,
+       FAILURE_APPEND
+};
+
+
 /*!
  * Linked list of events.
  * Global events are appended to the list by append_event().
@@ -1053,17 +1067,19 @@ static int action_ping(struct mansession *s, const struct message *m)
 
 static char mandescr_getconfig[] =
 "Description: A 'GetConfig' action will dump the contents of a configuration\n"
-"file by category and contents.\n"
-"Variables:\n"
-"   Filename: Configuration filename (e.g. foo.conf)\n";
+"file by category and contents or optionally by specified category only.\n"
+"Variables: (Names marked with * are required)\n"
+"   *Filename: Configuration filename (e.g. foo.conf)\n"
+"   Category: Category in configuration file\n";
 
 static int action_getconfig(struct mansession *s, const struct message *m)
 {
        struct ast_config *cfg;
        const char *fn = astman_get_header(m, "Filename");
+       const char *category = astman_get_header(m, "Category");
        int catcount = 0;
        int lineno = 0;
-       char *category=NULL;
+       char *cur_category = NULL;
        struct ast_variable *v;
        struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
 
@@ -1075,20 +1091,63 @@ static int action_getconfig(struct mansession *s, const struct message *m)
                astman_send_error(s, m, "Config file not found");
                return 0;
        }
+
+       astman_start_ack(s, m);
+       while ((cur_category = ast_category_browse(cfg, cur_category))) {
+               if (ast_strlen_zero(category) || (!ast_strlen_zero(category) && !strcmp(category, cur_category))) {
+                       lineno = 0;
+                       astman_append(s, "Category-%06d: %s\r\n", catcount, cur_category);
+                       for (v = ast_variable_browse(cfg, cur_category); v; v = v->next)
+                               astman_append(s, "Line-%06d-%06d: %s=%s\r\n", catcount, lineno++, v->name, v->value);
+                       catcount++;
+               }
+       }
+       if (!ast_strlen_zero(category) && catcount == 0) /* TODO: actually, a config with no categories doesn't even get loaded */
+               astman_append(s, "No categories found");
+       ast_config_destroy(cfg);
+       astman_append(s, "\r\n");
+
+       return 0;
+}
+
+static char mandescr_listcategories[] =
+"Description: A 'ListCategories' action will dump the categories in\n"
+"a given file.\n"
+"Variables:\n"
+"   Filename: Configuration filename (e.g. foo.conf)\n";
+
+static int action_listcategories(struct mansession *s, const struct message *m)
+{
+       struct ast_config *cfg;
+       const char *fn = astman_get_header(m, "Filename");
+       char *category = NULL;
+       struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
+       int catcount = 0;
+
+       if (ast_strlen_zero(fn)) {
+               astman_send_error(s, m, "Filename not specified");
+               return 0;
+       }
+       if (!(cfg = ast_config_load(fn, config_flags))) {
+               astman_send_error(s, m, "Config file not found or file has invalid syntax");
+               return 0;
+       }
        astman_start_ack(s, m);
        while ((category = ast_category_browse(cfg, category))) {
-               lineno = 0;
                astman_append(s, "Category-%06d: %s\r\n", catcount, category);
-               for (v = ast_variable_browse(cfg, category); v; v = v->next)
-                       astman_append(s, "Line-%06d-%06d: %s=%s\r\n", catcount, lineno++, v->name, v->value);
                catcount++;
        }
+       if (catcount == 0) /* TODO: actually, a config with no categories doesn't even get loaded */
+               astman_append(s, "Error: no categories found");
        ast_config_destroy(cfg);
        astman_append(s, "\r\n");
 
        return 0;
 }
 
+
+       
+
 /*! The amount of space in out must be at least ( 2 * strlen(in) + 1 ) */
 static void json_escape(char *out, const char *in)
 {
@@ -1171,11 +1230,11 @@ static int action_getconfigjson(struct mansession *s, const struct message *m)
 }
 
 /* helper function for action_updateconfig */
-static void handle_updates(struct mansession *s, const struct message *m, struct ast_config *cfg, const char *dfn)
+static enum error_type handle_updates(struct mansession *s, const struct message *m, struct ast_config *cfg, const char *dfn)
 {
        int x;
        char hdr[40];
-       const char *action, *cat, *var, *value, *match;
+       const char *action, *cat, *var, *value, *match, *line;
        struct ast_category *category;
        struct ast_variable *v;
 
@@ -1198,38 +1257,72 @@ static void handle_updates(struct mansession *s, const struct message *m, struct
                }
                snprintf(hdr, sizeof(hdr), "Match-%06d", x);
                match = astman_get_header(m, hdr);
+               snprintf(hdr, sizeof(hdr), "Line-%06d", x);
+               line = astman_get_header(m, hdr);
                if (!strcasecmp(action, "newcat")) {
-                       if (!ast_strlen_zero(cat)) {
-                               category = ast_category_new(cat, dfn, 99999);
-                               if (category) {
-                                       ast_category_append(cfg, category);
-                               }
-                       }
+                       if (ast_strlen_zero(cat))
+                               return UNSPECIFIED_CATEGORY;
+                       if (!(category = ast_category_new(cat, dfn, -1)))
+                               return FAILURE_ALLOCATION;
+                       if (ast_strlen_zero(match)) {
+                               ast_category_append(cfg, category);
+                       } else
+                               ast_category_insert(cfg, category, match);
                } else if (!strcasecmp(action, "renamecat")) {
-                       if (!ast_strlen_zero(cat) && !ast_strlen_zero(value)) {
-                               category = ast_category_get(cfg, cat);
-                               if (category)
-                                       ast_category_rename(category, value);
-                       }
+                       if (ast_strlen_zero(cat) || ast_strlen_zero(value))
+                               return UNSPECIFIED_ARGUMENT;
+                       if (!(category = ast_category_get(cfg, cat)))
+                               return UNKNOWN_CATEGORY;
+                       ast_category_rename(category, value);
                } else if (!strcasecmp(action, "delcat")) {
-                       if (!ast_strlen_zero(cat))
-                               ast_category_delete(cfg, cat);
+                       if (ast_strlen_zero(cat))
+                               return UNSPECIFIED_CATEGORY;
+                       if (ast_category_delete(cfg, cat))
+                               return FAILURE_DELCAT;
+               } else if (!strcasecmp(action, "emptycat")) {
+                       if (ast_strlen_zero(cat))
+                               return UNSPECIFIED_CATEGORY;
+                       if (ast_category_empty(cfg, cat))
+                               return FAILURE_EMPTYCAT;
                } else if (!strcasecmp(action, "update")) {
-                       if (!ast_strlen_zero(cat) && !ast_strlen_zero(var) && (category = ast_category_get(cfg, cat)))
-                               ast_variable_update(category, var, value, match, object);
+                       if (ast_strlen_zero(cat) || ast_strlen_zero(var))
+                               return UNSPECIFIED_ARGUMENT;
+                       if (!(category = ast_category_get(cfg,cat)))
+                               return UNKNOWN_CATEGORY;
+                       if (ast_variable_update(category, var, value, match, object))
+                               return FAILURE_UPDATE;
                } else if (!strcasecmp(action, "delete")) {
-                       if (!ast_strlen_zero(cat) && !ast_strlen_zero(var) && (category = ast_category_get(cfg, cat)))
-                               ast_variable_delete(category, var, match);
+                       if (ast_strlen_zero(cat) || (ast_strlen_zero(var) && ast_strlen_zero(line)))
+                               return UNSPECIFIED_ARGUMENT;
+                       if (!(category = ast_category_get(cfg, cat)))
+                               return UNKNOWN_CATEGORY;
+                       if (ast_variable_delete(category, var, match, line))
+                               return FAILURE_DELETE;
                } else if (!strcasecmp(action, "append")) {
-                       if (!ast_strlen_zero(cat) && !ast_strlen_zero(var) &&
-                               (category = ast_category_get(cfg, cat)) &&
-                               (v = ast_variable_new(var, value, dfn))) {
-                               if (object || (match && !strcasecmp(match, "object")))
-                                       v->object = 1;
-                               ast_variable_append(category, v);
-                       }
+                       if (ast_strlen_zero(cat) || ast_strlen_zero(var))
+                               return UNSPECIFIED_ARGUMENT;
+                       if (!(category = ast_category_get(cfg, cat)))
+                               return UNKNOWN_CATEGORY;        
+                       if (!(v = ast_variable_new(var, value, dfn)))
+                               return FAILURE_ALLOCATION;
+                       if (object || (match && !strcasecmp(match, "object")))
+                               v->object = 1;
+                       ast_variable_append(category, v);
+               } else if (!strcasecmp(action, "insert")) {
+                       if (ast_strlen_zero(cat) || ast_strlen_zero(var) || ast_strlen_zero(line))
+                               return UNSPECIFIED_ARGUMENT;
+                       if (!(category = ast_category_get(cfg, cat)))
+                               return UNKNOWN_CATEGORY;
+                       if (!(v = ast_variable_new(var, value, dfn)))
+                               return FAILURE_ALLOCATION;
+                       ast_variable_insert(category, v, line);
+               }
+               else {
+                       ast_log(LOG_WARNING, "Action-%06d: %s not handled\n", x, action);
+                       return UNKNOWN_ACTION;
                }
        }
+       return 0;
 }
 
 static char mandescr_updateconfig[] =
@@ -1239,11 +1332,12 @@ static char mandescr_updateconfig[] =
 "   SrcFilename:   Configuration filename to read(e.g. foo.conf)\n"
 "   DstFilename:   Configuration filename to write(e.g. foo.conf)\n"
 "   Reload:        Whether or not a reload should take place (or name of specific module)\n"
-"   Action-XXXXXX: Action to Take (NewCat,RenameCat,DelCat,Update,Delete,Append)\n"
+"   Action-XXXXXX: Action to Take (NewCat,RenameCat,DelCat,EmptyCat,Update,Delete,Append,Insert)\n"
 "   Cat-XXXXXX:    Category to operate on\n"
 "   Var-XXXXXX:    Variable to work on\n"
 "   Value-XXXXXX:  Value to work on\n"
-"   Match-XXXXXX:  Extra match required to match line\n";
+"   Match-XXXXXX:  Extra match required to match line\n"
+"   Line-XXXXXX:   Line in category to operate on (used with delete and insert actions)\n";
 
 static int action_updateconfig(struct mansession *s, const struct message *m)
 {
@@ -1253,32 +1347,93 @@ static int action_updateconfig(struct mansession *s, const struct message *m)
        int res;
        const char *rld = astman_get_header(m, "Reload");
        struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
+       enum error_type result;
 
        if (ast_strlen_zero(sfn) || ast_strlen_zero(dfn)) {
                astman_send_error(s, m, "Filename not specified");
                return 0;
        }
-       if (!(cfg = ast_config_load(sfn, config_flags))) {
-               astman_send_error(s, m, "Config file not found");
-               return 0;
-       }
-       handle_updates(s, m, cfg, dfn);
-       ast_include_rename(cfg, sfn, dfn); /* change the include references from dfn to sfn, so things match up */
-       res = config_text_file_save(dfn, cfg, "Manager");
-       ast_config_destroy(cfg);
-       if (res) {
-               astman_send_error(s, m, "Save of config failed");
-               return 0;
-       }
-       astman_send_ack(s, m, NULL);
-       if (!ast_strlen_zero(rld)) {
-               if (ast_true(rld))
-                       rld = NULL;
-               ast_module_reload(rld);
+       if (!(cfg = ast_config_load(sfn, config_flags))) {
+        astman_send_error(s, m, "Config file not found");
+        return 0;
+    }
+       result = handle_updates(s, m, cfg, dfn);
+       if (!result) {
+               ast_include_rename(cfg, sfn, dfn); /* change the include references from dfn to sfn, so things match up */
+               res = config_text_file_save(dfn, cfg, "Manager");
+               ast_config_destroy(cfg);
+               if (res) {
+                       astman_send_error(s, m, "Save of config failed");
+                       return 0;
+               }
+               astman_send_ack(s, m, NULL);
+               if (!ast_strlen_zero(rld)) {
+                       if (ast_true(rld))
+                               rld = NULL;
+                       ast_module_reload(rld);
+               }
+       } else {
+               ast_config_destroy(cfg);
+               switch(result) {
+               case UNKNOWN_ACTION:
+                       astman_send_error(s, m, "Unknown action command");
+                       break;
+               case UNKNOWN_CATEGORY:
+                       astman_send_error(s, m, "Given category does not exist");
+                       break;
+               case UNSPECIFIED_CATEGORY:
+                       astman_send_error(s, m, "Category not specified");
+                       break;
+               case UNSPECIFIED_ARGUMENT:
+                       astman_send_error(s, m, "Problem with category, value, or line (if required)");
+                       break;
+               case FAILURE_ALLOCATION:
+                       astman_send_error(s, m, "Memory allocation failure, this should not happen");
+                       break;
+               case FAILURE_DELCAT:
+                       astman_send_error(s, m, "Delete category did not complete successfully");
+                       break;
+               case FAILURE_EMPTYCAT:
+                       astman_send_error(s, m, "Empty category did not complete successfully");
+                       break;
+               case FAILURE_UPDATE:
+                       astman_send_error(s, m, "Update did not complete successfully");
+                       break;
+               case FAILURE_DELETE:
+                       astman_send_error(s, m, "Delete did not complete successfully");
+                       break;
+               case FAILURE_APPEND:
+                       astman_send_error(s, m, "Append did not complete successfully");
+                       break;
+               }
        }
        return 0;
 }
 
+static char mandescr_createconfig[] =
+"Description: A 'CreateConfig' action will create an empty file in the\n"
+"configuration directory. This action is intended to be used before an\n"
+"UpdateConfig action.\n"
+"Variables\n"
+"   Filename:   The configuration filename to create (e.g. foo.conf)\n";
+
+static int action_createconfig(struct mansession *s, const struct message *m)
+{
+       int fd;
+       const char *fn = astman_get_header(m, "Filename");
+       struct ast_str *filepath = ast_str_alloca(PATH_MAX);
+       ast_str_set(&filepath, 0, "%s/", ast_config_AST_CONFIG_DIR);
+       ast_str_append(&filepath, 0, "%s", fn);
+
+       if ((fd = open(filepath->str, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP| S_IROTH)) != -1) {
+               close(fd);
+               astman_send_ack(s, m, "New configuration file created successfully");
+       } else 
+               astman_send_error(s, m, strerror(errno));
+
+       return 0;
+}
+
 /*! \brief Manager WAITEVENT */
 static char mandescr_waitevent[] =
 "Description: A 'WaitEvent' action will ellicit a 'Success' response.  Whenever\n"
@@ -3483,6 +3638,8 @@ static int __init_manager(int reload)
                ast_manager_register2("GetConfig", EVENT_FLAG_SYSTEM | EVENT_FLAG_CONFIG, action_getconfig, "Retrieve configuration", mandescr_getconfig);
                ast_manager_register2("GetConfigJSON", EVENT_FLAG_SYSTEM | EVENT_FLAG_CONFIG, action_getconfigjson, "Retrieve configuration (JSON format)", mandescr_getconfigjson);
                ast_manager_register2("UpdateConfig", EVENT_FLAG_CONFIG, action_updateconfig, "Update basic configuration", mandescr_updateconfig);
+               ast_manager_register2("CreateConfig", EVENT_FLAG_CONFIG, action_createconfig, "Creates an empty file in the configuration directory", mandescr_createconfig);
+               ast_manager_register2("ListCategories", EVENT_FLAG_CONFIG, action_listcategories, "List categories in configuration file", mandescr_listcategories);
                ast_manager_register2("Redirect", EVENT_FLAG_CALL, action_redirect, "Redirect (transfer) a call", mandescr_redirect );
                ast_manager_register2("Originate", EVENT_FLAG_CALL, action_originate, "Originate Call", mandescr_originate);
                ast_manager_register2("Command", EVENT_FLAG_COMMAND, action_command, "Execute Asterisk CLI Command", mandescr_command );