]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
[Core, mod_commands] Add posix_spawn replacement for the system call. Add unit-tests.
authorAndrey Volk <andywolk@gmail.com>
Fri, 5 Feb 2021 21:38:51 +0000 (00:38 +0300)
committerAndrey Volk <andywolk@gmail.com>
Fri, 16 Jul 2021 14:34:42 +0000 (17:34 +0300)
src/include/switch_core.h
src/mod/applications/mod_commands/Makefile.am
src/mod/applications/mod_commands/mod_commands.c
src/mod/applications/mod_commands/test/.gitignore [new file with mode: 0644]
src/mod/applications/mod_commands/test/conf/freeswitch.xml [new file with mode: 0644]
src/mod/applications/mod_commands/test/test_mod_commands.c [new file with mode: 0644]
src/switch_core.c
tests/unit/switch_core.c

index 6299e09c52191889f3ddeda22882a8a49e9cf92e..e2dfa57afc0b849c851fee10139cbcadd6443cdb 100644 (file)
@@ -2842,6 +2842,8 @@ SWITCH_DECLARE(int) switch_system(const char *cmd, switch_bool_t wait);
 SWITCH_DECLARE(int) switch_stream_system_fork(const char *cmd, switch_stream_handle_t *stream);
 SWITCH_DECLARE(int) switch_stream_system(const char *cmd, switch_stream_handle_t *stream);
 
+SWITCH_DECLARE(int) switch_spawn(const char *cmd, switch_bool_t wait);
+SWITCH_DECLARE(int) switch_stream_spawn(const char *cmd, switch_bool_t wait, switch_stream_handle_t *stream);
 
 SWITCH_DECLARE(void) switch_core_session_debug_pool(switch_stream_handle_t *stream);
 
index 82cfe69a4de8cd9ade4f8b89eceb09e6ba2147d6..60d4aa13bdc0cab870f82d15b9f799182afb1a60 100644 (file)
@@ -6,3 +6,10 @@ mod_commands_la_SOURCES  = mod_commands.c
 mod_commands_la_CFLAGS   = $(AM_CFLAGS)
 mod_commands_la_LIBADD   = $(switch_builddir)/libfreeswitch.la
 mod_commands_la_LDFLAGS  = -avoid-version -module -no-undefined -shared
+
+noinst_PROGRAMS = test/test_mod_commands
+test_test_mod_commands_CFLAGS = $(SWITCH_AM_CFLAGS) -I../ -DSWITCH_TEST_BASE_DIR_FOR_CONF=\"${abs_builddir}/test\" -DSWITCH_TEST_BASE_DIR_OVERRIDE=\"${abs_builddir}/test\"
+test_test_mod_commands_LDFLAGS = -avoid-version -no-undefined $(SWITCH_AM_LDFLAGS)
+test_test_mod_commands_LDADD = mod_commands.la $(switch_builddir)/libfreeswitch.la
+
+TESTS = $(noinst_PROGRAMS)
index 37eda7992969f0a1bd67a83581f2e0c30718c57b..6c36d26d2a4def6559dfa43a88c8ae4a9efa61a0 100644 (file)
@@ -6522,6 +6522,53 @@ SWITCH_STANDARD_API(bg_system_function)
        return SWITCH_STATUS_SUCCESS;
 }
 
+#define SPAWN_SYNTAX "<command>"
+SWITCH_STANDARD_API(spawn_stream_function)
+{
+       if (zstr(cmd)) {
+               stream->write_function(stream, "-USAGE: %s\n", SPAWN_SYNTAX);
+               return SWITCH_STATUS_SUCCESS;
+       }
+
+       if (switch_stream_spawn(cmd, SWITCH_TRUE, stream) < 0) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Failed to execute command: %s\n", cmd);
+       }
+
+       return SWITCH_STATUS_SUCCESS;
+}
+
+#define SPAWN_SYNTAX "<command>"
+SWITCH_STANDARD_API(spawn_function)
+{
+       if (zstr(cmd)) {
+               stream->write_function(stream, "-USAGE: %s\n", SPAWN_SYNTAX);
+               return SWITCH_STATUS_SUCCESS;
+       }
+
+       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Executing command: %s\n", cmd);
+       if (switch_spawn(cmd, SWITCH_TRUE) < 0) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Failed to execute command: %s\n", cmd);
+       }
+       stream->write_function(stream, "+OK\n");
+       return SWITCH_STATUS_SUCCESS;
+}
+
+#define SPAWN_SYNTAX "<command>"
+SWITCH_STANDARD_API(bg_spawn_function)
+{
+       if (zstr(cmd)) {
+               stream->write_function(stream, "-USAGE: %s\n", SPAWN_SYNTAX);
+               return SWITCH_STATUS_SUCCESS;
+       }
+
+       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Executing command: %s\n", cmd);
+       if (switch_spawn(cmd, SWITCH_FALSE) < 0) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_NOTICE, "Failed to execute command: %s\n", cmd);
+       }
+       stream->write_function(stream, "+OK\n");
+       return SWITCH_STATUS_SUCCESS;
+}
+
 SWITCH_STANDARD_API(strftime_tz_api_function)
 {
        char *format = NULL;
@@ -7456,6 +7503,9 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load)
        if (use_system_commands) {
                SWITCH_ADD_API(commands_api_interface, "bg_system", "Execute a system command in the background", bg_system_function, SYSTEM_SYNTAX);
                SWITCH_ADD_API(commands_api_interface, "system", "Execute a system command", system_function, SYSTEM_SYNTAX);
+               SWITCH_ADD_API(commands_api_interface, "bg_spawn", "Execute a spawn command in the background", bg_spawn_function, SPAWN_SYNTAX);
+               SWITCH_ADD_API(commands_api_interface, "spawn", "Execute a spawn command without capturing it's output", spawn_function, SPAWN_SYNTAX);
+               SWITCH_ADD_API(commands_api_interface, "spawn_stream", "Execute a spawn command and capture it's output", spawn_stream_function, SPAWN_SYNTAX);
        }
 
        SWITCH_ADD_API(commands_api_interface, "acl", "Compare an ip to an acl list", acl_function, "<ip> <list_name>");
diff --git a/src/mod/applications/mod_commands/test/.gitignore b/src/mod/applications/mod_commands/test/.gitignore
new file mode 100644 (file)
index 0000000..b752c84
--- /dev/null
@@ -0,0 +1,5 @@
+.dirstamp\r
+.libs/\r
+.deps/\r
+test_mod_commands*.o\r
+test_mod_commands\r
diff --git a/src/mod/applications/mod_commands/test/conf/freeswitch.xml b/src/mod/applications/mod_commands/test/conf/freeswitch.xml
new file mode 100644 (file)
index 0000000..9369d4e
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<document type="freeswitch/xml">
+
+  <section name="configuration" description="Various Configuration">
+    <configuration name="modules.conf" description="Modules">
+      <modules>
+        <load module="mod_console"/>
+      </modules>
+    </configuration>
+
+    <configuration name="console.conf" description="Console Logger">
+      <mappings>
+        <map name="all" value="console,debug,info,notice,warning,err,crit,alert"/>
+      </mappings>
+      <settings>
+        <param name="colorize" value="true"/>
+        <param name="loglevel" value="debug"/>
+      </settings>
+    </configuration>
+
+    <configuration name="timezones.conf" description="Timezones">
+      <timezones>
+          <zone name="GMT" value="GMT0" />
+      </timezones>
+    </configuration>
+  </section>
+
+  <section name="dialplan" description="Regex/XML Dialplan">
+    <context name="default">
+      <extension name="sample">
+        <condition>
+          <action application="info"/>
+        </condition>
+      </extension>
+    </context>
+  </section>
+</document>
diff --git a/src/mod/applications/mod_commands/test/test_mod_commands.c b/src/mod/applications/mod_commands/test/test_mod_commands.c
new file mode 100644 (file)
index 0000000..d12535e
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ * Copyright (C) 2005-2018, Anthony Minessale II <anthm@freeswitch.org>
+ *
+ * Version: MPL 1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ *
+ * The Initial Developer of the Original Code is
+ * Andrey Volk <andywolk@gmail.com>
+ * Portions created by the Initial Developer are Copyright (C)
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andrey Volk <andywolk@gmail.com>
+ *
+ * mod_commands_test -- mod_commands tests
+ *
+ */
+
+#include <test/switch_test.h>
+
+FST_CORE_BEGIN("conf")
+{
+       FST_MODULE_BEGIN(mod_commands, mod_commands_test)
+       {
+               FST_SETUP_BEGIN()
+               {
+               }
+               FST_SETUP_END()
+
+               FST_TEST_BEGIN(spawn_test)
+               {
+#ifdef __linux__
+                       switch_stream_handle_t stream = { 0 };
+
+                       SWITCH_STANDARD_STREAM(stream);
+                       switch_api_execute("bg_spawn", "echo TEST_BG_SPAWN", NULL, &stream);
+                       fst_check_string_equals(stream.data, "+OK\n");
+                       switch_safe_free(stream.data);
+
+                       SWITCH_STANDARD_STREAM(stream);
+                       switch_api_execute("spawn_stream", "echo DEADBEEF", NULL, &stream);
+                       fst_check_string_equals(stream.data, "DEADBEEF\n");
+                       switch_safe_free(stream.data);
+
+                       SWITCH_STANDARD_STREAM(stream);
+                       switch_api_execute("spawn", "echo TEST_NO_OUTPUT", NULL, &stream);
+                       fst_check_string_equals(stream.data, "+OK\n");
+                       switch_safe_free(stream.data);
+#endif
+               }
+               FST_TEST_END()
+
+               FST_TEARDOWN_BEGIN()
+               {
+               }
+               FST_TEARDOWN_END()
+       }
+       FST_MODULE_END()
+}
+FST_CORE_END()
index d4507aa49c26717650b31e4a8ba405116969afa4..617b6237535847b84eff210ecc73eb998ac94a51 100644 (file)
 #include <priv.h>
 #endif
 
+#ifdef __linux__
+#include <sys/wait.h>
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* Required for POSIX_SPAWN_USEVFORK */
+#endif
+#include <spawn.h>
+#include <poll.h>
+#endif
+
 #ifdef WIN32
 #define popen _popen
 #define pclose _pclose
@@ -2282,6 +2291,17 @@ static void switch_load_core_config(const char *file)
                                        } else {
                                                switch_clear_flag((&runtime), SCF_THREADED_SYSTEM_EXEC);
                                        }
+#endif
+                               } else if (!strcasecmp(var, "spawn-instead-of-system") && !zstr(val)) {
+#ifdef WIN32
+                                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "spawn-instead-of-system is not implemented on this platform\n");
+#else
+                                       int v = switch_true(val);
+                                       if (v) {
+                                               switch_core_set_variable("spawn_instead_of_system", "true");
+                                       } else {
+                                               switch_core_set_variable("spawn_instead_of_system", "false");
+                                       }
 #endif
                                } else if (!strcasecmp(var, "min-idle-cpu") && !zstr(val)) {
                                        switch_core_min_idle_cpu(atof(val));
@@ -3341,9 +3361,14 @@ static int switch_system_fork(const char *cmd, switch_bool_t wait)
 
 SWITCH_DECLARE(int) switch_system(const char *cmd, switch_bool_t wait)
 {
+#ifdef __linux__
+       switch_bool_t spawn_instead_of_system = switch_true(switch_core_get_variable("spawn_instead_of_system"));
+#else
+       switch_bool_t spawn_instead_of_system = SWITCH_FALSE;
+#endif
        int (*sys_p)(const char *cmd, switch_bool_t wait);
 
-       sys_p = switch_test_flag((&runtime), SCF_THREADED_SYSTEM_EXEC) ? switch_system_thread : switch_system_fork;
+       sys_p = spawn_instead_of_system ? switch_spawn : switch_test_flag((&runtime), SCF_THREADED_SYSTEM_EXEC) ? switch_system_thread : switch_system_fork;
 
        return sys_p(cmd, wait);
 
@@ -3356,6 +3381,141 @@ SWITCH_DECLARE(int) switch_stream_system_fork(const char *cmd, switch_stream_han
        return switch_stream_system(cmd, stream);
 }
 
+#ifdef __linux__
+extern char **environ;
+#endif
+
+SWITCH_DECLARE(int) switch_stream_spawn(const char *cmd, switch_bool_t wait, switch_stream_handle_t *stream)
+{
+#ifndef __linux__
+       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "posix_spawn is unsupported on current platform\n");
+       return 1;
+#else
+       int status = 0, rval;
+       char buffer[1024];
+       pid_t pid;
+       char *pdata = NULL, *argv[64];
+       posix_spawn_file_actions_t action;
+       posix_spawnattr_t *attr;
+       int cout_pipe[2];
+       int cerr_pipe[2];
+       struct pollfd pfds[2] = { {0} };
+
+       if (zstr(cmd)) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Failed to execute switch_spawn_stream because of empty command\n");
+               return 1;
+       }
+
+       if (!(pdata = strdup(cmd))) {
+               return 1;
+       }
+
+       if (!switch_separate_string(pdata, ' ', argv, (sizeof(argv) / sizeof(argv[0])))) {
+               free(pdata);
+               return 1;
+       }
+
+       if (!(attr = malloc(sizeof(posix_spawnattr_t)))) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to execute switch_spawn_stream because of a memory error: %s\n", cmd);
+               free(pdata);
+               return 1;
+       }
+
+       if (stream) {
+               if (pipe(cout_pipe)) {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to execute switch_spawn_stream because of a pipe error: %s\n", cmd);
+                       free(attr);
+                       free(pdata);
+                       return 1;
+               }
+
+               if (pipe(cerr_pipe)) {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to execute switch_spawn_stream because of a pipe error: %s\n", cmd);
+                       close(cout_pipe[0]);
+                       close(cout_pipe[1]);
+                       free(attr);
+                       free(pdata);
+                       return 1;
+               }
+       }
+
+       memset(attr, 0, sizeof(posix_spawnattr_t));
+       posix_spawnattr_init(attr);
+       posix_spawnattr_setflags(attr, POSIX_SPAWN_USEVFORK);
+
+       posix_spawn_file_actions_init(&action);
+
+       if (stream) {
+               posix_spawn_file_actions_addclose(&action, cout_pipe[0]);
+               posix_spawn_file_actions_addclose(&action, cerr_pipe[0]);
+               posix_spawn_file_actions_adddup2(&action, cout_pipe[1], 1);
+               posix_spawn_file_actions_adddup2(&action, cerr_pipe[1], 2);
+
+               posix_spawn_file_actions_addclose(&action, cout_pipe[1]);
+               posix_spawn_file_actions_addclose(&action, cerr_pipe[1]);
+       }
+
+       if (posix_spawnp(&pid, argv[0], &action, attr, argv, environ) != 0) {
+               status = 1;
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Failed to execute posix_spawnp: %s\n", cmd);
+               if (stream) {
+                       close(cout_pipe[0]), close(cerr_pipe[0]);
+                       close(cout_pipe[1]), close(cerr_pipe[1]);
+               }
+       } else {
+               if (stream) {
+                       close(cout_pipe[1]), close(cerr_pipe[1]); /* close child-side of pipes */
+
+                       pfds[0] = (struct pollfd) {
+                               .fd = cout_pipe[0],
+                                       .events = POLLIN,
+                                       .revents = 0
+                       };
+
+                       pfds[1] = (struct pollfd) {
+                               .fd = cerr_pipe[0],
+                                       .events = POLLIN,
+                                       .revents = 0
+                       };
+
+                       while ((rval = poll(pfds, 2, /*timeout*/-1)) > 0) {
+                               if (pfds[0].revents & POLLIN) {
+                                       int bytes_read = read(cout_pipe[0], buffer, sizeof(buffer));
+                                       stream->raw_write_function(stream, (unsigned char *)buffer, bytes_read);
+                               } else if (pfds[1].revents & POLLIN) {
+                                       int bytes_read = read(cerr_pipe[0], buffer, sizeof(buffer));
+                                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "STDERR of cmd (%s): %.*s\n", cmd, bytes_read, buffer);
+                               } else {
+                                       break; /* nothing left to read */
+                               }
+                       }
+
+                       close(cout_pipe[0]), close(cerr_pipe[0]);
+               }
+
+               if (wait) {
+                       if (waitpid(pid, &status, 0) != pid) {
+                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "waitpid failed: %s\n", cmd);
+                       } else if (status != 0) {
+                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Exit status (%d): %s\n", status, cmd);
+                       }
+               }
+       }
+
+       posix_spawnattr_destroy(attr);
+       free(attr);
+       posix_spawn_file_actions_destroy(&action);
+       free(pdata);
+
+       return status;
+#endif
+}
+
+SWITCH_DECLARE(int) switch_spawn(const char *cmd, switch_bool_t wait)
+{
+       return switch_stream_spawn(cmd, wait, NULL);
+}
+
 SWITCH_DECLARE(switch_status_t) switch_core_get_stacksizes(switch_size_t *cur, switch_size_t *max)
 {
 #ifdef HAVE_SETRLIMIT
@@ -3382,26 +3542,36 @@ SWITCH_DECLARE(switch_status_t) switch_core_get_stacksizes(switch_size_t *cur, s
 
 SWITCH_DECLARE(int) switch_stream_system(const char *cmd, switch_stream_handle_t *stream)
 {
-       char buffer[128];
-       size_t bytes;
-       FILE* pipe = popen(cmd, "r");
-       if (!pipe) return 1;
+#ifdef __linux__
+       switch_bool_t spawn_instead_of_system = switch_true(switch_core_get_variable("spawn_instead_of_system"));
+#else
+       switch_bool_t spawn_instead_of_system = SWITCH_FALSE;
+#endif
 
-       while (!feof(pipe)) {
-               while ((bytes = fread(buffer, 1, 128, pipe)) > 0) {
-                       if (stream != NULL) {
-                               stream->raw_write_function(stream, (unsigned char *)buffer, bytes);
+       if (spawn_instead_of_system){
+               return switch_stream_spawn(cmd, SWITCH_TRUE, stream);
+       } else {
+               char buffer[128];
+               size_t bytes;
+               FILE* pipe = popen(cmd, "r");
+               if (!pipe) return 1;
+
+               while (!feof(pipe)) {
+                       while ((bytes = fread(buffer, 1, 128, pipe)) > 0) {
+                               if (stream != NULL) {
+                                       stream->raw_write_function(stream, (unsigned char *)buffer, bytes);
+                               }
                        }
                }
-       }
 
-       if (ferror(pipe)) {
+               if (ferror(pipe)) {
+                       pclose(pipe);
+                       return 1;
+               }
+
                pclose(pipe);
-               return 1;
+               return 0;
        }
-
-       pclose(pipe);
-       return 0;
 }
 
 SWITCH_DECLARE(uint16_t) switch_core_get_rtp_port_range_start_port()
index d0d6bacef2d5488b93a9cf27443c59ae06e33ef1..44cfe148bf04005914e20d2cbd8c4132e138d3fd 100644 (file)
@@ -160,6 +160,40 @@ FST_CORE_BEGIN("./conf")
                        fst_check_int_equals(r, SWITCH_TRUE);
                }
                FST_TEST_END()
+
+               FST_TEST_BEGIN(test_switch_spawn)
+               {
+#ifdef __linux__
+                       int status;
+                       switch_stream_handle_t stream = { 0 };
+
+                       status = switch_spawn("echo CHECKING_BAD_FILE_DESCRIPTOR", SWITCH_TRUE);
+                       fst_check_int_equals(status, 0);
+
+                       SWITCH_STANDARD_STREAM(stream);
+                       status = switch_stream_spawn("echo DEADBEEF", SWITCH_TRUE, &stream);
+                       fst_check_int_equals(status, 0);
+                       fst_check_string_equals(stream.data, "DEADBEEF\n");
+                       switch_safe_free(stream.data);
+
+                       SWITCH_STANDARD_STREAM(stream);
+                       status = switch_stream_spawn("echo DEADBEEF", SWITCH_FALSE, &stream);
+                       fst_check_int_equals(status, 0);
+                       fst_check_string_equals(stream.data, "DEADBEEF\n");
+                       switch_safe_free(stream.data);
+
+                       printf("\nExpected warning check ... ");
+                       status = switch_spawn("false", SWITCH_TRUE);
+                       fct_chk_neq_int(status, 0);
+
+                       status = switch_spawn("false", SWITCH_FALSE);
+                       fct_chk_eq_int(status, 0);
+
+                       status = switch_spawn("true", SWITCH_TRUE);
+                       fct_chk_eq_int(status, 0);
+#endif
+               }
+               FST_TEST_END()
        }
        FST_SUITE_END()
 }