| /* |
| * 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 "celix_launcher.h" |
| #include "celix_launcher_private.h" |
| #include "celix_compiler.h" |
| |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <libgen.h> |
| |
| #ifndef CELIX_NO_CURLINIT |
| #include <stdbool.h> |
| #include <curl/curl.h> |
| #endif |
| |
| #include "celix_bundle_context.h" |
| #include "celix_constants.h" |
| #include "celix_err.h" |
| #include "celix_file_utils.h" |
| #include "celix_framework_factory.h" |
| #include "celix_framework_utils.h" |
| #include "celix_threads.h" |
| |
| #define DEFAULT_CONFIG_FILE "config.properties" |
| |
| #define CELIX_LAUNCHER_OK_EXIT_CODE 0 |
| #define CELIX_LAUNCHER_ERROR_EXIT_CODE 1 |
| |
| typedef struct { |
| celix_thread_mutex_t lock; // protect the following |
| framework_t* framework; |
| long shutdownEventId; |
| bool launched; |
| bool shutdown; // accessed through atomic operations |
| int signal; // accessed through atomic operations |
| } celix_launcher_t; |
| |
| typedef struct { |
| bool showHelp; |
| bool showProps; |
| bool createCache; |
| const char* configFile; |
| } celix_launcher_options_t; |
| |
| static celix_launcher_t g_launcher = { PTHREAD_MUTEX_INITIALIZER, NULL, -1L, false, false, -1 }; |
| |
| /** |
| * @brief SIGUSR1 SIGUSR2 no-op callback handler. |
| */ |
| static void celix_launcher_noopSignalHandler(int signal); |
| |
| /** |
| * @brief SIGINT SIGTERM callback to shutdown the framework. |
| */ |
| static void celix_launcher_shutdownFrameworkSignalHandler(int signal); |
| |
| /** |
| * @brief Check and set if a framework can be launched. |
| * @return true if the framework can be launched. |
| */ |
| static bool celix_launcher_checkFrameworkLaunched(); |
| |
| /** |
| * @brief Reset the launcher state. |
| */ |
| static void celix_launcher_resetLauncher(); |
| |
| /** |
| * @brief Create a Celix framework instance with the given embedded and runtime properties. |
| * |
| * Also: |
| * - Set up signal handlers for SIGINT and SIGTERM to stop the framework. |
| * - Set up signal handlers for SIGUSR1 and SIGUSR2 to ignore the signals. |
| * - If `CELIX_NO_CURLINIT` was not defined during compilation, initialize Curl. |
| */ |
| static celix_status_t celix_launcher_createFramework(celix_properties_t* embeddedProps, |
| const celix_properties_t* runtimeProps, |
| celix_framework_t** frameworkOut); |
| |
| /** |
| * @brief Parse launcher options from the given command line arguments. |
| */ |
| static celix_status_t celix_launcher_parseOptions(int argc, char* argv[], celix_launcher_options_t* opts); |
| |
| /** |
| * @brief Print the usage of the Celix launcher command arguments. |
| */ |
| static void celix_launcher_printUsage(char* progName); |
| |
| /** |
| * @brief Print the embedded, runtime and combined properties. |
| */ |
| static void celix_launcher_printProperties(const celix_properties_t* embeddedProps, |
| const celix_properties_t* runtimeProps, |
| const char* configFile); |
| |
| |
| /** |
| * @brief Create the bundle cache using the given embedded and runtime properties. |
| * |
| * Will create a framework instance to create the bundle cache and destroy the framework instance afterwards. |
| */ |
| static celix_status_t celix_launcher_createBundleCache(celix_properties_t* embeddedProps, const celix_properties_t* runtimeProps); |
| |
| /** |
| * @brief Load the runtime properties from the given file. |
| * |
| * If no file is given, a check is done for the default file and if it exists, the default file is used. |
| */ |
| static celix_status_t celix_launcher_loadRuntimeProperties(const char* configFile, celix_properties_t** outConfigProperties); |
| |
| #ifndef CELIX_NO_CURLINIT |
| /** |
| * @brief Initializes the CURL library if it has not been initialized yet. |
| * |
| * This function ensures that the CURL initialization function is |
| * called only once, regardless of how many times a celix framework is launched. |
| * |
| * @return CELIX_SUCCESS if CURL was initialized successfully, or |
| * CELIX_ILLEGAL_STATE if curl initialization failed. |
| */ |
| static celix_status_t celix_launcher_initializeCurl(); |
| |
| /** |
| * @brief Cleans up the CURL library if it was previously initialized. |
| * |
| * This function is called with __attribute__(destructor) to ensure that the |
| * CURL cleanup function is called only once, regardless of how many times |
| * a (global) launched celix framework is stopped and started again. |
| */ |
| static void celix_launcher_cleanupCurl() __attribute__((destructor)); |
| |
| /** |
| * @brief CURL initialization bool, used to check if CURL has been initialized. |
| */ |
| static bool g_curl_initialized = 0; |
| #endif |
| |
| /** |
| * @brief Set the global framework instance. |
| */ |
| static celix_status_t celix_launcher_setGlobalFramework(celix_framework_t* fw); |
| |
| int celix_launcher_launchAndWait(int argc, char* argv[], const char* embeddedConfig) { |
| celix_autoptr(celix_properties_t) embeddedProps = NULL; |
| if (embeddedConfig) { |
| (void)celix_properties_loadFromString(embeddedConfig, 0, &embeddedProps); |
| } else { |
| embeddedProps = celix_properties_create(); |
| } |
| if (embeddedProps == NULL) { |
| celix_err_printErrors(stderr, "Error creating embedded properties: ", NULL); |
| return CELIX_LAUNCHER_ERROR_EXIT_CODE; |
| } |
| |
| celix_launcher_options_t options; |
| memset(&options, 0, sizeof(options)); |
| celix_status_t status = celix_launcher_parseOptions(argc, argv, &options); |
| if (status != CELIX_SUCCESS) { |
| return CELIX_LAUNCHER_ERROR_EXIT_CODE; |
| } |
| |
| celix_autoptr(celix_properties_t) runtimeProps = NULL; |
| status = celix_launcher_loadRuntimeProperties(options.configFile, &runtimeProps); |
| if (status != CELIX_SUCCESS) { |
| return CELIX_LAUNCHER_ERROR_EXIT_CODE; |
| } |
| |
| if (options.showHelp) { |
| celix_launcher_printUsage(argv[0]); |
| return CELIX_LAUNCHER_OK_EXIT_CODE; |
| } else if (options.showProps) { |
| celix_launcher_printProperties(embeddedProps, runtimeProps, options.configFile); |
| return CELIX_LAUNCHER_OK_EXIT_CODE; |
| } |
| |
| if (!celix_launcher_checkFrameworkLaunched()) { |
| return CELIX_LAUNCHER_ERROR_EXIT_CODE; |
| } |
| |
| if (options.createCache) { |
| status = celix_launcher_createBundleCache(celix_steal_ptr(embeddedProps), runtimeProps); |
| celix_launcher_resetLauncher(); |
| return status == CELIX_SUCCESS ? CELIX_LAUNCHER_OK_EXIT_CODE : CELIX_LAUNCHER_ERROR_EXIT_CODE; |
| } |
| |
| celix_framework_t* framework = NULL; |
| status = celix_launcher_createFramework(celix_steal_ptr(embeddedProps), runtimeProps, &framework); |
| if (status != CELIX_SUCCESS) { |
| celix_launcher_resetLauncher(); |
| return CELIX_LAUNCHER_ERROR_EXIT_CODE; |
| } |
| status = celix_launcher_setGlobalFramework(framework); |
| if (status != CELIX_SUCCESS) { |
| celix_bundleContext_log(celix_framework_getFrameworkContext(framework), CELIX_LOG_LEVEL_WARNING, |
| "Failed to schedule celix_shutdown_check"); |
| } |
| |
| #ifndef CELIX_NO_CURLINIT |
| status = celix_launcher_initializeCurl(); |
| if (status != CELIX_SUCCESS) { |
| celix_launcher_resetLauncher(); |
| return CELIX_LAUNCHER_ERROR_EXIT_CODE; |
| } |
| #endif |
| |
| celix_framework_waitForStop(framework); |
| celix_launcher_resetLauncher(); |
| |
| return CELIX_LAUNCHER_OK_EXIT_CODE; |
| } |
| |
| static celix_status_t celix_launcher_parseOptions(int argc, char* argv[], celix_launcher_options_t* opts) { |
| // Perform some minimal command-line option parsing... |
| int optCount = 0; |
| for (int i = 1; i < argc; ++i) { |
| const char* opt = argv[i]; |
| // Check whether the user wants some help... |
| if (strncmp("-?", opt, sizeof("-?")) == 0 || strncmp("-h", opt, sizeof("-h")) == 0 || |
| strncmp("--help", opt, sizeof("--help")) == 0) { |
| opts->showHelp = true; |
| optCount++; |
| } else if (strncmp("-p", opt, sizeof("-p")) == 0 || strncmp("--props", opt, sizeof("--props")) == 0) { |
| opts->showProps = true; |
| optCount++; |
| } else if (strncmp("-c", opt, sizeof("-c")) == 0 || |
| strncmp("--create-bundle-cache", opt, sizeof("--create-bundle-cache")) == 0) { |
| opts->createCache = true; |
| optCount++; |
| } else { |
| if (opts->configFile) { |
| fprintf(stderr, "Error: multiple configuration files specified\n"); |
| return CELIX_ILLEGAL_ARGUMENT; |
| } |
| opts->configFile = opt; |
| } |
| } |
| |
| //check if not multiple options are set |
| if (optCount > 1) { |
| fprintf(stderr, "Error: multiple options specified\n"); |
| return CELIX_ILLEGAL_ARGUMENT; |
| } |
| |
| return CELIX_SUCCESS; |
| } |
| |
| static celix_status_t celix_launcher_createFramework(celix_properties_t* embeddedProps, |
| const celix_properties_t* runtimeProps, |
| celix_framework_t** frameworkOut) { |
| celix_status_t status = celix_launcher_combineProperties(embeddedProps, runtimeProps); |
| if (status != CELIX_SUCCESS) { |
| return status; |
| } |
| |
| struct sigaction sigact; |
| memset(&sigact, 0, sizeof(sigact)); |
| sigact.sa_handler = celix_launcher_shutdownFrameworkSignalHandler; |
| sigaction(SIGINT, &sigact, NULL); |
| sigaction(SIGTERM, &sigact, NULL); |
| |
| memset(&sigact, 0, sizeof(sigact)); |
| sigact.sa_handler = celix_launcher_noopSignalHandler; |
| sigaction(SIGUSR1, &sigact, NULL); |
| sigaction(SIGUSR2, &sigact, NULL); |
| |
| *frameworkOut = celix_frameworkFactory_createFramework(embeddedProps); |
| return *frameworkOut != NULL ? CELIX_SUCCESS : CELIX_FRAMEWORK_EXCEPTION; |
| } |
| |
| // LCOV_EXCL_START |
| /** |
| * @brief SIGUSR1 SIGUSR2 no-op callback |
| */ |
| static void celix_launcher_noopSignalHandler(int signal __attribute__((unused))) { |
| // ignoring signal SIGUSR1, SIGUSR2. Can be used on threads |
| } |
| // LCOV_EXCL_STOP |
| |
| static void celix_launcher_printUsage(char* progName) { |
| printf("Usage:\n %s [-h|-p|-c] [path/to/runtime/config.properties]\n", basename(progName)); |
| printf("Options:\n"); |
| printf("\t-h | --help: Show this message.\n"); |
| printf("\t-p | --props: Show the embedded and runtime properties for this Celix container and exit.\n"); |
| printf("\t-c | --create-bundle-cache: Create the bundle cache for this Celix container and exit.\n"); |
| printf("\n"); |
| } |
| |
| static void celix_launcher_printProperties(const celix_properties_t* embeddedProps, |
| const celix_properties_t* runtimeProps, |
| const char* configFile) { |
| celix_autoptr(celix_properties_t) keys = celix_properties_create(); // only to store the keys |
| |
| printf("Embedded properties:\n"); |
| if (embeddedProps == NULL || celix_properties_size(embeddedProps) == 0) { |
| printf("|- Empty!\n"); |
| } else { |
| CELIX_PROPERTIES_ITERATE(embeddedProps, visit) { |
| printf("|- %s=%s\n", visit.key, visit.entry.value); |
| celix_properties_set(keys, visit.key, NULL); |
| } |
| } |
| printf("\n"); |
| |
| |
| printf("Runtime properties (input %s):\n", configFile ? configFile : "none"); |
| if (runtimeProps == NULL || celix_properties_size(runtimeProps) == 0) { |
| printf("|- Empty!\n"); |
| } else { |
| CELIX_PROPERTIES_ITERATE(runtimeProps, visit) { |
| printf("|- %s=%s\n", visit.key, visit.entry.value); |
| celix_properties_set(keys, visit.key, NULL); |
| } |
| } |
| printf("\n"); |
| |
| // combined result |
| printf("Resolved (env, runtime and embedded) properties:\n"); |
| if (celix_properties_size(keys) == 0) { |
| printf("|- Empty!\n"); |
| } else { |
| CELIX_PROPERTIES_ITERATE(keys, visit) { |
| const char* valEm = celix_properties_get(embeddedProps, visit.key, NULL); |
| const char* valRt = celix_properties_get(runtimeProps, visit.key, NULL); |
| const char* envVal = getenv(visit.key); |
| const char* val = envVal != NULL ? envVal : valRt != NULL ? valRt : valEm; |
| const char* source = envVal != NULL ? "environment" : valRt != NULL ? "runtime" : "embedded"; |
| printf("|- %s=%s (source %s)\n", visit.key, val, source); |
| } |
| } |
| printf("\n"); |
| } |
| |
| static celix_status_t celix_launcher_createBundleCache(celix_properties_t* embeddedProps, |
| const celix_properties_t* runtimeProps) { |
| celix_status_t status = celix_launcher_combineProperties(embeddedProps, runtimeProps); |
| if (status != CELIX_SUCCESS) { |
| return status; |
| } |
| |
| celix_framework_t* fw = celix_frameworkFactory_createFramework(embeddedProps); |
| if (!fw) { |
| fprintf(stderr, "Failed to create framework for bundle cache creation\n"); |
| return CELIX_FRAMEWORK_EXCEPTION; |
| } |
| |
| status = celix_framework_utils_createBundleArchivesCache(fw); |
| if (status != CELIX_SUCCESS) { |
| celix_bundle_context_t* ctx = celix_framework_getFrameworkContext(fw); |
| celix_bundleContext_log(ctx, CELIX_LOG_LEVEL_ERROR, "Failed to create bundle cache"); |
| } |
| celix_frameworkFactory_destroyFramework(fw); |
| return status; |
| } |
| |
| celix_status_t celix_launcher_combineProperties(celix_properties_t* embeddedProps, |
| const celix_properties_t* runtimeProps) { |
| if (embeddedProps && runtimeProps) { |
| CELIX_PROPERTIES_ITERATE(runtimeProps, visit) { |
| celix_status_t status = celix_properties_setEntry(embeddedProps, visit.key, &visit.entry); |
| if (status != CELIX_SUCCESS) { |
| celix_properties_destroy(embeddedProps); |
| return status; |
| } |
| } |
| } |
| return CELIX_SUCCESS; |
| } |
| |
| /** |
| * @brief Load the config properties from the given file. |
| * If configFile is NULL, config properties will only be loaded if DEFAULT_CONFIG_FILE exists. |
| */ |
| static celix_status_t celix_launcher_loadRuntimeProperties(const char* configFile, celix_properties_t** outConfigProperties) { |
| *outConfigProperties = NULL; |
| bool loadConfig = configFile != NULL; |
| if (!loadConfig) { |
| loadConfig = celix_utils_fileExists(DEFAULT_CONFIG_FILE); |
| configFile = DEFAULT_CONFIG_FILE; |
| } |
| if (loadConfig) { |
| celix_status_t status = celix_properties_load(configFile, 0, outConfigProperties); |
| if (status != CELIX_SUCCESS) { |
| celix_err_printErrors(stderr, "Error creating runtime properties: ", NULL); |
| return status; |
| } |
| } |
| return CELIX_SUCCESS; |
| } |
| |
| static void celix_launcher_shutdownFrameworkSignalHandler(int signal) { |
| __atomic_store_n(&g_launcher.signal, signal, __ATOMIC_RELAXED); |
| __atomic_store_n(&g_launcher.shutdown, true, __ATOMIC_RELEASE); |
| } |
| |
| void celix_launcher_triggerStop() { |
| celix_auto(celix_mutex_lock_guard_t) lck = celixMutexLockGuard_init(&g_launcher.lock); |
| if (g_launcher.framework == NULL || g_launcher.shutdownEventId == -1) { |
| fprintf(stderr, "No global framework instance to stop\n"); |
| return; |
| } |
| __atomic_store_n(&g_launcher.shutdown, true, __ATOMIC_RELAXED); |
| celix_bundleContext_wakeupScheduledEvent(celix_framework_getFrameworkContext(g_launcher.framework), |
| g_launcher.shutdownEventId); |
| } |
| |
| static void celix_launcher_shutdownCheck(void* data CELIX_UNUSED) { |
| celix_auto(celix_mutex_lock_guard_t) lck = celixMutexLockGuard_init(&g_launcher.lock); |
| if (__atomic_load_n(&g_launcher.shutdown, __ATOMIC_ACQUIRE)) { |
| int sig = __atomic_load_n(&g_launcher.signal, __ATOMIC_RELAXED); |
| if (sig != -1) { |
| celix_bundleContext_log( |
| celix_framework_getFrameworkContext(g_launcher.framework), CELIX_LOG_LEVEL_INFO, |
| "Stopping Celix framework due to signal %s", strsignal(sig)); |
| } |
| celix_bundleContext_removeScheduledEventAsync(celix_framework_getFrameworkContext(g_launcher.framework), |
| g_launcher.shutdownEventId); |
| celix_framework_stopBundleAsync(g_launcher.framework, CELIX_FRAMEWORK_BUNDLE_ID); |
| g_launcher.shutdownEventId = -1; |
| } |
| } |
| |
| static celix_status_t celix_launcher_setGlobalFramework(celix_framework_t* fw) { |
| celix_auto(celix_mutex_lock_guard_t) lck = celixMutexLockGuard_init(&g_launcher.lock); |
| celix_bundle_context_t *ctx = celix_framework_getFrameworkContext(fw); |
| g_launcher.framework = fw; |
| celix_scheduled_event_options_t opts = CELIX_EMPTY_SCHEDULED_EVENT_OPTIONS; |
| opts.name = "celix_shutdown_check"; |
| opts.callback = celix_launcher_shutdownCheck; |
| opts.callbackData = &g_launcher; |
| opts.initialDelayInSeconds = celix_bundleContext_getPropertyAsDouble(ctx, CELIX_LAUNCHER_SHUTDOWN_PERIOD_IN_SECONDS, |
| CELIX_LAUNCHER_SHUTDOWN_PERIOD_IN_SECONDS_DEFAULT); |
| opts.intervalInSeconds = celix_bundleContext_getPropertyAsDouble(ctx, CELIX_LAUNCHER_SHUTDOWN_PERIOD_IN_SECONDS, |
| CELIX_LAUNCHER_SHUTDOWN_PERIOD_IN_SECONDS_DEFAULT); |
| g_launcher.shutdownEventId = celix_bundleContext_scheduleEvent(ctx, &opts); |
| return g_launcher.shutdownEventId >= 0 ? CELIX_SUCCESS : CELIX_FRAMEWORK_EXCEPTION; |
| } |
| |
| static bool celix_launcher_checkFrameworkLaunched() { |
| celix_auto(celix_mutex_lock_guard_t) lck = celixMutexLockGuard_init(&g_launcher.lock); |
| if (g_launcher.launched) { |
| fprintf(stderr, "Cannot launch framework, already launched\n"); |
| return false; |
| } |
| g_launcher.launched = true; |
| return true; |
| } |
| |
| static void celix_launcher_resetLauncher() { |
| celix_auto(celix_mutex_lock_guard_t) lck = celixMutexLockGuard_init(&g_launcher.lock); |
| __atomic_store_n(&g_launcher.shutdown, false, __ATOMIC_RELAXED); |
| __atomic_store_n(&g_launcher.signal, -1, __ATOMIC_RELAXED); |
| if (g_launcher.framework) { |
| long schedId = g_launcher.shutdownEventId; |
| g_launcher.shutdownEventId = -1L; |
| celix_bundleContext_removeScheduledEvent(celix_framework_getFrameworkContext(g_launcher.framework), schedId); |
| celix_frameworkFactory_destroyFramework(g_launcher.framework); |
| g_launcher.framework = NULL; |
| } |
| g_launcher.launched = false; |
| } |
| |
| #ifndef CELIX_NO_CURLINIT |
| celix_status_t celix_launcher_initializeCurl() { |
| bool alreadyInitialized = __atomic_exchange_n(&g_curl_initialized, true, __ATOMIC_SEQ_CST); |
| if (alreadyInitialized) { |
| return CELIX_SUCCESS; |
| } |
| CURLcode cc = curl_global_init(CURL_GLOBAL_DEFAULT); |
| if (cc != CURLE_OK) { |
| fprintf(stderr, "Failed to initialize Curl: %s\n", curl_easy_strerror(cc)); |
| //note only 1 framework can be launcher with celix launcher and the launcher is set, so no startup race |
| __atomic_store_n(&g_curl_initialized, false, __ATOMIC_SEQ_CST); |
| return CELIX_ILLEGAL_STATE; |
| } |
| return CELIX_SUCCESS; |
| } |
| |
| static void celix_launcher_cleanupCurl() { |
| bool wasInitialized = __atomic_load_n(&g_curl_initialized, __ATOMIC_SEQ_CST); |
| if (wasInitialized) { |
| curl_global_cleanup(); |
| } |
| } |
| #endif |