blob: 29968c68240492b88e18a2e49103b9d5961dd220 [file] [log] [blame]
/*
* 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_bundle_cache.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <errno.h>
#include "celix_constants.h"
#include "celix_log.h"
#include "celix_properties.h"
#include "celix_file_utils.h"
#include "celix_stdlib_cleanup.h"
#include "celix_utils.h"
#include "celix_bundle_context.h"
#include "framework_private.h"
#include "celix_string_hash_map.h"
#include "celix_stdio_cleanup.h"
//for Celix 3.0 update to a different bundle root scheme
//#define CELIX_BUNDLE_ARCHIVE_ROOT_FORMAT "%s/bundle_%li"
#define CELIX_BUNDLE_ARCHIVE_ROOT_FORMAT "%s/bundle%li"
#define FW_LOG(level, ...) \
celix_framework_log(cache->fw->logger, (level), __FUNCTION__ , __FILE__, __LINE__, __VA_ARGS__)
struct celix_bundle_cache {
celix_framework_t* fw;
char* cacheDir;
bool deleteOnDestroy;
bool deleteOnCreate;
celix_thread_mutex_t mutex; //protects below and access to the cache dir
celix_string_hash_map_t* locationToBundleIdLookupMap; //key = location, value = bundle id.
bool locationToBundleIdLookupMapLoaded; //true if the locationToBundleIdLookupMap is loaded from disk
};
static const char* celix_bundleCache_programName() {
#if defined(__APPLE__) || defined(__FreeBSD__)
return getprogname();
#elif defined(_GNU_SOURCE)
return program_invocation_short_name;
#else
return NULL;
#endif
}
static const char* celix_bundleCache_cacheDirPath(celix_framework_t* fw) {
return celix_framework_getConfigProperty(fw,
CELIX_FRAMEWORK_CACHE_DIR,
CELIX_FRAMEWORK_CACHE_DIR_DEFAULT,
NULL);
}
static bool celix_bundleCache_useTmpDir(celix_framework_t* fw) {
return celix_framework_getConfigPropertyAsBool(fw,
CELIX_FRAMEWORK_CACHE_USE_TMP_DIR,
CELIX_FRAMEWORK_CACHE_USE_TMP_DIR_DEFAULT,
NULL);
}
static bool celix_bundleCache_cleanOnCreate(celix_framework_t* fw) {
return celix_framework_getConfigPropertyAsBool(fw,
CELIX_FRAMEWORK_CLEAN_CACHE_DIR_ON_CREATE,
CELIX_FRAMEWORK_CLEAN_CACHE_DIR_ON_CREATE_DEFAULT,
NULL);
}
celix_status_t celix_bundleCache_create(celix_framework_t* fw, celix_bundle_cache_t** out) {
celix_status_t status = CELIX_SUCCESS;
celix_autofree celix_bundle_cache_t* cache = calloc(1, sizeof(*cache));
if (!cache) {
return CELIX_ENOMEM;
}
cache->fw = fw;
bool useTmpDir = celix_bundleCache_useTmpDir(fw);
cache->deleteOnCreate = celix_bundleCache_cleanOnCreate(fw);
cache->deleteOnDestroy = useTmpDir; //if tmp dir is used, delete on destroy
celix_autoptr(celix_string_hash_map_t) locationToBundleIdLookupMap =
cache->locationToBundleIdLookupMap = celix_stringHashMap_create();
if (NULL == cache->locationToBundleIdLookupMap) {
return CELIX_ENOMEM;
}
celixThreadMutex_create(&cache->mutex, NULL);
celix_autoptr(celix_thread_mutex_t) mutex = &cache->mutex;
if (useTmpDir) {
//Using /tmp dir for cache, so that multiple frameworks can be launched
//instead of cacheDir = ".cache";
const char* pg = celix_bundleCache_programName();
pg = !pg ? "no-program-name" : pg;
asprintf(&cache->cacheDir, "/tmp/celix-cache-%s-%s", pg, celix_framework_getUUID(fw));
} else {
const char* cacheDir = celix_bundleCache_cacheDirPath(fw);
cache->cacheDir = celix_utils_strdup(cacheDir);
}
if (NULL == cache->cacheDir) {
return CELIX_ENOMEM;
}
celix_autofree char* cacheDir = cache->cacheDir;
if (cache->deleteOnCreate) {
status = celix_bundleCache_deleteCacheDir(cache);
if (status != CELIX_SUCCESS) {
return status;
}
}
const char* errorStr;
status = celix_utils_createDirectory(cache->cacheDir, false, &errorStr);
if (status != CELIX_SUCCESS) {
fw_logCode(fw->logger, CELIX_LOG_LEVEL_ERROR, status, "Cannot create bundle cache directory %s, error %s",
cache->cacheDir, errorStr);
return status;
}
cache->locationToBundleIdLookupMapLoaded = false;
celix_steal_ptr(cacheDir);
celix_steal_ptr(mutex);
celix_steal_ptr(locationToBundleIdLookupMap);
*out = celix_steal_ptr(cache);
return CELIX_SUCCESS;
}
celix_status_t celix_bundleCache_destroy(celix_bundle_cache_t* cache) {
celix_status_t status = CELIX_SUCCESS;
if (cache->deleteOnDestroy) {
status = celix_bundleCache_deleteCacheDir(cache);
}
free(cache->cacheDir);
celix_stringHashMap_destroy(cache->locationToBundleIdLookupMap);
celixThreadMutex_destroy(&cache->mutex);
free(cache);
return status;
}
celix_status_t celix_bundleCache_deleteCacheDir(celix_bundle_cache_t* cache) {
const char* err = NULL;
celixThreadMutex_lock(&cache->mutex);
celix_status_t status = celix_utils_deleteDirectory(cache->cacheDir, &err);
if (status == CELIX_SUCCESS) {
celix_stringHashMap_clear(cache->locationToBundleIdLookupMap);
}
celixThreadMutex_unlock(&cache->mutex);
if (status != CELIX_SUCCESS) {
fw_logCode(cache->fw->logger, CELIX_LOG_LEVEL_ERROR, status, "Cannot delete bundle cache directory %s: %s",
cache->cacheDir, err);
}
return status;
}
celix_status_t
celix_bundleCache_createArchive(celix_bundle_cache_t* cache, long id, const char* location, celix_bundle_archive_t** archiveOut) {
celix_status_t status = CELIX_SUCCESS;
celix_bundle_archive_t* archive = NULL;
char archiveRootBuffer[CELIX_DEFAULT_STRING_CREATE_BUFFER_SIZE];
char* archiveRoot = celix_utils_writeOrCreateString(archiveRootBuffer, sizeof(archiveRootBuffer),
CELIX_BUNDLE_ARCHIVE_ROOT_FORMAT, cache->cacheDir, id);
if (archiveRoot) {
celixThreadMutex_lock(&cache->mutex);
status = celix_bundleArchive_create(cache->fw, archiveRoot, id, location, &archive);
if (status == CELIX_SUCCESS) {
celix_stringHashMap_put(cache->locationToBundleIdLookupMap, location, (void*) id);
}
celixThreadMutex_unlock(&cache->mutex);
celix_utils_freeStringIfNotEqual(archiveRootBuffer, archiveRoot);
} else {
status = CELIX_ENOMEM;
}
if (status != CELIX_SUCCESS) {
fw_logCode(cache->fw->logger, CELIX_LOG_LEVEL_ERROR, status, "Failed to create archive.");
return status;
}
*archiveOut = archive;
return status;
}
celix_status_t celix_bundleCache_createSystemArchive(celix_framework_t* fw, celix_bundle_archive_t** archive) {
return celix_bundleCache_createArchive(fw->cache, CELIX_FRAMEWORK_BUNDLE_ID, NULL, archive);
}
void celix_bundleCache_destroyArchive(celix_bundle_cache_t* cache, celix_bundle_archive_t* archive) {
celixThreadMutex_lock(&cache->mutex);
if (!celix_bundleArchive_isCacheValid(archive)) {
const char* loc = celix_bundleArchive_getLocation(archive);
(void) celix_stringHashMap_remove(cache->locationToBundleIdLookupMap, loc);
}
(void)celix_bundleArchive_removeInvalidDirs(archive);
celixThreadMutex_unlock(&cache->mutex);
celix_bundleArchive_destroy(archive);
}
/**
* Update location->bundle id lookup map.
*/
static celix_status_t celix_bundleCache_updateIdForLocationLookupMap(celix_bundle_cache_t* cache) {
celix_autoptr(DIR) dir = opendir(cache->cacheDir);
if (dir == NULL) {
fw_logCode(cache->fw->logger, CELIX_LOG_LEVEL_ERROR, CELIX_BUNDLE_EXCEPTION,
"Cannot open bundle cache directory %s: %s", cache->cacheDir, strerror(errno));
return CELIX_FILE_IO_EXCEPTION;
}
char archiveRootBuffer[CELIX_DEFAULT_STRING_CREATE_BUFFER_SIZE];
struct dirent* dent = NULL;
while ((dent = readdir(dir)) != NULL) {
if (strncmp(dent->d_name, "bundle", 6) != 0) {
continue;
}
char* bundleStateProperties = celix_utils_writeOrCreateString(archiveRootBuffer, sizeof(archiveRootBuffer),
"%s/%s/%s", cache->cacheDir, dent->d_name,
CELIX_BUNDLE_ARCHIVE_STATE_PROPERTIES_FILE_NAME);
celix_auto(celix_utils_string_guard_t) strGuard = celix_utils_stringGuard_init(archiveRootBuffer, bundleStateProperties);
if (celix_utils_fileExists(bundleStateProperties)) {
celix_autoptr(celix_properties_t) props = NULL;
celix_status_t status = celix_properties_load(bundleStateProperties, 0, &props);
if (status != CELIX_SUCCESS) {
fw_logCode(cache->fw->logger, CELIX_LOG_LEVEL_ERROR, status,
"Cannot load bundle state properties from %s", bundleStateProperties);
celix_framework_logTssErrors(cache->fw->logger, CELIX_LOG_LEVEL_ERROR);
return status;
}
const char* visitLoc = celix_properties_get(props, CELIX_BUNDLE_ARCHIVE_LOCATION_PROPERTY_NAME, NULL);
long bndId = celix_properties_getAsLong(props, CELIX_BUNDLE_ARCHIVE_BUNDLE_ID_PROPERTY_NAME, -1);
if (visitLoc != NULL && bndId >= 0) {
fw_log(cache->fw->logger, CELIX_LOG_LEVEL_TRACE, "Adding location %s -> bnd id %li to lookup map",
visitLoc, bndId);
celix_stringHashMap_putLong(cache->locationToBundleIdLookupMap, visitLoc, bndId);
}
}
}
return CELIX_SUCCESS;
}
celix_status_t
celix_bundleCache_findBundleIdForLocation(celix_bundle_cache_t* cache, const char* location, long* outBndId) {
*outBndId = -1L;
celix_auto(celix_mutex_lock_guard_t) lck = celixMutexLockGuard_init(&cache->mutex);
if (!cache->locationToBundleIdLookupMapLoaded) {
celix_status_t status = celix_bundleCache_updateIdForLocationLookupMap(cache);
if (status != CELIX_SUCCESS) {
return status;
}
cache->locationToBundleIdLookupMapLoaded = true;
}
if (celix_stringHashMap_hasKey(cache->locationToBundleIdLookupMap, location)) {
*outBndId = celix_stringHashMap_getLong(cache->locationToBundleIdLookupMap, location, -1);
}
return CELIX_SUCCESS;
}
bool celix_bundleCache_isBundleIdAlreadyUsed(celix_bundle_cache_t* cache, long bndId) {
bool found = false;
celixThreadMutex_lock(&cache->mutex);
CELIX_STRING_HASH_MAP_ITERATE(cache->locationToBundleIdLookupMap, iter) {
if (iter.value.longValue == bndId) {
found = true;
break;
}
}
celixThreadMutex_unlock(&cache->mutex);
return found;
}
static celix_status_t
celix_bundleCache_createBundleArchivesForSpaceSeparatedList(celix_framework_t* fw, long* bndId, const char* list,
bool logProgress) {
celix_status_t status = CELIX_SUCCESS;
char delims[] = " ";
char* savePtr = NULL;
char zipFileListBuffer[CELIX_DEFAULT_STRING_CREATE_BUFFER_SIZE];
char* zipFileList = celix_utils_writeOrCreateString(zipFileListBuffer, sizeof(zipFileListBuffer), "%s", list);
if (zipFileList) {
char* location = strtok_r(zipFileList, delims, &savePtr);
while (location != NULL) {
celix_bundle_archive_t* archive = NULL;
status = celix_bundleCache_createArchive(fw->cache, (*bndId)++, location, &archive);
if (status != CELIX_SUCCESS) {
fw_logCode(fw->logger, CELIX_LOG_LEVEL_ERROR, status,
"Cannot create bundle archive for %s", location);
break;
} else {
celix_log_level_e lvl = logProgress ? CELIX_LOG_LEVEL_INFO : CELIX_LOG_LEVEL_DEBUG;
fw_log(fw->logger, lvl, "Created bundle cache '%s' for bundle archive %s (bndId=%li).",
celix_bundleArchive_getCurrentRevisionRoot(archive),
celix_bundleArchive_getSymbolicName(archive), celix_bundleArchive_getId(archive));
celix_bundleArchive_destroy(archive);
}
location = strtok_r(NULL, delims, &savePtr);
}
} else {
status = CELIX_ENOMEM;
fw_logCode(fw->logger, CELIX_LOG_LEVEL_ERROR, status, "Failed to create zip file list.");
}
celix_utils_freeStringIfNotEqual(zipFileListBuffer, zipFileList);
return status;
}
celix_status_t celix_bundleCache_createBundleArchivesCache(celix_framework_t* fw, bool logProgress) {
celix_status_t status = CELIX_SUCCESS;
const char* const celixKeys[] = {CELIX_AUTO_START_0, CELIX_AUTO_START_1, CELIX_AUTO_START_2, CELIX_AUTO_START_3,
CELIX_AUTO_START_4, CELIX_AUTO_START_5, CELIX_AUTO_START_6, NULL};
long bndId = CELIX_FRAMEWORK_BUNDLE_ID + 1; //note cleaning cache, so starting bundle id at 1
const char* errorStr = NULL;
status = celix_utils_deleteDirectory(fw->cache->cacheDir, &errorStr);
if (status != CELIX_SUCCESS) {
fw_logCode(fw->logger, CELIX_LOG_LEVEL_ERROR, status, "Failed to delete bundle cache directory %s: %s",
fw->cache->cacheDir, errorStr);
return status;
} else {
celix_log_level_e lvl = logProgress ? CELIX_LOG_LEVEL_INFO : CELIX_LOG_LEVEL_DEBUG;
fw_log(fw->logger, lvl, "Deleted bundle cache directory %s", fw->cache->cacheDir);
}
for (int i = 0; celixKeys[i] != NULL; ++i) {
const char* autoStart = celix_framework_getConfigProperty(fw, celixKeys[i], NULL, NULL);
if (autoStart) {
status = celix_bundleCache_createBundleArchivesForSpaceSeparatedList(fw, &bndId, autoStart, logProgress);
if (status != CELIX_SUCCESS) {
fw_logCode(fw->logger, CELIX_LOG_LEVEL_ERROR, status,
"Failed to create bundle archives for auto start list %s", autoStart);
return status;
}
}
}
const char* autoInstall = celix_framework_getConfigProperty(fw, CELIX_AUTO_INSTALL, NULL, NULL);
if (autoInstall) {
status = celix_bundleCache_createBundleArchivesForSpaceSeparatedList(fw, &bndId, autoInstall, logProgress);
if (status != CELIX_SUCCESS) {
fw_logCode(fw->logger, CELIX_LOG_LEVEL_ERROR, status,
"Failed to create bundle archives for auto install list %s", autoInstall);
return status;
}
}
return status;
}