]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
cli.c: Allow 'channel request hangup' to accept patterns.
authorSean Bright <sean@seanbright.com>
Mon, 5 Jan 2026 16:44:47 +0000 (11:44 -0500)
committergithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Tue, 13 Jan 2026 16:03:31 +0000 (16:03 +0000)
This extends 'channel request hangup' to accept multiple channel
names, a POSIX Extended Regular Expression, a glob-like pattern, or a
combination of all of them.

UserNote: The 'channel request hangup' CLI command now accepts
multiple channel names, POSIX Extended Regular Expressions, glob-like
patterns, or a combination of all of them. See the CLI command 'core
show help channel request hangup' for full details.

configure
configure.ac
include/asterisk/autoconfig.h.in
main/cli.c

index 42c6647b9c4b860a70ab6dce73b9f827025ce0e7..8e8dee7046d74ef126f1424d5c5addbc9cbb712f 100755 (executable)
--- a/configure
+++ b/configure
@@ -696,6 +696,7 @@ PBX_DAHDI_HALF_FULL
 PBX_DLADDR
 PBX_IP_MTU_DISCOVER
 PBX_RTLD_NOLOAD
+PBX_FNM_CASEFOLD
 PBX_GLOB_BRACE
 PBX_GLOB_NOMAGIC
 BIND8_CFLAGS
@@ -31524,7 +31525,6 @@ esac
 fi
 rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
 
-
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if we can increase the maximum select-able file descriptor" >&5
 printf %s "checking if we can increase the maximum select-able file descriptor... " >&6; }
 if test "$cross_compiling" = yes
@@ -32918,6 +32918,55 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
 
 
 
+    if test "x${PBX_FNM_CASEFOLD}" != "x1"; then
+       { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for FNM_CASEFOLD in fnmatch.h" >&5
+printf %s "checking for FNM_CASEFOLD in fnmatch.h... " >&6; }
+       saved_cppflags="${CPPFLAGS}"
+       if test "x${FNM_CASEFOLD_DIR}" != "x"; then
+           FNM_CASEFOLD_INCLUDE="-I${FNM_CASEFOLD_DIR}/include"
+       fi
+       CPPFLAGS="${CPPFLAGS} ${FNM_CASEFOLD_INCLUDE}"
+
+       cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+ #include <fnmatch.h>
+int
+main (void)
+{
+#if defined(FNM_CASEFOLD)
+                               int foo = 0;
+                               #else
+                               int foo = bar;
+                               #endif
+                               0
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+     { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+               PBX_FNM_CASEFOLD=1
+
+printf "%s\n" "#define HAVE_FNM_CASEFOLD 1" >>confdefs.h
+
+
+
+else case e in #(
+  e)    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+        ;;
+esac
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+       CPPFLAGS="${saved_cppflags}"
+    fi
+
+
+
+
     if test "x${PBX_RTLD_NOLOAD}" != "x1"; then
        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for RTLD_NOLOAD in dlfcn.h" >&5
 printf %s "checking for RTLD_NOLOAD in dlfcn.h... " >&6; }
index f999d5b6ecc4281f8771f132751a172806233d70..5ea5f052730d95bbcb7c51eb4ccec2020cf977c2 100644 (file)
@@ -1600,6 +1600,8 @@ AST_C_DEFINE_CHECK([GLOB_NOMAGIC], [GLOB_NOMAGIC], [glob.h])
 
 AST_C_DEFINE_CHECK([GLOB_BRACE], [GLOB_BRACE], [glob.h])
 
+AST_C_DEFINE_CHECK([FNM_CASEFOLD], [FNM_CASEFOLD], [fnmatch.h])
+
 AST_C_DEFINE_CHECK([RTLD_NOLOAD], [RTLD_NOLOAD], [dlfcn.h])
 
 AST_C_DEFINE_CHECK([IP_MTU_DISCOVER], [IP_MTU_DISCOVER], [netinet/in.h])
index a3c1fc719fab445495ff2174e52e95ba9bbcadbf..6ba8622757a602c9321fc0eb10a05eb3d7a34c10 100644 (file)
 /* Define to 1 if you have the 'fmodl' function. */
 #undef HAVE_FMODL
 
+/* Define if your system has the FNM_CASEFOLD headers. */
+#undef HAVE_FNM_CASEFOLD
+
 /* Define to 1 if you have the 'fork' function. */
 #undef HAVE_FORK
 
 /* Define to 1 if you have the 'socket' function. */
 #undef HAVE_SOCKET
 
+/* Define to 1 if your socket() implementation has the IPV6_V6ONLY socket
+   option. */
+#undef HAVE_SOCK_IPV6_V6ONLY
+
 /* Define to 1 if your socket() implementation can accept SOCK_NONBLOCK. */
 #undef HAVE_SOCK_NONBLOCK
 
-/* Define to 1 if your socket() implementation has the IPV6_V6ONLY socket option. */
-#undef HAVE_SOCK_IPV6_V6ONLY
-
 /* Define to 1 if your system has soxmix application. */
 #undef HAVE_SOXMIX
 
index 57bafa60de24e60cc57308244e3496ea888c2abd..90a901198e7f899304ccf8a84c6a9c61f5a0c37e 100644 (file)
@@ -45,6 +45,7 @@
 #include <regex.h>
 #include <pwd.h>
 #include <grp.h>
+#include <fnmatch.h>
 
 #include "asterisk/cli.h"
 #include "asterisk/linkedlists.h"
@@ -115,6 +116,8 @@ static AST_VECTOR(, struct ast_cli_entry *) shutdown_commands;
 /*! \brief Initial buffer size for resulting strings in ast_cli() */
 #define AST_CLI_INITLEN   256
 
+#define MAX_REGEX_ERROR_LEN 128
+
 void ast_cli(int fd, const char *fmt, ...)
 {
        int res;
@@ -1227,57 +1230,162 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 #undef VERBOSE_FORMAT_STRING2
 }
 
+static int channel_match_by_regex(const char *channel_name, const void *data)
+{
+       return regexec(data, channel_name, 0, NULL, 0);
+}
+
+static int channel_match_by_glob(const char *channel_name, const void *pattern)
+{
+#if defined(HAVE_FNM_CASEFOLD)
+       return fnmatch(pattern, channel_name, FNM_NOESCAPE | FNM_PATHNAME | FNM_CASEFOLD);
+#else
+       char *lower_channel_name = ast_str_to_lower(ast_strdup(channel_name));
+       if (lower_channel_name) {
+               int res = fnmatch(pattern, lower_channel_name, FNM_NOESCAPE | FNM_PATHNAME);
+               ast_free(lower_channel_name);
+               return res;
+       }
+       return FNM_NOMATCH;
+#endif
+}
+
+static int channel_hangup_matches(
+       struct ast_cli_args *a,
+       int (*matchfn)(const char *, const void *),
+       const void *data)
+{
+       struct ao2_container *cached_channels;
+       struct ao2_iterator iter;
+       struct ast_channel_snapshot *snapshot;
+       struct ast_channel *c;
+       int matched = 0;
+
+       cached_channels = ast_channel_cache_all();
+
+       iter = ao2_iterator_init(cached_channels, 0);
+       for (; (snapshot = ao2_iterator_next(&iter)); ao2_ref(snapshot, -1)) {
+               if ((!matchfn || !matchfn(snapshot->base->name, data))
+                  && (c = ast_channel_get_by_name(snapshot->base->name))) {
+                       ast_cli(a->fd, "Requested Hangup on channel '%s'\n", snapshot->base->name);
+                       ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT);
+                       c = ast_channel_unref(c);
+                       matched++;
+               }
+       }
+       ao2_iterator_destroy(&iter);
+       ao2_ref(cached_channels, -1);
+
+       return matched;
+}
+
+#define arg_looks_like_regex(n) \
+       (*n == '/' && n[strlen(n) - 1] == '/')
+
+#define arg_looks_like_glob(n) \
+       (strcspn(n, "?*[") < strlen(n))
+
 static char *handle_softhangup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        struct ast_channel *c = NULL;
        static const char * const completions[] = { "all", NULL };
-       char *complete;
+       char *complete = NULL;
+       int x;
 
        switch (cmd) {
        case CLI_INIT:
                e->command = "channel request hangup";
                e->usage =
-                       "Usage: channel request hangup <channel>|<all>\n"
-                       "       Request that a channel be hung up. The hangup takes effect\n"
-                       "       the next time the driver reads or writes from the channel.\n"
+                       "Usage: channel request hangup <all>|<channel> [<channel> ...]\n"
+                       "       Request that one or more channels be hung up. The hangup takes\n"
+                       "       effect the next time the driver reads or writes from the channel.\n\n"
                        "       If 'all' is specified instead of a channel name, all channels\n"
-                       "       will see the hangup request.\n";
+                       "       will see the hangup request.\n\n"
+                       "       A POSIX Extended Regular Expression can be provided in the place\n"
+                       "       of a fixed channel name by wrapping it in forward slashes\n"
+                       "       (e.g. /^PJSIP.*/). Note that the expression will match any part\n"
+                       "       of the channel name unless anchored explicitly by ^ and/or $.\n\n"
+                       "       Finally, wildcards (*) and other glob-like characters can also\n"
+                       "       be used to match more than one channel. Similar to how path globs\n"
+                       "       behave, a forward slash cannot be matched with a wildcard or\n"
+                       "       bracket expression. In other words, if you want to hangup all\n"
+                       "       Local channels, you must specify Local/* and not Loc*.\n";
                return NULL;
        case CLI_GENERATE:
-               if (a->pos != e->args) {
+               if (a->pos < e->args) {
                        return NULL;
+               } else if (a->pos == e->args) {
+                       complete = ast_cli_complete(a->word, completions, a->n);
                }
-               complete = ast_cli_complete(a->word, completions, a->n);
                if (!complete) {
-                       complete = ast_complete_channels(a->line, a->word, a->pos, a->n - 1, e->args);
+                       complete = ast_complete_channels(a->line, a->word, a->pos, a->n - 1, a->pos);
                }
                return complete;
        }
 
-       if (a->argc != 4) {
+       if (a->argc < 4) {
                return CLI_SHOWUSAGE;
        }
 
        if (!strcasecmp(a->argv[3], "all")) {
-               struct ast_channel_iterator *iter = NULL;
-               if (!(iter = ast_channel_iterator_all_new())) {
-                       return CLI_FAILURE;
-               }
-               for (; iter && (c = ast_channel_iterator_next(iter)); ast_channel_unref(c)) {
+               channel_hangup_matches(a, NULL, NULL);
+               return CLI_SUCCESS;
+       }
+
+       for (x = e->args; x < a->argc; x++) {
+               /* Try to find a literal channel name first */
+               if ((c = ast_channel_get_by_name(a->argv[x]))) {
                        ast_channel_lock(c);
                        ast_cli(a->fd, "Requested Hangup on channel '%s'\n", ast_channel_name(c));
                        ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT);
                        ast_channel_unlock(c);
+                       c = ast_channel_unref(c);
+                       continue;
+               }
+
+               if (arg_looks_like_regex(a->argv[x])) {
+                       char *pattern = ast_strdup(a->argv[x]);
+                       regex_t re;
+                       int res;
+
+                       if (!pattern) {
+                               return CLI_FAILURE;
+                       }
+
+                       pattern[strlen(pattern) - 1] = 0;
+                       res = regcomp(&re, &pattern[1], REG_EXTENDED | REG_ICASE | REG_NOSUB);
+                       ast_free(pattern);
+                       if (res) {
+                               char errbuf[MAX_REGEX_ERROR_LEN];
+                               regerror(res, &re, errbuf, sizeof(errbuf));
+                               ast_cli(a->fd, "%s is not a valid POSIX Extended Regular Expression: %s\n",
+                                       a->argv[x], errbuf);
+                               continue;
+                       }
+
+                       if (!channel_hangup_matches(a, channel_match_by_regex, &re)) {
+                               ast_cli(a->fd, "%s did not match a known channel\n", a->argv[x]);
+                       }
+                       regfree(&re);
+               } else if (arg_looks_like_glob(a->argv[x])) {
+#if defined(HAVE_FNM_CASEFOLD)
+                       if (!channel_hangup_matches(a, channel_match_by_glob, a->argv[x])) {
+                               ast_cli(a->fd, "%s did not match a known channel\n", a->argv[x]);
+                       }
+#else
+                       char *pattern = ast_str_to_lower(ast_strdup(a->argv[x]));
+                       if (pattern) {
+                               if (!channel_hangup_matches(a, channel_match_by_glob, pattern)) {
+                                       ast_cli(a->fd, "%s did not match a known channel\n", a->argv[x]);
+                               }
+                               ast_free(pattern);
+                       } else {
+                               return CLI_FAILURE;
+                       }
+#endif
+               } else {
+                       ast_cli(a->fd, "%s is not a known channel\n", a->argv[x]);
                }
-               ast_channel_iterator_destroy(iter);
-       } else if ((c = ast_channel_get_by_name(a->argv[3]))) {
-               ast_channel_lock(c);
-               ast_cli(a->fd, "Requested Hangup on channel '%s'\n", ast_channel_name(c));
-               ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT);
-               ast_channel_unlock(c);
-               c = ast_channel_unref(c);
-       } else {
-               ast_cli(a->fd, "%s is not a known channel\n", a->argv[3]);
        }
 
        return CLI_SUCCESS;