Merge "BuildSystem: Really do not pass unknown-warning options to the compiler."
[asterisk/asterisk.git] / funcs / func_devstate.c
index f1fad0a..e6159e9 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 2007, Digium, Inc.
  *
- * Russell Bryant <russell@digium.com> 
+ * Russell Bryant <russell@digium.com>
  *
  * See http://www.asterisk.org for more information about
  * the Asterisk project. Please do not directly contact
  *
  * \brief Manually controlled blinky lights
  *
- * \author Russell Bryant <russell@digium.com> 
+ * \author Russell Bryant <russell@digium.com>
  *
  * \ingroup functions
  *
+ * \todo Delete the entry from AstDB when set to nothing like Set(DEVICE_STATE(Custom:lamp1)=)
+ *
  * \note Props go out to Ahrimanes in \#asterisk for requesting this at 4:30 AM
  *       when I couldn't sleep.  :)
  */
 
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
 
-#include <stdlib.h>
+#include "asterisk.h"
 
 #include "asterisk/module.h"
 #include "asterisk/channel.h"
@@ -41,14 +43,61 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/linkedlists.h"
 #include "asterisk/devicestate.h"
 #include "asterisk/cli.h"
+#include "asterisk/astdb.h"
+#include "asterisk/app.h"
 
-struct custom_device {
-       int state;
-       AST_RWLIST_ENTRY(custom_device) entry;
-       char name[1];
-};
+/*** DOCUMENTATION
+       <function name="DEVICE_STATE" language="en_US">
+               <synopsis>
+                       Get or Set a device state.
+               </synopsis>
+               <syntax>
+                       <parameter name="device" required="true" />
+               </syntax>
+               <description>
+                       <para>The DEVICE_STATE function can be used to retrieve the device state from any
+                       device state provider. For example:</para>
+                       <para>NoOp(SIP/mypeer has state ${DEVICE_STATE(SIP/mypeer)})</para>
+                       <para>NoOp(Conference number 1234 has state ${DEVICE_STATE(MeetMe:1234)})</para>
+                       <para>The DEVICE_STATE function can also be used to set custom device state from
+                       the dialplan.  The <literal>Custom:</literal> prefix must be used. For example:</para>
+                       <para>Set(DEVICE_STATE(Custom:lamp1)=BUSY)</para>
+                       <para>Set(DEVICE_STATE(Custom:lamp2)=NOT_INUSE)</para>
+                       <para>You can subscribe to the status of a custom device state using a hint in
+                       the dialplan:</para>
+                       <para>exten => 1234,hint,Custom:lamp1</para>
+                       <para>The possible values for both uses of this function are:</para>
+                       <para>UNKNOWN | NOT_INUSE | INUSE | BUSY | INVALID | UNAVAILABLE | RINGING |
+                       RINGINUSE | ONHOLD</para>
+               </description>
+       </function>
+       <function name="HINT" language="en_US">
+               <synopsis>
+                       Get the devices set for a dialplan hint.
+               </synopsis>
+               <syntax>
+                       <parameter name="extension" required="true" argsep="@">
+                               <argument name="extension" required="true" />
+                               <argument name="context" />
+                       </parameter>
+                       <parameter name="options">
+                               <optionlist>
+                                       <option name="n">
+                                               <para>Retrieve name on the hint instead of list of devices.</para>
+                                       </option>
+                               </optionlist>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>The HINT function can be used to retrieve the list of devices that are
+                       mapped to a dialplan hint. For example:</para>
+                       <para>NoOp(Hint for Extension 1234 is ${HINT(1234)})</para>
+               </description>
+       </function>
+ ***/
 
-static AST_RWLIST_HEAD_STATIC(custom_devices, custom_device);
+
+static const char astdb_family[] = "CustomDevstate";
 
 static int devstate_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
 {
@@ -59,67 +108,100 @@ static int devstate_read(struct ast_channel *chan, const char *cmd, char *data,
 
 static int devstate_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
 {
-       struct custom_device *dev;
-       int len = strlen("Custom:");
+       size_t len = strlen("Custom:");
+       enum ast_device_state state_val;
 
        if (strncasecmp(data, "Custom:", len)) {
-               ast_log(LOG_WARNING, "The DEVSTATE function can only be used to set 'Custom:' device state!\n");
+               ast_log(LOG_WARNING, "The DEVICE_STATE function can only be used to set 'Custom:' device state!\n");
                return -1;
        }
        data += len;
        if (ast_strlen_zero(data)) {
-               ast_log(LOG_WARNING, "DEVSTATE function called with no custom device name!\n");
+               ast_log(LOG_WARNING, "DEVICE_STATE function called with no custom device name!\n");
                return -1;
        }
 
-       AST_RWLIST_WRLOCK(&custom_devices);
-       AST_RWLIST_TRAVERSE(&custom_devices, dev, entry) {
-               if (!strcasecmp(dev->name, data))
-                       break;
-       }
-       if (!dev) {
-               if (!(dev = ast_calloc(1, sizeof(*dev) + strlen(data) + 1))) {
-                       AST_RWLIST_UNLOCK(&custom_devices);
-                       return -1;
-               }
-               strcpy(dev->name, data);
-               AST_RWLIST_INSERT_HEAD(&custom_devices, dev, entry);
+       state_val = ast_devstate_val(value);
+
+       if (state_val == AST_DEVICE_UNKNOWN) {
+               ast_log(LOG_ERROR, "DEVICE_STATE function given invalid state value '%s'\n", value);
+               return -1;
        }
-       dev->state = ast_devstate_val(value);
-       ast_devstate_changed(dev->state, "Custom:%s", dev->name);
-       AST_RWLIST_UNLOCK(&custom_devices);
+
+       ast_db_put(astdb_family, data, value);
+
+       ast_devstate_changed(state_val, AST_DEVSTATE_CACHABLE, "Custom:%s", data);
 
        return 0;
 }
 
-static enum ast_device_state custom_devstate_callback(const char *data)
+enum {
+       HINT_OPT_NAME = (1 << 0),
+};
+
+AST_APP_OPTIONS(hint_options, BEGIN_OPTIONS
+       AST_APP_OPTION('n', HINT_OPT_NAME),
+END_OPTIONS );
+
+static int hint_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
 {
-       struct custom_device *dev;
-       enum ast_device_state state = AST_DEVICE_UNKNOWN;
-
-       AST_RWLIST_RDLOCK(&custom_devices);
-       AST_RWLIST_TRAVERSE(&custom_devices, dev, entry) {
-               if (!strcasecmp(dev->name, data)) {
-                       state = dev->state;     
-                       break;
-               }
+       char *exten, *context;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(exten);
+               AST_APP_ARG(options);
+       );
+       struct ast_flags opts = { 0, };
+       int res;
+
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_WARNING, "The HINT function requires an extension\n");
+               return -1;
+       }
+
+       AST_STANDARD_APP_ARGS(args, data);
+
+       if (ast_strlen_zero(args.exten)) {
+               ast_log(LOG_WARNING, "The HINT function requires an extension\n");
+               return -1;
        }
-       AST_RWLIST_UNLOCK(&custom_devices);
 
-       return state;
+       context = exten = args.exten;
+       strsep(&context, "@");
+       if (ast_strlen_zero(context))
+               context = "default";
+
+       if (!ast_strlen_zero(args.options))
+               ast_app_parse_options(hint_options, &opts, NULL, args.options);
+
+       if (ast_test_flag(&opts, HINT_OPT_NAME))
+               res = ast_get_hint(NULL, 0, buf, len, chan, context, exten);
+       else
+               res = ast_get_hint(buf, len, NULL, 0, chan, context, exten);
+
+       return !res; /* ast_get_hint returns non-zero on success */
 }
 
-static char *cli_funcdevstate_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+static enum ast_device_state custom_devstate_callback(const char *data)
 {
-       struct custom_device *dev;
+       char buf[256] = "";
+
+       /* Ignore check_return warning from Coverity fow ast_db_get below */
+       ast_db_get(astdb_family, data, buf, sizeof(buf));
+
+       return ast_devstate_val(buf);
+}
+
+static char *handle_cli_devstate_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct ast_db_entry *db_entry, *db_tree;
 
        switch (cmd) {
        case CLI_INIT:
-               e->command = "funcdevstate list";
+               e->command = "devstate list";
                e->usage =
-                       "Usage: funcdevstate list\n"
+                       "Usage: devstate list\n"
                        "       List all custom device states that have been set by using\n"
-                       "       the DEVSTATE dialplan function.\n";
+                       "       the DEVICE_STATE dialplan function.\n";
                return NULL;
        case CLI_GENERATE:
                return NULL;
@@ -133,12 +215,18 @@ static char *cli_funcdevstate_list(struct ast_cli_entry *e, int cmd, struct ast_
                "--- Custom Device States --------------------------------------------\n"
                "---------------------------------------------------------------------\n"
                "---\n");
-       AST_RWLIST_RDLOCK(&custom_devices);
-       AST_RWLIST_TRAVERSE(&custom_devices, dev, entry) {
+
+       db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
+       for (; db_entry; db_entry = db_entry->next) {
+               const char *dev_name = strrchr(db_entry->key, '/') + 1;
+               if (dev_name <= (const char *) 1)
+                       continue;
                ast_cli(a->fd, "--- Name: 'Custom:%s'  State: '%s'\n"
-                              "---\n", dev->name, ast_devstate_str(dev->state));
+                              "---\n", dev_name, db_entry->data);
        }
-       AST_RWLIST_UNLOCK(&custom_devices);
+       ast_db_freetree(db_tree);
+       db_tree = NULL;
+
        ast_cli(a->fd,
                "---------------------------------------------------------------------\n"
                "---------------------------------------------------------------------\n"
@@ -147,61 +235,126 @@ static char *cli_funcdevstate_list(struct ast_cli_entry *e, int cmd, struct ast_
        return CLI_SUCCESS;
 }
 
+static char *handle_cli_devstate_change(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+    size_t len;
+       const char *dev, *state;
+       enum ast_device_state state_val;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "devstate change";
+               e->usage =
+                       "Usage: devstate change <device> <state>\n"
+                       "       Change a custom device to a new state.\n"
+                       "       The possible values for the state are:\n"
+                       "UNKNOWN | NOT_INUSE | INUSE | BUSY | INVALID | UNAVAILABLE | RINGING\n"
+                       "RINGINUSE | ONHOLD\n"
+                       "\n"
+                       "Examples:\n"
+                       "       devstate change Custom:mystate1 INUSE\n"
+                       "       devstate change Custom:mystate1 NOT_INUSE\n"
+                       "       \n";
+               return NULL;
+       case CLI_GENERATE:
+       {
+               static const char * const cmds[] = { "UNKNOWN", "NOT_INUSE", "INUSE", "BUSY",
+                                                    "UNAVAILABLE", "RINGING", "RINGINUSE", "ONHOLD", NULL };
+
+               if (a->pos == e->args + 1)
+                       return ast_cli_complete(a->word, cmds, a->n);
+
+               return NULL;
+       }
+       }
+
+       if (a->argc != e->args + 2)
+               return CLI_SHOWUSAGE;
+
+       len = strlen("Custom:");
+       dev = a->argv[e->args];
+       state = a->argv[e->args + 1];
+
+       if (strncasecmp(dev, "Custom:", len)) {
+               ast_cli(a->fd, "The devstate command can only be used to set 'Custom:' device state!\n");
+               return CLI_FAILURE;
+       }
+
+       dev += len;
+       if (ast_strlen_zero(dev))
+               return CLI_SHOWUSAGE;
+
+       state_val = ast_devstate_val(state);
+
+       if (state_val == AST_DEVICE_UNKNOWN)
+               return CLI_SHOWUSAGE;
+
+       ast_cli(a->fd, "Changing %s to %s\n", dev, state);
+
+       ast_db_put(astdb_family, dev, state);
+
+       ast_devstate_changed(state_val, AST_DEVSTATE_CACHABLE, "Custom:%s", dev);
+
+       return CLI_SUCCESS;
+}
+
 static struct ast_cli_entry cli_funcdevstate[] = {
-       NEW_CLI(cli_funcdevstate_list, "List currently known custom device states"),
+       AST_CLI_DEFINE(handle_cli_devstate_list, "List currently known custom device states"),
+       AST_CLI_DEFINE(handle_cli_devstate_change, "Change a custom device state"),
 };
 
 static struct ast_custom_function devstate_function = {
-       .name = "DEVSTATE",
-       .synopsis = "Get or Set a device state",
-       .syntax = "DEVSTATE(device)",
-       .desc =
-       "  The DEVSTATE function can be used to retrieve the device state from any\n"
-       "device state provider.  For example:\n"
-       "   NoOp(SIP/mypeer has state ${DEVSTATE(SIP/mypeer)})\n"
-       "   NoOp(Conference number 1234 has state ${DEVSTATE(MeetMe:1234)})\n"
-       "\n"
-       "  The DEVSTATE function can also be used to set custom device state from\n"
-       "the dialplan.  The \"Custom:\" prefix must be used.  For example:\n"
-       "  Set(DEVSTATE(Custom:lamp1)=BUSY)\n"
-       "  Set(DEVSTATE(Custom:lamp2)=NOT_INUSE)\n"
-       "You can subscribe to the status of a custom device state using a hint in\n"
-       "the dialplan:\n"
-       "  exten => 1234,hint,Custom:lamp1\n"
-       "\n"
-       "  The possible values for both uses of this function are:\n"
-       "UNKNOWN | NOT_INUSE | INUSE | BUSY | INVALID | UNAVAILABLE | RINGING\n"
-       "RINGINUSE | ONHOLD\n",
+       .name = "DEVICE_STATE",
        .read = devstate_read,
        .write = devstate_write,
 };
 
+static struct ast_custom_function hint_function = {
+       .name = "HINT",
+       .read = hint_read,
+};
+
 static int unload_module(void)
 {
-       struct custom_device *dev;
        int res = 0;
 
        res |= ast_custom_function_unregister(&devstate_function);
+       res |= ast_custom_function_unregister(&hint_function);
        res |= ast_devstate_prov_del("Custom");
        res |= ast_cli_unregister_multiple(cli_funcdevstate, ARRAY_LEN(cli_funcdevstate));
 
-       AST_RWLIST_WRLOCK(&custom_devices);
-       while ((dev = AST_RWLIST_REMOVE_HEAD(&custom_devices, entry)))
-               ast_free(dev);
-       AST_RWLIST_UNLOCK(&custom_devices);
-
        return res;
 }
 
 static int load_module(void)
 {
        int res = 0;
+       struct ast_db_entry *db_entry, *db_tree;
+
+       /* Populate the device state cache on the system with all of the currently
+        * known custom device states. */
+       db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
+       for (; db_entry; db_entry = db_entry->next) {
+               const char *dev_name = strrchr(db_entry->key, '/') + 1;
+               if (dev_name <= (const char *) 1)
+                       continue;
+               ast_devstate_changed(ast_devstate_val(db_entry->data),
+                       AST_DEVSTATE_CACHABLE, "Custom:%s", dev_name);
+       }
+       ast_db_freetree(db_tree);
+       db_tree = NULL;
 
        res |= ast_custom_function_register(&devstate_function);
+       res |= ast_custom_function_register(&hint_function);
        res |= ast_devstate_prov_add("Custom", custom_devstate_callback);
        res |= ast_cli_register_multiple(cli_funcdevstate, ARRAY_LEN(cli_funcdevstate));
 
        return res;
 }
 
-AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Gets or sets a device state in the dialplan");
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Gets or sets a device state in the dialplan",
+       .support_level = AST_MODULE_SUPPORT_CORE,
+       .load = load_module,
+       .unload = unload_module,
+       .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,
+);