diff --git a/src/include/switch_core.h b/src/include/switch_core.h index 6299e09c52..e2dfa57afc 100644 --- a/src/include/switch_core.h +++ b/src/include/switch_core.h @@ -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); diff --git a/src/mod/applications/mod_commands/Makefile.am b/src/mod/applications/mod_commands/Makefile.am index 82cfe69a4d..60d4aa13bd 100644 --- a/src/mod/applications/mod_commands/Makefile.am +++ b/src/mod/applications/mod_commands/Makefile.am @@ -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) diff --git a/src/mod/applications/mod_commands/mod_commands.c b/src/mod/applications/mod_commands/mod_commands.c index 37eda79929..6c36d26d2a 100644 --- a/src/mod/applications/mod_commands/mod_commands.c +++ b/src/mod/applications/mod_commands/mod_commands.c @@ -6522,6 +6522,53 @@ SWITCH_STANDARD_API(bg_system_function) return SWITCH_STATUS_SUCCESS; } +#define SPAWN_SYNTAX "" +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 "" +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 "" +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, " "); diff --git a/src/mod/applications/mod_commands/test/.gitignore b/src/mod/applications/mod_commands/test/.gitignore new file mode 100644 index 0000000000..b752c84499 --- /dev/null +++ b/src/mod/applications/mod_commands/test/.gitignore @@ -0,0 +1,5 @@ +.dirstamp +.libs/ +.deps/ +test_mod_commands*.o +test_mod_commands 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 index 0000000000..9369d4e918 --- /dev/null +++ b/src/mod/applications/mod_commands/test/conf/freeswitch.xml @@ -0,0 +1,37 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + +
+
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 index 0000000000..d12535e430 --- /dev/null +++ b/src/mod/applications/mod_commands/test/test_mod_commands.c @@ -0,0 +1,72 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2018, Anthony Minessale II + * + * 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 + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Andrey Volk + * + * mod_commands_test -- mod_commands tests + * + */ + +#include + +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() diff --git a/src/switch_core.c b/src/switch_core.c index d4507aa49c..617b623753 100644 --- a/src/switch_core.c +++ b/src/switch_core.c @@ -59,6 +59,15 @@ #include #endif +#ifdef __linux__ +#include +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* Required for POSIX_SPAWN_USEVFORK */ +#endif +#include +#include +#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() diff --git a/tests/unit/switch_core.c b/tests/unit/switch_core.c index d0d6bacef2..44cfe148bf 100644 --- a/tests/unit/switch_core.c +++ b/tests/unit/switch_core.c @@ -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() }