This is an automated email from the ASF dual-hosted git repository. pnoltes pushed a commit to branch feature/deadlock_on_stop_cmd in repository https://gitbox.apache.org/repos/asf/celix.git
commit f76b082e6bf98b6b1d8c286aa3e4e460d1165852 Author: Pepijn Noltes <[email protected]> AuthorDate: Sun Oct 3 13:49:18 2021 +0200 Adds async install,start,stop and uninstall bundle command for use in the shell commands Bundle lifecycle shell commands need to be async to prevent deadlock issue. For example when a `stop` is used to stop the shell bundle. Also refactor shell_tui and introduces unit test for the shell_tui. --- bundles/shell/shell/CMakeLists.txt | 2 +- bundles/shell/shell/{test => gtest}/CMakeLists.txt | 4 +- .../src/ShellTestSuite.cc} | 32 ++- bundles/shell/shell/src/install_command.c | 18 +- bundles/shell/shell/src/lb_command.c | 89 +----- bundles/shell/shell/src/start_command.c | 4 +- bundles/shell/shell/src/std_commands.c | 10 +- bundles/shell/shell/src/stop_command.c | 4 +- bundles/shell/shell/src/uninstall_command.c | 11 +- bundles/shell/shell_tui/CMakeLists.txt | 17 +- bundles/shell/shell_tui/README.md | 2 +- bundles/shell/shell_tui/{ => gtest}/CMakeLists.txt | 36 +-- .../shell/shell_tui/gtest/src/ShellTuiTestSuite.cc | 238 ++++++++++++++++ bundles/shell/shell_tui/private/src/activator.c | 126 --------- bundles/shell/shell_tui/src/activator.c | 100 +++++++ .../shell/shell_tui/{private => }/src/history.c | 0 .../shell_tui/{private/include => src}/history.h | 0 .../shell/shell_tui/{private => }/src/shell_tui.c | 305 ++++++++++++--------- .../shell_tui/{private/include => src}/shell_tui.h | 32 ++- libs/framework/gtest/src/single_framework_test.cpp | 21 ++ libs/framework/include/celix_framework.h | 84 ++++-- libs/framework/src/framework.c | 51 +++- .../src/framework_bundle_lifecycle_handler.c | 29 +- libs/framework/src/framework_private.h | 30 +- 24 files changed, 788 insertions(+), 457 deletions(-) diff --git a/bundles/shell/shell/CMakeLists.txt b/bundles/shell/shell/CMakeLists.txt index 698eba4..432a52c 100644 --- a/bundles/shell/shell/CMakeLists.txt +++ b/bundles/shell/shell/CMakeLists.txt @@ -87,6 +87,6 @@ if (SHELL) endif () if (ENABLE_TESTING) - add_subdirectory(test) + add_subdirectory(gtest) endif() endif (SHELL) diff --git a/bundles/shell/shell/test/CMakeLists.txt b/bundles/shell/shell/gtest/CMakeLists.txt similarity index 93% rename from bundles/shell/shell/test/CMakeLists.txt rename to bundles/shell/shell/gtest/CMakeLists.txt index fdca2de..120c738 100644 --- a/bundles/shell/shell/test/CMakeLists.txt +++ b/bundles/shell/shell/gtest/CMakeLists.txt @@ -16,7 +16,7 @@ # under the License. add_executable(test_shell - src/ShellTestSuite.cpp + src/ShellTestSuite.cc ) target_link_libraries(test_shell PRIVATE Celix::framework Celix::shell_api GTest::gtest GTest::gtest_main) @@ -32,7 +32,7 @@ setup_target_for_coverage(test_shell SCAN_DIR ..) add_executable(test_cxx_shell - src/ShellTestSuite.cpp + src/ShellTestSuite.cc ) target_link_libraries(test_cxx_shell PRIVATE Celix::framework Celix::shell_api GTest::gtest GTest::gtest_main) diff --git a/bundles/shell/shell/test/src/ShellTestSuite.cpp b/bundles/shell/shell/gtest/src/ShellTestSuite.cc similarity index 90% rename from bundles/shell/shell/test/src/ShellTestSuite.cpp rename to bundles/shell/shell/gtest/src/ShellTestSuite.cc index 6666e11..93d7acc 100644 --- a/bundles/shell/shell/test/src/ShellTestSuite.cpp +++ b/bundles/shell/shell/gtest/src/ShellTestSuite.cc @@ -28,7 +28,10 @@ class ShellTestSuite : public ::testing::Test { public: static constexpr const char * const SHELL_BUNDLE_LOC = "" SHELL_BUNDLE_LOCATION ""; - ShellTestSuite() : ctx{createFrameworkContext()} {} + ShellTestSuite() : ctx{createFrameworkContext()} { + shellBundleId = celix_bundleContext_installBundle(ctx.get(), SHELL_BUNDLE_LOCATION, true); + EXPECT_GE(shellBundleId, 0); + } static std::shared_ptr<celix_bundle_context_t> createFrameworkContext() { auto properties = properties_create(); @@ -39,15 +42,13 @@ public: auto* cFw = celix_frameworkFactory_createFramework(properties); auto cCtx = framework_getContext(cFw); - long shellBundleId = celix_bundleContext_installBundle(cCtx, SHELL_BUNDLE_LOCATION, true); - EXPECT_GE(shellBundleId, 0); - return std::shared_ptr<celix_bundle_context_t>{cCtx, [](celix_bundle_context_t* context) { auto *fw = celix_bundleContext_getFramework(context); celix_frameworkFactory_destroyFramework(fw); }}; } - + + long shellBundleId = -1; std::shared_ptr<celix_bundle_context_t> ctx; }; @@ -88,13 +89,13 @@ static void callCommand(std::shared_ptr<celix_bundle_context_t>& ctx, const char TEST_F(ShellTestSuite, testAllCommandsAreCallable) { callCommand(ctx, "non-existing", false); - callCommand(ctx, "install a-bundle-loc.zip", false); + callCommand(ctx, "install a-bundle-loc.zip", true); callCommand(ctx, "help", true); callCommand(ctx, "help lb", false); //note need namespace callCommand(ctx, "help celix::lb", true); callCommand(ctx, "help non-existing-command", false); - callCommand(ctx, "lb -a", true); callCommand(ctx, "lb", true); + callCommand(ctx, "lb -l", true); callCommand(ctx, "query", true); callCommand(ctx, "q -v", true); callCommand(ctx, "stop 15", false); @@ -117,6 +118,23 @@ TEST_F(ShellTestSuite, stopFrameworkTest) { std::this_thread::sleep_for(std::chrono::milliseconds{100}); } +TEST_F(ShellTestSuite, stopSelfTest) { + auto* list = celix_bundleContext_listBundles(ctx.get()); + EXPECT_EQ(1, celix_arrayList_size(list)); + celix_arrayList_destroy(list); + + //rule it should be possible to stop the Shell bundle from the stop command (which is part of the Shell bundle) + std::string cmd = std::string{"stop "} + std::to_string(shellBundleId); + callCommand(ctx, cmd.c_str(), true); + + //ensure that the command can be executed + std::this_thread::sleep_for(std::chrono::milliseconds{100}); + + list = celix_bundleContext_listBundles(ctx.get()); + EXPECT_EQ(0, celix_arrayList_size(list)); + celix_arrayList_destroy(list); +} + TEST_F(ShellTestSuite, queryTest) { celix_service_use_options_t opts{}; opts.filter.serviceName = CELIX_SHELL_COMMAND_SERVICE_NAME; diff --git a/bundles/shell/shell/src/install_command.c b/bundles/shell/shell/src/install_command.c index 322afc0..0dd5fca 100644 --- a/bundles/shell/shell/src/install_command.c +++ b/bundles/shell/shell/src/install_command.c @@ -34,24 +34,22 @@ bool installCommand_execute(void *handle, const char *const_line, FILE *outStrea sub = strtok_r(line, delims, &save_ptr); sub = strtok_r(NULL, delims, &save_ptr); - bool installSucceeded = false; - if (sub == NULL) { fprintf(errStream, "Incorrect number of arguments.\n"); } else { while (sub != NULL) { - long bndId = celix_bundleContext_installBundle(ctx, sub, true); - if (bndId >= 0) { - char *name = celix_bundleContext_getBundleSymbolicName(ctx, bndId); - fprintf(outStream, "Bundle '%s' installed with bundle id %li\n", name, bndId); - free(name); - installSucceeded = true; - } + celix_framework_t* fw = celix_bundleContext_getFramework(ctx); + long bndId = celix_framework_installBundleAsync(fw, sub, true); + if (bndId >= 0) { + fprintf(outStream, "Bundle installed with bundle id %li\n", bndId); + } else { + fprintf(errStream, "Error installed bundle\n"); + } sub = strtok_r(NULL, delims, &save_ptr); } } free(line); - return installSucceeded; + return true; } \ No newline at end of file diff --git a/bundles/shell/shell/src/lb_command.c b/bundles/shell/shell/src/lb_command.c index e12cb30..902b67e 100644 --- a/bundles/shell/shell/src/lb_command.c +++ b/bundles/shell/shell/src/lb_command.c @@ -48,83 +48,10 @@ typedef struct lb_options { //group char *listGroup; - bool listAllGroups; } lb_options_t; static char * psCommand_stateString(bundle_state_e state); -static void addToGroup(hash_map_t *map, const char *group, long bndId) { - celix_array_list_t *ids = hashMap_get(map, group); - if (ids == NULL) { - ids = celix_arrayList_create(); - char *key = strndup(group, 1024 * 1024); - hashMap_put(map, key, ids); - } - celix_arrayList_addLong(ids, bndId); -} - -static void collectGroups(void *handle, const celix_bundle_t *bnd) { - hash_map_t *map = handle; - const char *group = celix_bundle_getGroup(bnd); - if (group == NULL) { - group = "-"; - addToGroup(map, group, celix_bundle_getId(bnd)); - } else { - char *at = strstr(group, "/"); - if (at != NULL) { - unsigned long size = at-group; - char buf[size+1]; - strncpy(buf, group, size); - buf[size] = '\0'; - addToGroup(map, buf, celix_bundle_getId(bnd)); - } else { - addToGroup(map, group, celix_bundle_getId(bnd)); - } - } -} - -static void lbCommand_showGroups(celix_bundle_context_t *ctx, const lb_options_t *opts, FILE *out) { - const char* startColor = ""; - const char* endColor = ""; - if (opts->useColors) { - startColor = HEAD_COLOR; - endColor = END_COLOR; - } - - fprintf(out, "%s Groups:%s\n", startColor, endColor); - fprintf(out, "%s %-20s %-20s %s\n", startColor, "Group", "Bundle Ids", endColor); - - hash_map_t *map = hashMap_create(utils_stringHash, NULL, utils_stringEquals, NULL); - celix_bundleContext_useBundle(ctx, 0, map, collectGroups); - celix_bundleContext_useBundles(ctx, map, collectGroups); - - hash_map_iterator_t iter = hashMapIterator_construct(map); - int count = 0; - while (hashMapIterator_hasNext(&iter)) { - startColor = ""; - endColor = ""; - if (opts->useColors) { - startColor = count++ % 2 == 0 ? EVEN_COLOR : ODD_COLOR; - endColor = END_COLOR; - } - - hash_map_entry_t *entry = hashMapIterator_nextEntry(&iter); - char *key = hashMapEntry_getKey(entry); - fprintf(out, "%s %-20s ", startColor, key); - celix_array_list_t *ids = hashMapEntry_getValue(entry); - int s = celix_arrayList_size(ids); - for (int i = s-1; i >= 0; --i) { //note reverse to start with lower bundle id first - long id = celix_arrayList_getLong(ids, (int)i); - fprintf(out, "%li ", id); - } - fprintf(out, "%s\n", endColor); - free(key); - celix_arrayList_destroy(ids); - } - fprintf(out, "\n\n"); - hashMap_destroy(map, false, false); -} - static void lbCommand_listBundles(celix_bundle_context_t *ctx, const lb_options_t *opts, FILE *out) { const char *message_str = "Name"; if (opts->show_location) { @@ -248,14 +175,9 @@ static void lbCommand_listBundles(celix_bundle_context_t *ctx, const lb_options_ startColor = i % 2 == 0 ? EVEN_COLOR : ODD_COLOR; endColor = END_COLOR; } - bool print = false; - if (opts->listAllGroups) { - print = true; - } else if (opts->listGroup != NULL && group_str != NULL) { - print = strncmp(opts->listGroup, group_str, strlen(opts->listGroup)) == 0; - } else if (opts->listGroup == NULL){ - //listGroup == NULL -> only print not grouped bundles - print = group_str == NULL; + bool print = true; + if (opts->listGroup != NULL) { + print = group_str != NULL && strstr(group_str, opts->listGroup) != NULL; } if (print) { @@ -302,15 +224,14 @@ bool lbCommand_execute(void *handle, const char *const_command_line_str, FILE *o opts.show_symbolic_name = true; } else if (strcmp(sub_str, "-u") == 0) { opts.show_update_location = true; - } else if (strcmp(sub_str, "-a") == 0) { - opts.listAllGroups = true; + } else if (strncmp(sub_str, "-", 1) == 0) { + fprintf(out_ptr, "Ignoring unknown lb option '%s'\n", sub_str); } else { opts.listGroup = strdup(sub_str); } sub_str = strtok_r(NULL, OSGI_SHELL_COMMAND_SEPARATOR, &save_ptr); } - lbCommand_showGroups(ctx, &opts, out_ptr); lbCommand_listBundles(ctx, &opts, out_ptr); free(opts.listGroup); diff --git a/bundles/shell/shell/src/start_command.c b/bundles/shell/shell/src/start_command.c index d1fe45b..c97be68 100644 --- a/bundles/shell/shell/src/start_command.c +++ b/bundles/shell/shell/src/start_command.c @@ -46,7 +46,9 @@ bool startCommand_execute(void *handle, const char *const_command, FILE *outStre } else { bool exists = celix_bundleContext_isBundleInstalled(ctx, bndId); if (exists) { - startSucceeded = celix_bundleContext_startBundle(ctx, bndId); + celix_framework_t* fw = celix_bundleContext_getFramework(ctx); + celix_framework_startBundleAsync(fw, bndId); + startSucceeded = true; } else { fprintf(outStream, "No bundle with id %li.\n", bndId); } diff --git a/bundles/shell/shell/src/std_commands.c b/bundles/shell/shell/src/std_commands.c index 8ad0e6c..e41dd07 100644 --- a/bundles/shell/shell/src/std_commands.c +++ b/bundles/shell/shell/src/std_commands.c @@ -51,10 +51,10 @@ celix_std_commands_t* celix_stdCommands_create(celix_bundle_context_t* ctx) { (struct celix_shell_command_register_entry) { .exec = lbCommand_execute, .name = "celix::lb", - .description = "list bundles. Default only the groupless bundles are listed. Use -a to list all bundles." \ - "\nIf a group string is provided only bundles matching the group string will be listed." \ + .description = "list bundles. Default all bundles are listed." \ + "\nIf a group string is provided only bundles where the bundle group matching group string will be listed." \ "\nUse -l to print the bundle locations.\nUse -s to print the bundle symbolic names\nUse -u to print the bundle update location.", - .usage = "lb [-l | -s | -u | -a] [group]" + .usage = "lb [-l | -s | -u] [group]" }; commands->std_commands[1] = (struct celix_shell_command_register_entry) { @@ -95,8 +95,8 @@ celix_std_commands_t* celix_stdCommands_create(celix_bundle_context_t* ctx) { (struct celix_shell_command_register_entry) { .exec = helpCommand_execute, .name = "celix::help", - .description = "display available commands and description.", - .usage = "help <command>]" + .description = "display available commands or detail info if a command argument is provided", + .usage = "help [<command>]" }; commands->std_commands[7] = (struct celix_shell_command_register_entry) { diff --git a/bundles/shell/shell/src/stop_command.c b/bundles/shell/shell/src/stop_command.c index 340add7..71c3b58 100644 --- a/bundles/shell/shell/src/stop_command.c +++ b/bundles/shell/shell/src/stop_command.c @@ -46,7 +46,9 @@ bool stopCommand_execute(void *handle, const char *const_command, FILE *outStrea } else { bool exists = celix_bundleContext_isBundleInstalled(ctx, bndId); if (exists) { - stoppedCalledAndSucceeded = celix_bundleContext_stopBundle(ctx, bndId); + celix_framework_t* fw = celix_bundleContext_getFramework(ctx); + celix_framework_stopBundleAsync(fw, bndId); + stoppedCalledAndSucceeded = true; } else { fprintf(outStream, "No bundle with id %li.\n", bndId); } diff --git a/bundles/shell/shell/src/uninstall_command.c b/bundles/shell/shell/src/uninstall_command.c index c2e398a..8c92e5f 100644 --- a/bundles/shell/shell/src/uninstall_command.c +++ b/bundles/shell/shell/src/uninstall_command.c @@ -16,13 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -/** - * uninstall_command.c - * - * \date Aug 20, 2010 - * \author <a href="mailto:[email protected]">Apache Celix Project Team</a> - * \copyright Apache License, Version 2.0 - */ #include <stdlib.h> #include <string.h> @@ -49,7 +42,9 @@ bool uninstallCommand_execute(void *handle, const char* const_command, FILE *out long bndId = atol(sub); bool exists = celix_bundleContext_isBundleInstalled(ctx, bndId); if (exists) { - uninstallSucceeded = celix_bundleContext_uninstallBundle(ctx, bndId); + celix_framework_t* fw = celix_bundleContext_getFramework(ctx); + celix_framework_uninstallBundleAsync(fw, bndId); + uninstallSucceeded = true; } else { fprintf(outStream, "No bundle with id %li.\n", bndId); } diff --git a/bundles/shell/shell_tui/CMakeLists.txt b/bundles/shell/shell_tui/CMakeLists.txt index 31bb87a..1002a77 100644 --- a/bundles/shell/shell_tui/CMakeLists.txt +++ b/bundles/shell/shell_tui/CMakeLists.txt @@ -24,19 +24,20 @@ if (SHELL_TUI) FILENAME celix_shell_tui GROUP "Celix/Shell" SOURCES - private/src/activator.c - private/src/shell_tui.c - private/src/history.c + src/activator.c + src/shell_tui.c + src/history.c ) - target_include_directories(shell_tui PRIVATE - "${PROJECT_SOURCE_DIR}/utils/public/include" - private/include - ) - target_link_libraries(shell_tui PRIVATE Celix::shell_api) + target_include_directories(shell_tui PRIVATE src) + target_link_libraries(shell_tui PRIVATE Celix::shell_api Celix::utils) install_celix_bundle(shell_tui EXPORT celix) #Alias setup to match external usage add_library(Celix::shell_tui ALIAS shell_tui) + + if (ENABLE_TESTING AND TARGET Celix::shell) + add_subdirectory(gtest) + endif() endif (SHELL_TUI) diff --git a/bundles/shell/shell_tui/README.md b/bundles/shell/shell_tui/README.md index 3027c90..60f10a7 100644 --- a/bundles/shell/shell_tui/README.md +++ b/bundles/shell/shell_tui/README.md @@ -28,7 +28,7 @@ The Celix Shell TUI implements a textual user interface for the Celix Shell. ## Config options -- SHELL_USE_ANSI_CONTROL_SEQUENCES - Whether to use ANSI control +- SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES - Whether to use ANSI control sequences to support backspace, left, up, etc key commands in the shell tui. Default is true if a TERM environment is set else false. diff --git a/bundles/shell/shell_tui/CMakeLists.txt b/bundles/shell/shell_tui/gtest/CMakeLists.txt similarity index 50% copy from bundles/shell/shell_tui/CMakeLists.txt copy to bundles/shell/shell_tui/gtest/CMakeLists.txt index 31bb87a..76f201c 100644 --- a/bundles/shell/shell_tui/CMakeLists.txt +++ b/bundles/shell/shell_tui/gtest/CMakeLists.txt @@ -5,38 +5,24 @@ # to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -celix_subproject(SHELL_TUI "Option to enable building the Shell Textual User Interface bundles" ON DEPS LAUNCHER SHELL) -if (SHELL_TUI) - add_celix_bundle(shell_tui - SYMBOLIC_NAME "apache_celix_shell_tui" - VERSION "1.1.0" - NAME "Apache Celix Shell TUI" - FILENAME celix_shell_tui - GROUP "Celix/Shell" - SOURCES - private/src/activator.c - private/src/shell_tui.c - private/src/history.c - ) - - target_include_directories(shell_tui PRIVATE - "${PROJECT_SOURCE_DIR}/utils/public/include" - private/include - ) - target_link_libraries(shell_tui PRIVATE Celix::shell_api) +add_executable(test_shell_tui + src/ShellTuiTestSuite.cc +) - install_celix_bundle(shell_tui EXPORT celix) +target_link_libraries(test_shell_tui PRIVATE Celix::framework GTest::gtest GTest::gtest_main) +add_celix_bundle_dependencies(test_shell_tui Celix::shell Celix::shell_tui) +target_compile_definitions(test_shell_tui PRIVATE -DSHELL_BUNDLE_LOCATION=\"$<TARGET_PROPERTY:shell,BUNDLE_FILE>\") +target_compile_definitions(test_shell_tui PRIVATE -DSHELL_TUI_BUNDLE_LOCATION=\"$<TARGET_PROPERTY:shell_tui,BUNDLE_FILE>\") - #Alias setup to match external usage - add_library(Celix::shell_tui ALIAS shell_tui) -endif (SHELL_TUI) +add_test(NAME test_shell_tui COMMAND test_shell_tui) +setup_target_for_coverage(test_shell_tui SCAN_DIR ..) \ No newline at end of file diff --git a/bundles/shell/shell_tui/gtest/src/ShellTuiTestSuite.cc b/bundles/shell/shell_tui/gtest/src/ShellTuiTestSuite.cc new file mode 100644 index 0000000..ce129ed --- /dev/null +++ b/bundles/shell/shell_tui/gtest/src/ShellTuiTestSuite.cc @@ -0,0 +1,238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <gtest/gtest.h> +#include <fcntl.h> + +#include "celix/FrameworkFactory.h" +#include "celix/BundleContext.h" + +class ShellTuiTestSuite : public ::testing::Test { +public: + ShellTuiTestSuite() { + //open pipe to stimulate shell tui input + int fds[2]; + int rc = pipe(fds); + EXPECT_EQ(rc, 0) << strerror(errno); + if (rc == 0) { + inputReadFd = fds[0]; + inputWriteFd = fds[1]; + } + + //open pipe to stimulate shell tui output + rc = pipe(fds); + EXPECT_EQ(rc, 0) << strerror(errno); + if (rc == 0) { + outputReadFd = fds[0]; + outputWriteFd = fds[1]; + + //set readFd non blocking + int flags = fcntl(outputReadFd, F_GETFL, 0); + fcntl(outputReadFd, F_SETFL, flags | O_NONBLOCK); + } + } + + ~ShellTuiTestSuite() override { + close(inputReadFd); + close(inputWriteFd); + close(outputReadFd); + close(outputWriteFd); + } + + void createFrameworkWithShellBundles(celix::Properties config = {}, bool configurePipes = false, bool installShell = true, bool installShellTui = true) { + config.set("CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "trace"); + + if (configurePipes) { + config.set("SHELL_TUI_INPUT_FILE_DESCRIPTOR", std::to_string(inputReadFd).c_str()); + config.set("SHELL_TUI_OUTPUT_FILE_DESCRIPTOR", std::to_string(outputWriteFd).c_str()); + config.set("SHELL_TUI_ERROR_FILE_DESCRIPTOR", std::to_string(outputWriteFd).c_str()); + } + + fw = celix::createFramework(config); + ctx = fw->getFrameworkBundleContext(); + + if (installShell) { + shellBundleId = ctx->installBundle(SHELL_BUNDLE_LOCATION); + EXPECT_GT(shellBundleId, 0); + } + if (installShellTui) { + shellTuiBundleId = ctx->installBundle(SHELL_TUI_BUNDLE_LOCATION); + EXPECT_GT(shellTuiBundleId, 0); + } + } + + [[nodiscard]] std::string readPipeOutput() const { + constexpr int BUFSIZE = 1024 * 10; + char buf[BUFSIZE]; + buf[BUFSIZE-1] = '\0'; //ensure 0 terminated + auto bytesRead = read(outputReadFd, buf, BUFSIZE-1); + EXPECT_GT(bytesRead, 0); + return std::string{buf}; + } + + void writeCmd(const char* cmd) const { + write(inputWriteFd, cmd, strlen(cmd)+1); + std::this_thread::sleep_for(std::chrono::milliseconds{100}); //sleep to let command be handled. + } + + void testExecuteLb(bool enableAnsiControlSequence) { + celix::Properties config{ + {"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", enableAnsiControlSequence ? "true" : "false"}, + + }; + createFrameworkWithShellBundles(std::move(config), true); + + const char* cmd = "lb\n"; + writeCmd(cmd); + + auto lbResult = readPipeOutput(); //lb output + std::cout << lbResult << std::endl; + EXPECT_TRUE(strstr(lbResult.c_str(), "Apache Celix Shell TUI")); //lb should print the shell tui name. + } + + void testExecuteLbWithoutShell(bool enableAnsiControlSequence) { + celix::Properties config{ + {"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", enableAnsiControlSequence ? "true" : "false"} + }; + createFrameworkWithShellBundles(std::move(config), true, false); + + const char* cmd = "lb\n"; + writeCmd(cmd); + + auto lbResult = readPipeOutput(); //lb output + std::cout << lbResult << std::endl; + EXPECT_TRUE(strstr(lbResult.c_str(), "[Shell TUI] Shell service not available") != nullptr); + } + + void testAutoCompleteForCommand(const char* cmd, const char* expectedOutputPart) { + //Test the triggering of auto complete and if the output contains the expected part + celix::Properties config{ + {"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", "true"}, + }; + createFrameworkWithShellBundles(std::move(config), true); + + writeCmd(cmd); + + auto result = readPipeOutput(); + std::cout << result << std::endl; + EXPECT_TRUE(strstr(result.c_str(), expectedOutputPart) != nullptr); + } + + std::shared_ptr<celix::Framework> fw{}; + std::shared_ptr<celix::BundleContext> ctx{}; + long shellBundleId = -1; + long shellTuiBundleId = -1; + int inputReadFd = -1; + int inputWriteFd = -1; + int outputReadFd = -1; + int outputWriteFd = -1; +}; + +TEST_F(ShellTuiTestSuite, testStartStop) { + //not empty, but should not leak + createFrameworkWithShellBundles(); +} + +TEST_F(ShellTuiTestSuite, testExecuteLb) { + testExecuteLb(false); +} + +TEST_F(ShellTuiTestSuite, testExecuteLbWithAnsiControlEnabled) { + testExecuteLb(true); +} + +TEST_F(ShellTuiTestSuite, testAutoCompleteHelpCommand) { + //note incomplete command with a tab -> should complete command to `help` + testAutoCompleteForCommand("hel\t", "help"); +} + +TEST_F(ShellTuiTestSuite, testAutoCompleteCelixLbCommand) { + //note incomplete command with a tab -> should complete command to `celix::help` + testAutoCompleteForCommand("celix::hel\t", "celix::help"); +} + +TEST_F(ShellTuiTestSuite, testAutoCompleteLbUsageCommand) { + //note complete help command with a tab -> should print usage + testAutoCompleteForCommand("help \t", "Usage:"); +} + +TEST_F(ShellTuiTestSuite, testAutoCompleteCelixLbUsageCommand) { + //note complete celix::help command with a tab -> should print usage + testAutoCompleteForCommand("celix::help \t", "Usage:"); +} + + +TEST_F(ShellTuiTestSuite, testShellTuiWithInvalidFD) { + celix::Properties config{ + {"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", "true"}, + {"SHELL_TUI_INPUT_FILE_DESCRIPTOR", "555"}, //note invalid fd + {"SHELL_TUI_OUTPUT_FILE_DESCRIPTOR", "556"}, //note invalid fd + {"SHELL_TUI_ERROR_FILE_DESCRIPTOR", "557"} //note invalid fd + }; + createFrameworkWithShellBundles(std::move(config)); + + writeCmd("lb\n"); +} + +TEST_F(ShellTuiTestSuite, testShellTuiWithoutShell) { + testExecuteLbWithoutShell(false); +} + +TEST_F(ShellTuiTestSuite, testShellTuiWithAnsiControlWithoutShell) { + testExecuteLbWithoutShell(true); +} + +TEST_F(ShellTuiTestSuite, testAnsiControl) { + celix::Properties config{ + {"SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES", "true"} + }; + createFrameworkWithShellBundles(std::move(config), true); + + //this test triggers the handling of ansi control sequences, but currently does not test the output + + //build history + const char* cmd = "lb\n"; + writeCmd(cmd); + + cmd = "\033[A"; //up + writeCmd(cmd); + + cmd = "\033[C"; //right + writeCmd(cmd); + + cmd = "\033[D"; //left + writeCmd(cmd); + + cmd = "\033[3"; //del1 + writeCmd(cmd); + + cmd = "\033[~"; //del2 + writeCmd(cmd); + + cmd = "\033[9"; //tab + writeCmd(cmd); + + cmd = "\033[127"; //backspace + writeCmd(cmd); + + cmd = "\033[B"; //down + writeCmd(cmd); + + std::cout << readPipeOutput() << std::endl; +} \ No newline at end of file diff --git a/bundles/shell/shell_tui/private/src/activator.c b/bundles/shell/shell_tui/private/src/activator.c deleted file mode 100644 index 9dc4da5..0000000 --- a/bundles/shell/shell_tui/private/src/activator.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include <stdlib.h> -#include <string.h> -#include <zconf.h> -#include <stdio.h> -#include <unistd.h> - -#include "bundle_context.h" -#include "bundle_activator.h" - - -#include "shell_tui.h" -#include "celix_shell.h" -#include "service_tracker.h" - -#define SHELL_USE_ANSI_CONTROL_SEQUENCES "SHELL_USE_ANSI_CONTROL_SEQUENCES" - -typedef struct shell_tui_activator { - shell_tui_t* shellTui; - long trackerId; - celix_shell_t* currentSvc; - bool useAnsiControlSequences; -} shell_tui_activator_t; - - -celix_status_t bundleActivator_create(bundle_context_pt context, void **userData) { - celix_status_t status = CELIX_SUCCESS; - - //Check if tty exists - if (!isatty(fileno(stdin))) { - printf("[Shell TUI] no tty connected. Shell TUI will not activate.\n"); - return status; - } - - shell_tui_activator_t* activator = calloc(1, sizeof(*activator)); - - if (activator != NULL) { - activator->trackerId = -1L; - bool useCommands; - const char* config = NULL; - bundleContext_getProperty(context, SHELL_USE_ANSI_CONTROL_SEQUENCES, &config); - if (config != NULL) { - useCommands = strncmp("true", config, 5) == 0; - } else { - char *term = getenv("TERM"); - useCommands = term != NULL; - } - - activator->shellTui = shellTui_create(useCommands); - - { - celix_service_tracking_options_t opts = CELIX_EMPTY_SERVICE_TRACKING_OPTIONS; - opts.filter.serviceName = CELIX_SHELL_SERVICE_NAME; - opts.callbackHandle = activator->shellTui; - opts.set = (void*)shellTui_setShell; - - activator->trackerId = celix_bundleContext_trackServicesWithOptions(context, &opts); - } - } - - if (activator != NULL && activator->shellTui != NULL) { - (*userData) = activator; - } else { - if (activator != NULL) { - shellTui_destroy(activator->shellTui); - celix_bundleContext_stopTracker(context, activator->trackerId); - activator->trackerId = -1L; - } - free(activator); - status = CELIX_ENOMEM; - } - - return status; -} - -celix_status_t bundleActivator_start(void * userData, bundle_context_pt context) { - celix_status_t status = CELIX_SUCCESS; - shell_tui_activator_t* act = (shell_tui_activator_t*) userData; - - if (act != NULL) { - shellTui_start(act->shellTui); - } - - return status; -} - -celix_status_t bundleActivator_stop(void * userData, bundle_context_pt context) { - celix_status_t status = CELIX_SUCCESS; - shell_tui_activator_t* act = (shell_tui_activator_t*) userData; - - if (act != NULL) { - celix_bundleContext_stopTracker(context, act->trackerId); - shellTui_stop(act->shellTui); - } - - return status; -} - -celix_status_t bundleActivator_destroy(void * userData, bundle_context_pt context) { - shell_tui_activator_t* act = (shell_tui_activator_t*) userData; - - if (act != NULL) { - shellTui_destroy(act->shellTui); - free(act); - } - - return CELIX_SUCCESS; -} diff --git a/bundles/shell/shell_tui/src/activator.c b/bundles/shell/shell_tui/src/activator.c new file mode 100644 index 0000000..aff3682 --- /dev/null +++ b/bundles/shell/shell_tui/src/activator.c @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <unistd.h> +#include "celix_bundle_activator.h" +#include "celix_shell.h" +#include "shell_tui.h" + +/** + * Whether to use ANSI control sequences to support backspace, left, up, etc key commands in the + * shell tui. Default is true if a TERM environment is set else false. + */ +#define SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES "SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES" + +/** + * The file descriptor to use as input. Default is `STDIN_FILENO`. + * This is used for testing. + */ +#define SHELL_TUI_INPUT_FILE_DESCRIPTOR "SHELL_TUI_INPUT_FILE_DESCRIPTOR" + +/** + * The file descriptor to use as output. Default is `STDOUT_FILENO`. + * This is used for testing. + */ +#define SHELL_TUI_OUTPUT_FILE_DESCRIPTOR "SHELL_TUI_OUTPUT_FILE_DESCRIPTOR" + +/** + * The file descriptor to use as error output. Default is `STDERR_FILENO`. + * This is used for testing. + */ +#define SHELL_TUI_ERROR_FILE_DESCRIPTOR "SHELL_TUI_ERROR_FILE_DESCRIPTOR" + +typedef struct celix_shell_tui_activator { + shell_tui_t* shellTui; + long trackerId; +} celix_shell_tui_activator_t; + + +static celix_status_t celix_shellTuiActivator_start(celix_shell_tui_activator_t* act, celix_bundle_context_t* ctx) { + celix_status_t status = CELIX_SUCCESS; + act->trackerId = -1L; + + int inputFd = (int)celix_bundleContext_getPropertyAsLong(ctx, SHELL_TUI_INPUT_FILE_DESCRIPTOR, STDIN_FILENO); + int outputFd = (int)celix_bundleContext_getPropertyAsLong(ctx, SHELL_TUI_OUTPUT_FILE_DESCRIPTOR, STDOUT_FILENO); + int errorFd = (int)celix_bundleContext_getPropertyAsLong(ctx, SHELL_TUI_ERROR_FILE_DESCRIPTOR, STDERR_FILENO); + + //Check if tty exists, no tty -> no shell tui expect if activateWithoutTTY==true + if (inputFd == STDIN_FILENO && !isatty(STDIN_FILENO)) { + celix_bundleContext_log(ctx, CELIX_LOG_LEVEL_INFO, "[Shell TUI] no tty connected. Shell TUI will not activate."); + return status; + } + bool useCommands = false; + char *term = getenv("TERM"); + useCommands = term != NULL; //if TERM exist, default is to use commands + useCommands = celix_bundleContext_getPropertyAsBool(ctx, SHELL_TUI_USE_ANSI_CONTROL_SEQUENCES, useCommands); + act->shellTui = shellTui_create(useCommands, inputFd, outputFd, errorFd); + + { + celix_service_tracking_options_t opts = CELIX_EMPTY_SERVICE_TRACKING_OPTIONS; + opts.filter.serviceName = CELIX_SHELL_SERVICE_NAME; + opts.callbackHandle = act->shellTui; + opts.set = (void*)shellTui_setShell; + act->trackerId = celix_bundleContext_trackServicesWithOptions(ctx, &opts); + } + + status = shellTui_start(act->shellTui); + if (status != CELIX_SUCCESS) { + shellTui_destroy(act->shellTui); + act->shellTui = NULL; + } + + return status; +} + +static celix_status_t celix_shellTuiActivator_stop(celix_shell_tui_activator_t* act, celix_bundle_context_t* ctx) { + celix_bundleContext_stopTracker(ctx, act->trackerId); + if (act->shellTui != NULL) { + shellTui_stop(act->shellTui); + shellTui_destroy(act->shellTui); + } + return CELIX_SUCCESS; +} + +CELIX_GEN_BUNDLE_ACTIVATOR(celix_shell_tui_activator_t, celix_shellTuiActivator_start, celix_shellTuiActivator_stop) \ No newline at end of file diff --git a/bundles/shell/shell_tui/private/src/history.c b/bundles/shell/shell_tui/src/history.c similarity index 100% rename from bundles/shell/shell_tui/private/src/history.c rename to bundles/shell/shell_tui/src/history.c diff --git a/bundles/shell/shell_tui/private/include/history.h b/bundles/shell/shell_tui/src/history.h similarity index 100% rename from bundles/shell/shell_tui/private/include/history.h rename to bundles/shell/shell_tui/src/history.h diff --git a/bundles/shell/shell_tui/private/src/shell_tui.c b/bundles/shell/shell_tui/src/shell_tui.c similarity index 55% rename from bundles/shell/shell_tui/private/src/shell_tui.c rename to bundles/shell/shell_tui/src/shell_tui.c index 33767f7..3b5c658 100644 --- a/bundles/shell/shell_tui/private/src/shell_tui.c +++ b/bundles/shell/shell_tui/src/shell_tui.c @@ -16,23 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -/** - * shell_tui.c - * - * \date Aug 13, 2010 - * \author <a href="mailto:[email protected]">Apache Celix Project Team</a> - * \copyright Apache License, Version 2.0 - */ -#include <sys/time.h> -#include <sys/select.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <termios.h> #include <unistd.h> +#include <poll.h> -#include "bundle_context.h" +#include "celix_array_list.h" #include "celix_shell.h" #include "shell_tui.h" #include "utils.h" @@ -55,13 +47,19 @@ #define KEY_DEL1 '3' #define KEY_DEL2 '~' +const char * const SHELL_NOT_AVAILABLE_MSG = "[Shell TUI] Shell service not available."; + struct shell_tui { celix_thread_mutex_t mutex; //protects shell celix_shell_t* shell; celix_thread_t thread; - int readPipeFd; - int writePipeFd; + int readStopPipeFd; + int writeStopPipeFd; + + int inputFd; + FILE* output; + FILE* error; bool useAnsiControlSequences; }; @@ -85,25 +83,35 @@ struct OriginalSettings { // static function declarations static void remove_newlines(char* line); -static void clearLine(); -static void cursorLeft(int n); -static void writeLine(const char*line, int pos); -static int autoComplete(celix_shell_t* shellSvc, char *in, int cursorPos, size_t maxLen); +static void clearLine(shell_tui_t*); +static void cursorLeft(shell_tui_t*, int n); +static void writeLine(shell_tui_t*, const char*line, int pos); +static int autoComplete(shell_tui_t*, celix_shell_t* shellSvc, char *in, int cursorPos, size_t maxLen); static void shellSigHandler(int sig, siginfo_t *info, void* ptr); static void* shellTui_runnable(void *data); -static void shellTui_parseInputForControl(shell_tui_t* shellTui, shell_context_t* ctx); -static void shellTui_parseInput(shell_tui_t* shellTui, shell_context_t* ctx); -static void writePrompt(void); +static int shellTui_parseInput(shell_tui_t* shellTui, shell_context_t* ctx); +static int shellTui_parseInputForControl(shell_tui_t* shellTui, shell_context_t* ctx); +static int shellTui_parseInputPlain(shell_tui_t* shellTui, shell_context_t* ctx); +static void writePrompt(shell_tui_t*); // Unfortunately has to be static, it is not possible to pass user defined data to the handler static struct OriginalSettings originalSettings; -shell_tui_t* shellTui_create(bool useAnsiControlSequences) { +shell_tui_t* shellTui_create(bool useAnsiControlSequences, int inputFd, int outputFd, int errorFd) { shell_tui_t* result = calloc(1, sizeof(*result)); - if (result != NULL) { - result->useAnsiControlSequences = useAnsiControlSequences; - celixThreadMutex_create(&result->mutex, NULL); + result->inputFd = inputFd; + result->output = outputFd == STDOUT_FILENO ? stdout : fdopen(outputFd, "a"); + if (result->output == NULL) { + fprintf(stderr, "Cannot open output fd %i for appending. Falling back to stdout\n", outputFd); + result->output = stdout; + } + result->error = errorFd == STDERR_FILENO ? stderr : fdopen(errorFd, "a"); + if (result->error == NULL) { + fprintf(stderr, "Cannot open error fd %i for appending. Falling back to stderr\n", errorFd); + result->error = stderr; } + result->useAnsiControlSequences = useAnsiControlSequences; + celixThreadMutex_create(&result->mutex, NULL); return result; } @@ -113,17 +121,16 @@ celix_status_t shellTui_start(shell_tui_t* shellTui) { int fds[2]; int rc = pipe(fds); if (rc == 0) { - shellTui->readPipeFd = fds[0]; - shellTui->writePipeFd = fds[1]; - if(fcntl(shellTui->writePipeFd, F_SETFL, O_NONBLOCK) == 0){ + shellTui->readStopPipeFd = fds[0]; + shellTui->writeStopPipeFd = fds[1]; + if (fcntl(shellTui->writeStopPipeFd, F_SETFL, O_NONBLOCK) == 0){ celixThread_create(&shellTui->thread, NULL, shellTui_runnable, shellTui); - } - else{ - fprintf(stderr,"fcntl on pipe failed"); + } else { + fprintf(shellTui->error,"[Shell TUI] fcntl on pipe failed"); status = CELIX_FILE_IO_EXCEPTION; } } else { - fprintf(stderr, "Cannot create pipe"); + fprintf(shellTui->error, "[Shell TUI] Cannot create pipe"); status = CELIX_BUNDLE_EXCEPTION; } @@ -131,17 +138,20 @@ celix_status_t shellTui_start(shell_tui_t* shellTui) { } celix_status_t shellTui_stop(shell_tui_t* shellTui) { - celix_status_t status = CELIX_SUCCESS; - write(shellTui->writePipeFd, "\0", 1); //trigger select to stop + write(shellTui->writeStopPipeFd, "", 1); //trigger select to stop celixThread_join(shellTui->thread, NULL); - close(shellTui->writePipeFd); - close(shellTui->readPipeFd); - return status; + close(shellTui->writeStopPipeFd); + close(shellTui->readStopPipeFd); + if (shellTui->output != stdout) { + fclose(shellTui->output); + } + if (shellTui->error != stderr) { + fclose(shellTui->error); + } + return CELIX_SUCCESS; } void shellTui_destroy(shell_tui_t* shellTui) { - if (shellTui == NULL) return; - celixThreadMutex_destroy(&shellTui->mutex); free(shellTui); } @@ -175,7 +185,7 @@ static void* shellTui_runnable(void *data) { ctx.hist = historyCreate(); struct termios term_new; - if (shellTui->useAnsiControlSequences) { + if (shellTui->useAnsiControlSequences && shellTui->inputFd == STDIN_FILENO) { sigaction(SIGINT, NULL, &originalSettings.oldSigIntAction); sigaction(SIGSEGV, NULL, &originalSettings.oldSigSegvAction); sigaction(SIGABRT, NULL, &originalSettings.oldSigAbrtAction); @@ -196,40 +206,47 @@ static void* shellTui_runnable(void *data) { tcsetattr(STDIN_FILENO, TCSANOW, &term_new); } - //setup file descriptors - fd_set rfds; - int nfds = shellTui->writePipeFd > STDIN_FILENO ? (shellTui->writePipeFd +1) : (STDIN_FILENO + 1); + //setup poll + nfds_t nfds = 2; + struct pollfd pollfds[2]; + pollfds[0].fd = shellTui->readStopPipeFd; + pollfds[0].events = POLLIN; + pollfds[1].fd = shellTui->inputFd; + pollfds[1].events = POLLIN; + bool printPrompt = true; for (;;) { - if (shellTui->useAnsiControlSequences) { - writeLine(ctx.in, ctx.pos); - } else { - writePrompt(); + if (printPrompt && shellTui->useAnsiControlSequences) { + writeLine(shellTui, ctx.in, ctx.pos); + } else if (printPrompt) { + writePrompt(shellTui); } - FD_ZERO(&rfds); - FD_SET(STDIN_FILENO, &rfds); - FD_SET(shellTui->readPipeFd, &rfds); - - if (select(nfds, &rfds, NULL, NULL, NULL) > 0) { - if (FD_ISSET(shellTui->readPipeFd, &rfds)) { - break; //something is written to the pipe -> exit thread - } else if (FD_ISSET(STDIN_FILENO, &rfds)) { - if (shellTui->useAnsiControlSequences) { - shellTui_parseInputForControl(shellTui, &ctx); - } else { - shellTui_parseInput(shellTui, &ctx); - } - if (!isatty(STDIN_FILENO)) { - //not connected to a tty anymore. sleep for 1 sec - usleep(10000000); - } + int rc = poll(pollfds, nfds, -1); + if (rc > 0) { + int nrOfCharsRead = 0; + if (pollfds[0].revents & POLLIN) { + break; //something is written to the stop pipe -> exit thread } + if (pollfds[1].revents & POLLIN) { + //something is written on the STDIN_FILENO fd + nrOfCharsRead = shellTui_parseInput(shellTui, &ctx); + } + printPrompt = nrOfCharsRead > 0; + if (shellTui->inputFd == STDIN_FILENO && !isatty(STDIN_FILENO)) { + //not connected to a tty (anymore) + //sleep for 1 sec to prevent 100% busy loop when a tty is removed. + usleep(10000000); + } + } else { + //error or (not configured timeout) + fprintf(shellTui->error, "[Shell TUI] Error reading stdin: %s\n", strerror(errno)); + break; } } historyDestroy(ctx.hist); - if (shellTui->useAnsiControlSequences) { + if (shellTui->useAnsiControlSequences && shellTui->inputFd == STDIN_FILENO) { tcsetattr(STDIN_FILENO, TCSANOW, &originalSettings.term_org); sigaction(SIGINT, &originalSettings.oldSigIntAction, NULL); sigaction(SIGSEGV, &originalSettings.oldSigSegvAction, NULL); @@ -241,7 +258,15 @@ static void* shellTui_runnable(void *data) { return NULL; } -static void shellTui_parseInput(shell_tui_t* shellTui, shell_context_t* ctx) { +static int shellTui_parseInput(shell_tui_t* shellTui, shell_context_t* ctx) { + if (shellTui->useAnsiControlSequences) { + return shellTui_parseInputForControl(shellTui, ctx); + } else { + return shellTui_parseInputPlain(shellTui, ctx); + } +} + +static int shellTui_parseInputPlain(shell_tui_t* shellTui, shell_context_t* ctx) { char* buffer = ctx->buffer; char* in = ctx->in; int pos = ctx->pos; @@ -249,16 +274,15 @@ static void shellTui_parseInput(shell_tui_t* shellTui, shell_context_t* ctx) { char* line = NULL; - int nr_chars = read(STDIN_FILENO, buffer, LINE_SIZE-pos-1); + int nr_chars = read(shellTui->inputFd, buffer, LINE_SIZE-pos-1); for(int bufpos = 0; bufpos < nr_chars; bufpos++) { if (buffer[bufpos] == KEY_ENTER) { //end of line -> forward command line = utils_stringTrim(in); celixThreadMutex_lock(&shellTui->mutex); if (shellTui->shell != NULL) { - printf("Providing command '%s' from in '%s'\n", line, in); - shellTui->shell->executeCommand(shellTui->shell->handle, line, stdout, stderr); + shellTui->shell->executeCommand(shellTui->shell->handle, line, shellTui->output, shellTui->error); } else { - fprintf(stderr, "Shell service not available\n"); + fprintf(shellTui->output, "%s\n", SHELL_NOT_AVAILABLE_MSG); } celixThreadMutex_unlock(&shellTui->mutex); pos = 0; @@ -271,22 +295,18 @@ static void shellTui_parseInput(shell_tui_t* shellTui, shell_context_t* ctx) { } } // for ctx->pos = pos; + return nr_chars; } -static void shellTui_parseInputForControl(shell_tui_t* shellTui, shell_context_t* ctx) { +static int shellTui_parseInputForControl(shell_tui_t* shellTui, shell_context_t* ctx) { char* buffer = ctx->buffer; char* in = ctx->in; char* dline = ctx->dline; history_t* hist = ctx->hist; int pos = ctx->pos; - char* line = NULL; - if (shellTui == NULL) { - return; - } - - int nr_chars = read(STDIN_FILENO, buffer, LINE_SIZE-pos-1); + int nr_chars = read(shellTui->inputFd, buffer, LINE_SIZE-pos-1); for(int bufpos = 0; bufpos < nr_chars; bufpos++) { if (buffer[bufpos] == KEY_ESC1 && buffer[bufpos+1] == KEY_ESC2) { switch (buffer[bufpos+2]) { @@ -294,27 +314,27 @@ static void shellTui_parseInputForControl(shell_tui_t* shellTui, shell_context_t if(historySize(hist) > 0) { strncpy(in, historyGetPrevLine(hist), LINE_SIZE); pos = strlen(in); - writeLine(in, pos); + writeLine(shellTui, in, pos); } break; case KEY_DOWN: if(historySize(hist) > 0) { strncpy(in, historyGetNextLine(hist), LINE_SIZE); pos = strlen(in); - writeLine(in, pos); + writeLine(shellTui, in, pos); } break; case KEY_RIGHT: if (pos < strlen(in)) { pos++; } - writeLine(in, pos); + writeLine(shellTui, in, pos); break; case KEY_LEFT: if (pos > 0) { pos--; } - writeLine(in, pos); + writeLine(shellTui, in, pos); break; case KEY_DEL1: if(buffer[bufpos+3] == KEY_DEL2) { @@ -325,7 +345,7 @@ static void shellTui_parseInputForControl(shell_tui_t* shellTui, shell_context_t in[i] = in[i + 1]; } } - writeLine(in, pos); + writeLine(shellTui, in, pos); } break; default: @@ -342,11 +362,11 @@ static void shellTui_parseInputForControl(shell_tui_t* shellTui, shell_context_t } pos--; } - writeLine(in, pos); + writeLine(shellTui, in, pos); continue; } else if(buffer[bufpos] == KEY_TAB) { celixThreadMutex_lock(&shellTui->mutex); - pos = autoComplete(shellTui->shell, in, pos, LINE_SIZE); + pos = autoComplete(shellTui, shellTui->shell, in, pos, LINE_SIZE); celixThreadMutex_unlock(&shellTui->mutex); continue; } else if (buffer[bufpos] != KEY_ENTER) { //not end of line -> text @@ -355,14 +375,14 @@ static void shellTui_parseInputForControl(shell_tui_t* shellTui, shell_context_t } in[pos] = buffer[bufpos]; pos++; - writeLine(in, pos); - fflush(stdout); + writeLine(shellTui, in, pos); + fflush(shellTui->output); continue; } //parse enter - writeLine(in, pos); - write(STDOUT_FILENO, "\n", 1); + writeLine(shellTui, in, pos); + fprintf(shellTui->output, "\n"); remove_newlines(in); history_addLine(hist, in); @@ -379,15 +399,15 @@ static void shellTui_parseInputForControl(shell_tui_t* shellTui, shell_context_t historyLineReset(hist); celixThreadMutex_lock(&shellTui->mutex); if (shellTui->shell != NULL) { - shellTui->shell->executeCommand(shellTui->shell->handle, line, stdout, stderr); - pos = 0; - nr_chars = 0; + shellTui->shell->executeCommand(shellTui->shell->handle, line, shellTui->output, shellTui->error); } else { - fprintf(stderr, "Shell service not available\n"); + fprintf(shellTui->output, "%s\n", SHELL_NOT_AVAILABLE_MSG); } celixThreadMutex_unlock(&shellTui->mutex); + break; } // for ctx->pos = pos; + return nr_chars; } static void remove_newlines(char* line) { @@ -400,71 +420,102 @@ static void remove_newlines(char* line) { } } -static void clearLine() { - printf("\033[2K\r"); - fflush(stdout); +static void clearLine(shell_tui_t* shellTui) { + fprintf(shellTui->output, "\033[2K\r"); + fflush(shellTui->output); } -static void cursorLeft(int n) { - if(n>0) { - printf("\033[%dD", n); - fflush(stdout); +static void cursorLeft(shell_tui_t* shellTui, int n) { + if (n>0) { + fprintf(shellTui->output, "\033[%dD", n); } + fflush(shellTui->output); } -static void writePrompt(void) { - write(STDIN_FILENO, PROMPT, strlen(PROMPT)); +static void writePrompt(shell_tui_t* shellTui) { + fwrite(PROMPT, 1, strlen(PROMPT), shellTui->output); + fflush(shellTui->output); } -static void writeLine(const char* line, int pos) { - clearLine(); - write(STDOUT_FILENO, PROMPT, strlen(PROMPT)); - write(STDOUT_FILENO, line, strlen(line)); - cursorLeft(strlen(line)-pos); +static void writeLine(shell_tui_t* shellTui, const char* line, int pos) { + clearLine(shellTui); + fwrite( PROMPT, 1, strlen(PROMPT), shellTui->output); + fwrite(line, 1, strlen(line), shellTui->output); + cursorLeft(shellTui, strlen(line)-pos); +} + +/** + * @brief Will check if there is a match with the input and the fully qualified cmd name or local name. + * + * @return Return cmd or local cmd if there is a match with the input. + */ +static char* isFullQualifiedOrLocalMatch(char *cmd, char *in, int cursorPos) { + char* matchCmd = NULL; + if (strncmp(in, cmd, cursorPos) == 0) { + matchCmd = cmd; + } else { + char* namespaceFound = strstr(cmd, "::"); + if (namespaceFound != NULL) { + //got a command with a namespace, strip namespace for a possible match. E.g celix::lb -> lb + char *localCmd = namespaceFound + 2; //note :: is 2 char, so forward 2 chars + if (strncmp(in, localCmd, cursorPos) == 0) { + matchCmd = localCmd; + } + } + } + return matchCmd; } -static int autoComplete(celix_shell_t* shellSvc, char *in, int cursorPos, size_t maxLen) { - array_list_pt commandList = NULL; - array_list_pt possibleCmdList = NULL; +static int autoComplete(shell_tui_t* shellTui, celix_shell_t* shellSvc, char *in, int cursorPos, size_t maxLen) { + celix_array_list_t* commandList = NULL; + celix_array_list_t* possibleCmdList = NULL; shellSvc->getCommands(shellSvc->handle, &commandList); - int nrCmds = arrayList_size(commandList); - arrayList_create(&possibleCmdList); + int nrCmds = celix_arrayList_size(commandList); + possibleCmdList = celix_arrayList_create(); for (int i = 0; i < nrCmds; i++) { - char *cmd = arrayList_get(commandList, i); - if (strncmp(in, cmd, cursorPos) == 0) { - arrayList_add(possibleCmdList, cmd); - } + char *cmd = celix_arrayList_get(commandList, i); + char *match = isFullQualifiedOrLocalMatch(cmd, in, cursorPos); + if (match != NULL) { + celix_arrayList_add(possibleCmdList, match); + } } - int nrPossibleCmds = arrayList_size(possibleCmdList); + int nrPossibleCmds = celix_arrayList_size(possibleCmdList); if (nrPossibleCmds == 0) { // Check if complete command with space is entered: show usage if this is the case if(in[strlen(in) - 1] == ' ') { for (int i = 0; i < nrCmds; i++) { - char *cmd = arrayList_get(commandList, i); - if (strncmp(in, cmd, strlen(cmd)) == 0) { - clearLine(); - char* usage = NULL; - shellSvc->getCommandUsage(shellSvc->handle, cmd, &usage); - printf("Usage:\n %s\n", usage); - } + char *cmd = celix_arrayList_get(commandList, i); + char *match = isFullQualifiedOrLocalMatch(cmd, in, strlen(in) - 1); + if (match != NULL) { + clearLine(shellTui); + char* usage = NULL; + shellSvc->getCommandUsage(shellSvc->handle, cmd, &usage); + fprintf(shellTui->output, "Usage:\n %s\n", usage); + free(usage); + } } } } else if (nrPossibleCmds == 1) { //Replace input string with the only possibility - snprintf(in, maxLen, "%s ", (char*)arrayList_get(possibleCmdList, 0)); + snprintf(in, maxLen, "%s ", (char*)celix_arrayList_get(possibleCmdList, 0)); cursorPos = strlen(in); } else { // Show possibilities - clearLine(); + clearLine(shellTui); for(int i = 0; i < nrPossibleCmds; i++) { - printf("%s ", (char*)arrayList_get(possibleCmdList, i)); + fprintf(shellTui->output,"%s ", (char*)celix_arrayList_get(possibleCmdList, i)); } - printf("\n"); + fprintf(shellTui->output,"\n"); } - arrayList_destroy(commandList); - arrayList_destroy(possibleCmdList); + + for (int i = 0; i < celix_arrayList_size(commandList); ++i) { + char* cmd = celix_arrayList_get(commandList, i); + free(cmd); + } + celix_arrayList_destroy(commandList); + celix_arrayList_destroy(possibleCmdList); return cursorPos; } diff --git a/bundles/shell/shell_tui/private/include/shell_tui.h b/bundles/shell/shell_tui/src/shell_tui.h similarity index 66% rename from bundles/shell/shell_tui/private/include/shell_tui.h rename to bundles/shell/shell_tui/src/shell_tui.h index 90061ad..f15e883 100644 --- a/bundles/shell/shell_tui/private/include/shell_tui.h +++ b/bundles/shell/shell_tui/src/shell_tui.h @@ -16,13 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -/** - * shell_tui.h - * - * \date Jan 16, 2016 - * \author <a href="mailto:[email protected]">Apache Celix Project Team</a> - * \copyright Apache License, Version 2.0 - */ #ifndef SHELL_TUI_H_ #define SHELL_TUI_H_ @@ -34,11 +27,34 @@ typedef struct shell_tui shell_tui_t ; -shell_tui_t* shellTui_create(bool useAnsiControlSequences); + +/** + * @brief Create a new shell tui. + * @param useAnsiControlSequences Whether to parse ansi control sequences. + * @param inputFd The input file descriptor to use. + * @param outputFd The output file descriptor to use. + * @param errorFd The error output file descriptor to use. + */ +shell_tui_t* shellTui_create(bool useAnsiControlSequences, int inputFd, int outputFd, int errorFd); + +/** + * @brief Start the shell tui and the thread reading the tty and optional extra read file descriptor. + */ celix_status_t shellTui_start(shell_tui_t* shellTui); + +/** + * @brief Stop the shell tui. + */ celix_status_t shellTui_stop(shell_tui_t* shellTui); + +/** + * @brief Free the resources for the shell tui + */ void shellTui_destroy(shell_tui_t* shellTui); +/** + * @brief set the shell service. + */ celix_status_t shellTui_setShell(shell_tui_t* shellTui, celix_shell_t* svc); #endif /* SHELL_TUI_H_ */ diff --git a/libs/framework/gtest/src/single_framework_test.cpp b/libs/framework/gtest/src/single_framework_test.cpp index 403707d..8765753 100644 --- a/libs/framework/gtest/src/single_framework_test.cpp +++ b/libs/framework/gtest/src/single_framework_test.cpp @@ -19,6 +19,8 @@ #include <gtest/gtest.h> #include <atomic> +#include <chrono> +#include <thread> #include "celix_launcher.h" #include "celix_framework_factory.h" @@ -74,6 +76,25 @@ TEST_F(CelixFramework, testEventQueue) { EXPECT_EQ(4, count); } +TEST_F(CelixFramework, testAsyncInstallStartStopAndUninstallBundle) { + long bndId = celix_framework_installBundleAsync(framework.get(), SIMPLE_TEST_BUNDLE1_LOCATION, false); + EXPECT_GE(bndId, 0); + EXPECT_TRUE(celix_framework_isBundleInstalled(framework.get(), bndId)); + EXPECT_FALSE(celix_framework_isBundleActive(framework.get(), bndId)); + + celix_framework_startBundle(framework.get(), bndId); + std::this_thread::sleep_for(std::chrono::milliseconds{100}); + EXPECT_TRUE(celix_framework_isBundleActive(framework.get(), bndId)); + + celix_framework_stopBundle(framework.get(), bndId); + std::this_thread::sleep_for(std::chrono::milliseconds{100}); + EXPECT_FALSE(celix_framework_isBundleActive(framework.get(), bndId)); + + celix_framework_uninstallBundleAsync(framework.get(), bndId); + std::this_thread::sleep_for(std::chrono::milliseconds{100}); + EXPECT_FALSE(celix_framework_isBundleInstalled(framework.get(), bndId)); +} + class FrameworkFactory : public ::testing::Test { public: FrameworkFactory() = default; diff --git a/libs/framework/include/celix_framework.h b/libs/framework/include/celix_framework.h index d50e5b4..5758795 100644 --- a/libs/framework/include/celix_framework.h +++ b/libs/framework/include/celix_framework.h @@ -30,13 +30,13 @@ extern "C" { #endif /** - * Returns the framework UUID. This is unique for every created framework and will not be the same if the process is + * @brief Returns the framework UUID. This is unique for every created framework and will not be the same if the process is * restarted. */ const char* celix_framework_getUUID(const celix_framework_t *fw); /** - * Returns the framework bundle context. This is the same as a 'normal' bundle context and can be used to register, use + * @brief Returns the framework bundle context. This is the same as a 'normal' bundle context and can be used to register, use * and track services. The only difference is that the framework is the bundle. * @param fw The framework * @return A pointer to the bundle context of the framework or NULL if something went wrong. @@ -44,7 +44,7 @@ const char* celix_framework_getUUID(const celix_framework_t *fw); celix_bundle_context_t* celix_framework_getFrameworkContext(const celix_framework_t *fw); /** - * Returns the framework bundle. This is the same as a 'normal' bundle, expect that this bundle cannot be uninstalled + * @brief Returns the framework bundle. This is the same as a 'normal' bundle, expect that this bundle cannot be uninstalled * and the `celix_bundle_getEntry` return a entries relative from the working directory. * @param fw The framework * @return A pointer to the bundle of the framework or NULL if something went wrong. @@ -52,7 +52,7 @@ celix_bundle_context_t* celix_framework_getFrameworkContext(const celix_framewor celix_bundle_t* celix_framework_getFrameworkBundle(const celix_framework_t *fw); /** - * Use the currently active (started) bundles. + * @brief Use the currently active (started) bundles. * The provided callback will be called for all the currently started bundles. * * @param ctx The bundle context. @@ -64,7 +64,7 @@ celix_bundle_t* celix_framework_getFrameworkBundle(const celix_framework_t *fw); void celix_framework_useBundles(celix_framework_t *fw, bool includeFrameworkBundle, void *callbackHandle, void(*use)(void *handle, const celix_bundle_t *bnd)); /** - * Use the bundle with the provided bundle id + * @brief Use the bundle with the provided bundle id * The provided callback will be called if the bundle is found. * * @param fw The framework. @@ -78,7 +78,7 @@ void celix_framework_useBundles(celix_framework_t *fw, bool includeFrameworkBund bool celix_framework_useBundle(celix_framework_t *fw, bool onlyActive, long bndId, void *callbackHandle, void(*use)(void *handle, const celix_bundle_t *bnd)); /** - * Check whether a bundle is installed. + * @brief Check whether a bundle is installed. * @param fw The Celix framework * @param bndId The bundle id to check * @return true if the bundle is installed. @@ -86,7 +86,7 @@ bool celix_framework_useBundle(celix_framework_t *fw, bool onlyActive, long bndI bool celix_framework_isBundleInstalled(celix_framework_t *fw, long bndId); /** - * Check whether the bundle is active. + * @brief Check whether the bundle is active. * @param fw The Celix framework * @param bndId The bundle id to check * @return true if the bundle is installed and active. @@ -95,7 +95,7 @@ bool celix_framework_isBundleActive(celix_framework_t *fw, long bndId); /** - * Install and optional start a bundle. + * @brief Install and optional start a bundle. * Will silently ignore bundle ids < 0. * * @param fw The Celix framework @@ -106,7 +106,7 @@ bool celix_framework_isBundleActive(celix_framework_t *fw, long bndId); long celix_framework_installBundle(celix_framework_t *fw, const char *bundleLoc, bool autoStart); /** - * Uninstall the bundle with the provided bundle id. If needed the bundle will be stopped first. + * @brief Uninstall the bundle with the provided bundle id. If needed the bundle will be stopped first. * Will silently ignore bundle ids < 0. * * @param fw The Celix framework @@ -116,7 +116,7 @@ long celix_framework_installBundle(celix_framework_t *fw, const char *bundleLoc, bool celix_framework_uninstallBundle(celix_framework_t *fw, long bndId); /** - * Stop the bundle with the provided bundle id. + * @brief Stop the bundle with the provided bundle id. * Will silently ignore bundle ids < 0. * * @param fw The Celix framework @@ -126,7 +126,7 @@ bool celix_framework_uninstallBundle(celix_framework_t *fw, long bndId); bool celix_framework_stopBundle(celix_framework_t *fw, long bndId); /** - * Start the bundle with the provided bundle id. + * @brief Start the bundle with the provided bundle id. * Will silently ignore bundle ids < 0. * * @param fw The Celix framework @@ -136,7 +136,53 @@ bool celix_framework_stopBundle(celix_framework_t *fw, long bndId); bool celix_framework_startBundle(celix_framework_t *fw, long bndId); /** - * Wait until the framework event queue is empty. + * @brief Install and optional start a bundle async. + * Will silently ignore bundle ids < 0. + * + * If the bundle needs to be started this will be done a separate spawned thread. + * + * @param fw The Celix framework + * @param bundleLoc The bundle location to the bundle zip file. + * @param autoStart If the bundle should also be started. + * @return The bundle id of the installed bundle or -1 if the bundle could not be installed + */ +long celix_framework_installBundleAsync(celix_framework_t *fw, const char *bundleLoc, bool autoStart); + +/** + * @brief Uninstall the bundle with the provided bundle id async. If needed the bundle will be stopped first. + * Will silently ignore bundle ids < 0. + * + * The bundle will be uninstalled on a separate spawned thread. + * + * @param fw The Celix framework + * @param bndId The bundle id to uninstall. + */ +void celix_framework_uninstallBundleAsync(celix_framework_t *fw, long bndId); + +/** + * @brief Stop the bundle with the provided bundle id async. + * Will silently ignore bundle ids < 0. + * + * The bundle will be stopped on a separate spawned thread. + * + * @param fw The Celix framework + * @param bndId The bundle id to stop. + */ +void celix_framework_stopBundleAsync(celix_framework_t *fw, long bndId); + +/** + * @brief Start the bundle with the provided bundle id async. + * Will silently ignore bundle ids < 0. + * + * The bundle will be started on a separate spawned thread. + * + * @param fw The Celix framework + * @param bndId The bundle id to start. + */ +void celix_framework_startBundleAsync(celix_framework_t *fw, long bndId); + +/** + * @brief Wait until the framework event queue is empty. * * The Celix framework has an event queue which (among others) handles bundle events. * This function can be used to ensure that all queue event are handled, mainly useful @@ -147,7 +193,7 @@ bool celix_framework_startBundle(celix_framework_t *fw, long bndId); void celix_framework_waitForEmptyEventQueue(celix_framework_t *fw); /** - * Sets the log function for this framework. + * @brief Sets the log function for this framework. * Default the celix framework will log to stdout/stderr. * * A log function can be injected to change how the Celix framework logs. @@ -157,18 +203,18 @@ void celix_framework_setLogCallback(celix_framework_t* fw, void* logHandle, void /** - * wait until all events for the bundle identified by the bndId are processed. + * @brief wait until all events for the bundle identified by the bndId are processed. */ void celix_framework_waitUntilNoEventsForBnd(celix_framework_t* fw, long bndId); /** - * Returns whether the current thread is the Celix framework event loop thread. + * @brief Returns whether the current thread is the Celix framework event loop thread. */ bool celix_framework_isCurrentThreadTheEventLoop(celix_framework_t* fw); /** - * Fire a generic event. The event will be added to the event loop and handled on the event loop thread. + * @brief Fire a generic event. The event will be added to the event loop and handled on the event loop thread. * * if bndId >=0 the bundle usage count will be increased while the event is not yet processed or finished processing. * The eventName is expected to be const char* valid during til the event is finished processing. @@ -179,7 +225,7 @@ bool celix_framework_isCurrentThreadTheEventLoop(celix_framework_t* fw); long celix_framework_fireGenericEvent(celix_framework_t* fw, long eventId, long bndId, const char *eventName, void* processData, void (*processCallback)(void *data), void* doneData, void (*doneCallback)(void* doneData)); /** - * Get the next event id. + * @brief Get the next event id. * * This can be used to ensure celix_framework_waitForGenericEvent can be used to wait for an event. * The returned event id will not be used by the framework itself unless followed up with a @@ -188,13 +234,13 @@ long celix_framework_fireGenericEvent(celix_framework_t* fw, long eventId, long long celix_framework_nextEventId(celix_framework_t *fw); /** - * Wait until a event with the provided event id is completely handled. + * @brief Wait until a event with the provided event id is completely handled. * This function will directly return if the provided event id is not in the event loop (already done or never issued). */ void celix_framework_waitForGenericEvent(celix_framework_t *fw, long eventId); /** - * Wait until the framework is stopped. + * @brief Wait until the framework is stopped. */ void celix_framework_waitForStop(celix_framework_t *framework); diff --git a/libs/framework/src/framework.c b/libs/framework/src/framework.c index c7b289e..e10d259 100644 --- a/libs/framework/src/framework.c +++ b/libs/framework/src/framework.c @@ -2168,7 +2168,7 @@ static void celix_framework_waitForBundleEvents(celix_framework_t *fw, long bndI } } -long celix_framework_installBundle(celix_framework_t *fw, const char *bundleLoc, bool autoStart) { +long celix_framework_installBundleInternal(celix_framework_t *fw, const char *bundleLoc, bool autoStart, bool forcedAsync) { long bundleId = -1; bundle_t *bnd = NULL; celix_status_t status = CELIX_SUCCESS; @@ -2179,7 +2179,7 @@ long celix_framework_installBundle(celix_framework_t *fw, const char *bundleLoc, celix_framework_bundle_entry_t* bndEntry = celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(fw, bundleId); if (bndEntry != NULL) { - status = celix_framework_startBundleOnANonCelixEventThread(fw, bndEntry); + status = celix_framework_startBundleOnANonCelixEventThread(fw, bndEntry, forcedAsync); celix_framework_bundleEntry_decreaseUseCount(bndEntry); } else { status = CELIX_ILLEGAL_STATE; @@ -2193,18 +2193,34 @@ long celix_framework_installBundle(celix_framework_t *fw, const char *bundleLoc, return bundleId; } -bool celix_framework_uninstallBundle(celix_framework_t *fw, long bndId) { +long celix_framework_installBundle(celix_framework_t *fw, const char *bundleLoc, bool autoStart) { + return celix_framework_installBundleInternal(fw, bundleLoc, autoStart, false); +} + +long celix_framework_installBundleAsync(celix_framework_t *fw, const char *bundleLoc, bool autoStart) { + return celix_framework_installBundleInternal(fw, bundleLoc, autoStart, true); +} + +static bool celix_framework_uninstallBundleInternal(celix_framework_t *fw, long bndId, bool forcedAsync) { bool uninstalled = false; celix_framework_bundle_entry_t *bndEntry = celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(fw, bndId); if (bndEntry != NULL) { - celix_status_t status = celix_framework_uninstallBundleOnANonCelixEventThread(fw, bndEntry); + celix_status_t status = celix_framework_uninstallBundleOnANonCelixEventThread(fw, bndEntry, forcedAsync); celix_framework_waitForBundleEvents(fw, bndId); - //note not decreasing bndEntry, because this is not deleted (uninstalled) + //note not decreasing bndEntry, because this entry should now be deleted (uninstalled) uninstalled = status == CELIX_SUCCESS; } return uninstalled; } +bool celix_framework_uninstallBundle(celix_framework_t *fw, long bndId) { + return celix_framework_uninstallBundleInternal(fw, bndId, false); +} + +void celix_framework_uninstallBundleAsync(celix_framework_t *fw, long bndId) { + celix_framework_uninstallBundleInternal(fw, bndId, true); +} + celix_status_t celix_framework_uninstallBundleEntry(celix_framework_t* framework, celix_framework_bundle_entry_t* bndEntry) { assert(!celix_framework_isCurrentThreadTheEventLoop(framework)); celix_bundle_state_e bndState = celix_bundle_getState(bndEntry->bnd); @@ -2213,6 +2229,7 @@ celix_status_t celix_framework_uninstallBundleEntry(celix_framework_t* framework } celix_framework_bundle_entry_t* removedEntry = fw_bundleEntry_removeBundleEntryAndIncreaseUseCount(framework, bndEntry->bndId); + celix_framework_bundleEntry_decreaseUseCount(bndEntry); if (removedEntry != NULL) { celix_status_t status = CELIX_SUCCESS; @@ -2278,13 +2295,13 @@ celix_status_t celix_framework_uninstallBundleEntry(celix_framework_t* framework } } -bool celix_framework_stopBundle(celix_framework_t *fw, long bndId) { +static bool celix_framework_stopBundleInternal(celix_framework_t* fw, long bndId, bool forcedAsync) { bool stopped = false; celix_framework_bundle_entry_t *bndEntry = celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(fw, bndId); if (bndEntry != NULL) { celix_bundle_state_e state = celix_bundle_getState(bndEntry->bnd); if (state == OSGI_FRAMEWORK_BUNDLE_ACTIVE) { - celix_status_t rc = celix_framework_stopBundleOnANonCelixEventThread(fw, bndEntry); + celix_status_t rc = celix_framework_stopBundleOnANonCelixEventThread(fw, bndEntry, forcedAsync); stopped = rc == CELIX_SUCCESS; } else if (state == OSGI_FRAMEWORK_BUNDLE_RESOLVED) { //already stopped, silently ignore. @@ -2297,6 +2314,14 @@ bool celix_framework_stopBundle(celix_framework_t *fw, long bndId) { return stopped; } +bool celix_framework_stopBundle(celix_framework_t *fw, long bndId) { + return celix_framework_stopBundleInternal(fw, bndId, false); +} + +void celix_framework_stopBundleAsync(celix_framework_t* fw, long bndId) { + celix_framework_stopBundleInternal(fw, bndId, true); +} + celix_status_t celix_framework_stopBundleEntry(celix_framework_t* framework, celix_framework_bundle_entry_t* bndEntry) { assert(!celix_framework_isCurrentThreadTheEventLoop(framework)); @@ -2408,13 +2433,13 @@ celix_status_t celix_framework_stopBundleEntry(celix_framework_t* framework, cel return status; } -bool celix_framework_startBundle(celix_framework_t *fw, long bndId) { +bool celix_framework_startBundleInternal(celix_framework_t *fw, long bndId, bool forcedAsync) { bool started = false; celix_framework_bundle_entry_t *bndEntry = celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(fw, bndId); if (bndEntry != NULL) { celix_bundle_state_e state = celix_bundle_getState(bndEntry->bnd); if (state == OSGI_FRAMEWORK_BUNDLE_INSTALLED || state == OSGI_FRAMEWORK_BUNDLE_RESOLVED) { - celix_status_t rc = celix_framework_startBundleOnANonCelixEventThread(fw, bndEntry); + celix_status_t rc = celix_framework_startBundleOnANonCelixEventThread(fw, bndEntry, forcedAsync); started = rc == CELIX_SUCCESS; } celix_framework_bundleEntry_decreaseUseCount(bndEntry); @@ -2423,6 +2448,14 @@ bool celix_framework_startBundle(celix_framework_t *fw, long bndId) { return started; } +bool celix_framework_startBundle(celix_framework_t *fw, long bndId) { + return celix_framework_startBundleInternal(fw, bndId, false); +} + +void celix_framework_startBundleAsync(celix_framework_t *fw, long bndId) { + celix_framework_startBundleInternal(fw, bndId, true); +} + celix_status_t celix_framework_startBundleEntry(celix_framework_t* framework, celix_framework_bundle_entry_t* bndEntry) { assert(!celix_framework_isCurrentThreadTheEventLoop(framework)); celix_status_t status = CELIX_SUCCESS; diff --git a/libs/framework/src/framework_bundle_lifecycle_handler.c b/libs/framework/src/framework_bundle_lifecycle_handler.c index c7ef523..09393c3 100644 --- a/libs/framework/src/framework_bundle_lifecycle_handler.c +++ b/libs/framework/src/framework_bundle_lifecycle_handler.c @@ -36,12 +36,15 @@ static void* celix_framework_stopStartBundleThread(void *data) { celix_framework_stopBundleEntry(handler->framework, handler->bndEntry); break; default: + celix_framework_bundleEntry_decreaseUseCount(handler->bndEntry); celix_framework_uninstallBundleEntry(handler->framework, handler->bndEntry); break; } int doneVal = 1; __atomic_store(&handler->done, &doneVal, __ATOMIC_SEQ_CST); - celix_framework_bundleEntry_decreaseUseCount(handler->bndEntry); + if (handler->command != CELIX_BUNDLE_LIFECYCLE_UNINSTALL) { + celix_framework_bundleEntry_decreaseUseCount(handler->bndEntry); + } return NULL; } @@ -96,8 +99,12 @@ static void celix_framework_createAndStartBundleLifecycleHandler(celix_framework celixThreadMutex_unlock(&fw->bundleLifecycleHandling.mutex); } -celix_status_t celix_framework_startBundleOnANonCelixEventThread(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry) { - if (celix_framework_isCurrentThreadTheEventLoop(fw)) { +celix_status_t celix_framework_startBundleOnANonCelixEventThread(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry, bool forceSpawnThread) { + if (forceSpawnThread) { + fw_log(fw->logger, CELIX_LOG_LEVEL_TRACE, "start bundle from a separate thread"); + celix_framework_createAndStartBundleLifecycleHandler(fw, bndEntry, CELIX_BUNDLE_LIFECYCLE_START); + return CELIX_SUCCESS; + } else if (celix_framework_isCurrentThreadTheEventLoop(fw)) { fw_log(fw->logger, CELIX_LOG_LEVEL_DEBUG, "Cannot start bundle from Celix event thread. Using a separate thread to start bundle. See celix_bundleContext_startBundle for more info."); celix_framework_createAndStartBundleLifecycleHandler(fw, bndEntry, CELIX_BUNDLE_LIFECYCLE_START); @@ -107,8 +114,12 @@ celix_status_t celix_framework_startBundleOnANonCelixEventThread(celix_framework } } -celix_status_t celix_framework_stopBundleOnANonCelixEventThread(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry) { - if (celix_framework_isCurrentThreadTheEventLoop(fw)) { +celix_status_t celix_framework_stopBundleOnANonCelixEventThread(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry, bool forceSpawnThread) { + if (forceSpawnThread) { + fw_log(fw->logger, CELIX_LOG_LEVEL_TRACE, "stop bundle from a separate thread"); + celix_framework_createAndStartBundleLifecycleHandler(fw, bndEntry, CELIX_BUNDLE_LIFECYCLE_STOP); + return CELIX_SUCCESS; + } else if (celix_framework_isCurrentThreadTheEventLoop(fw)) { fw_log(fw->logger, CELIX_LOG_LEVEL_DEBUG, "Cannot stop bundle from Celix event thread. Using a separate thread to stop bundle. See celix_bundleContext_startBundle for more info."); celix_framework_createAndStartBundleLifecycleHandler(fw, bndEntry, CELIX_BUNDLE_LIFECYCLE_STOP); @@ -118,8 +129,12 @@ celix_status_t celix_framework_stopBundleOnANonCelixEventThread(celix_framework_ } } -celix_status_t celix_framework_uninstallBundleOnANonCelixEventThread(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry) { - if (celix_framework_isCurrentThreadTheEventLoop(fw)) { +celix_status_t celix_framework_uninstallBundleOnANonCelixEventThread(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry, bool forceSpawnThread) { + if (forceSpawnThread) { + fw_log(fw->logger, CELIX_LOG_LEVEL_TRACE, "uninstall bundle from a separate thread"); + celix_framework_createAndStartBundleLifecycleHandler(fw, bndEntry, CELIX_BUNDLE_LIFECYCLE_UNINSTALL); + return CELIX_SUCCESS; + } else if (celix_framework_isCurrentThreadTheEventLoop(fw)) { fw_log(fw->logger, CELIX_LOG_LEVEL_DEBUG, "Cannot uninstall bundle from Celix event thread. Using a separate thread to uninstall bundle. See celix_bundleContext_uninstall Bundle for more info."); celix_framework_createAndStartBundleLifecycleHandler(fw, bndEntry, CELIX_BUNDLE_LIFECYCLE_UNINSTALL); diff --git a/libs/framework/src/framework_private.h b/libs/framework/src/framework_private.h index 19226b7..6c30ab1 100644 --- a/libs/framework/src/framework_private.h +++ b/libs/framework/src/framework_private.h @@ -300,23 +300,37 @@ void celix_framework_bundleEntry_decreaseUseCount(celix_framework_bundle_entry_t */ celix_framework_bundle_entry_t* celix_framework_bundleEntry_getBundleEntryAndIncreaseUseCount(celix_framework_t *fw, long bndId); -/** - * Start a bundle and ensure that this is not done on the Celix event thread. - * Will spawn a thread if needed. - */ -celix_status_t celix_framework_startBundleOnANonCelixEventThread(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry); + + + /** + * Start a bundle and ensure that this is not done on the Celix event thread. + * Will spawn a thread if needed. + * @param fw The Celix framework + * @param bndEntry A bnd entry + * @param forceSpawnThread If the true, the start bundle will always be done on a spawn thread + * @return CELIX_SUCCESS of the call went alright. + */ +celix_status_t celix_framework_startBundleOnANonCelixEventThread(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry, bool forceSpawnThread); /** * Stop a bundle and ensure that this is not done on the Celix event thread. * Will spawn a thread if needed. + * @param fw The Celix framework + * @param bndEntry A bnd entry + * @param forceSpawnThread If the true, the start bundle will always be done on a spawn thread + * @return CELIX_SUCCESS of the call went alright. */ -celix_status_t celix_framework_stopBundleOnANonCelixEventThread(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry); +celix_status_t celix_framework_stopBundleOnANonCelixEventThread(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry, bool forceSpawnThread); /** * Uninstall (and if needed stop) a bundle and ensure that this is not done on the Celix event thread. * Will spawn a thread if needed. + * @param fw The Celix framework + * @param bndEntry A bnd entry + * @param forceSpawnThread If the true, the start bundle will always be done on a spawn thread + * @return CELIX_SUCCESS of the call went alright. */ -celix_status_t celix_framework_uninstallBundleOnANonCelixEventThread(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry); +celix_status_t celix_framework_uninstallBundleOnANonCelixEventThread(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry, bool forceSpawnThread); /** @@ -337,7 +351,7 @@ celix_status_t celix_framework_startBundleEntry(celix_framework_t* fw, celix_fra celix_status_t celix_framework_stopBundleEntry(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry); /** - * Uinstall a bundle. Cannot be called on the Celix event thread. + * Uninstall a bundle. Cannot be called on the Celix event thread. */ celix_status_t celix_framework_uninstallBundleEntry(celix_framework_t* fw, celix_framework_bundle_entry_t* bndEntry);
