blob: 671d139a0c9c5acc342c3915087c68dbee364d13 [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_archive.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "celix_bundle_manifest.h"
#include "celix_bundle_manifest_type.h"
#include "celix_constants.h"
#include "celix_compiler.h"
#include "celix_file_utils.h"
#include "celix_framework_utils_private.h"
#include "celix_log.h"
#include "framework_private.h"
#include "celix_utils.h"
#include "celix_properties.h"
/**
* The bundle archive which is used to store the bundle data and can be reused when a framework is restarted.
* The lifecycle of a bundle archive is coupled to the lifecycle of the bundle that is created from the archive.
*
* @note The bundle archive is thread safe.
*/
struct celix_bundle_archive {
// initialed during creation and immutable
celix_framework_t* fw;
long id;
char* archiveRoot;
char* savedBundleStatePropertiesPath;
char* storeRoot;
char* resourceCacheRoot;
char* location;
celix_bundle_manifest_t* manifest;
bool cacheValid; // is the cache valid (e.g. not deleted)
bool valid; // is the archive valid (e.g. not deleted)
};
static celix_status_t celix_bundleArchive_storeBundleStateProperties(celix_bundle_archive_t* archive) {
bool needUpdate = false;
celix_autoptr(celix_properties_t) bundleStateProperties = NULL;
if (celix_utils_fileExists(archive->savedBundleStatePropertiesPath)) {
celix_status_t status =
celix_properties_load(archive->savedBundleStatePropertiesPath, 0, &bundleStateProperties);
if (status != CELIX_SUCCESS) {
celix_framework_logTssErrors(archive->fw->logger, CELIX_LOG_LEVEL_ERROR);
}
}
if (!bundleStateProperties) {
bundleStateProperties = celix_properties_create();
}
const char* bndVersion = celix_properties_getAsString(celix_bundleManifest_getAttributes(archive->manifest),
CELIX_BUNDLE_VERSION, "");
if (!bundleStateProperties) {
return CELIX_ENOMEM;
}
// set/update bundle cache state properties
if (celix_properties_getAsLong(bundleStateProperties, CELIX_BUNDLE_ARCHIVE_BUNDLE_ID_PROPERTY_NAME, 0) !=
archive->id) {
celix_properties_setLong(bundleStateProperties, CELIX_BUNDLE_ARCHIVE_BUNDLE_ID_PROPERTY_NAME, archive->id);
needUpdate = true;
}
if (strcmp(celix_properties_get(bundleStateProperties, CELIX_BUNDLE_ARCHIVE_LOCATION_PROPERTY_NAME, ""),
archive->location) != 0) {
celix_properties_set(bundleStateProperties, CELIX_BUNDLE_ARCHIVE_LOCATION_PROPERTY_NAME, archive->location);
needUpdate = true;
}
if (strcmp(celix_properties_get(bundleStateProperties, CELIX_BUNDLE_ARCHIVE_SYMBOLIC_NAME_PROPERTY_NAME, ""),
celix_bundleManifest_getBundleSymbolicName(archive->manifest)) != 0) {
celix_properties_set(
bundleStateProperties,
CELIX_BUNDLE_ARCHIVE_SYMBOLIC_NAME_PROPERTY_NAME,
celix_bundleManifest_getBundleSymbolicName(archive->manifest));
needUpdate = true;
}
if (strcmp(celix_properties_get(bundleStateProperties, CELIX_BUNDLE_ARCHIVE_VERSION_PROPERTY_NAME, ""),
bndVersion) != 0) {
celix_properties_set(bundleStateProperties, CELIX_BUNDLE_ARCHIVE_VERSION_PROPERTY_NAME, bndVersion);
needUpdate = true;
}
// save bundle cache state properties
if (needUpdate) {
celix_status_t status = celix_properties_save(
bundleStateProperties,
archive->savedBundleStatePropertiesPath,
CELIX_PROPERTIES_ENCODE_PRETTY);
if (status != CELIX_SUCCESS) {
return status;
}
}
return CELIX_SUCCESS;
}
static celix_status_t celix_bundleArchive_removeResourceCache(celix_bundle_archive_t* archive) {
celix_status_t status = CELIX_SUCCESS;
const char* error = NULL;
struct stat st;
status = lstat(archive->resourceCacheRoot, &st);
if (status == -1 && errno == ENOENT) {
return CELIX_SUCCESS;
} else if(status == -1 && errno != ENOENT) {
status = CELIX_ERROR_MAKE(CELIX_FACILITY_CERRNO,errno);
fw_logCode(archive->fw->logger, CELIX_LOG_LEVEL_ERROR, status, "Failed to stat bundle archive directory '%s'", archive->resourceCacheRoot);
return status;
}
assert(status == 0);
// celix_utils_deleteDirectory does not work for dangling symlinks, so handle this case separately
if (S_ISLNK(st.st_mode)) {
status = unlink(archive->resourceCacheRoot);
if (status == -1) {
status = CELIX_ERROR_MAKE(CELIX_FACILITY_CERRNO,errno);
error = "Failed to remove existing bundle symlink";
}
} else {
status = celix_utils_deleteDirectory(archive->resourceCacheRoot, &error);
}
if (status != CELIX_SUCCESS) {
fw_logCode(archive->fw->logger, CELIX_LOG_LEVEL_ERROR, status, "Failed to remove existing bundle archive revision directory '%s': %s", archive->resourceCacheRoot, error);
}
return status;
}
static celix_status_t
celix_bundleArchive_extractBundle(celix_bundle_archive_t* archive, const char* bundleUrl) {
celix_status_t status = CELIX_SUCCESS;
bool extractBundle = true;
//get revision mod time;
struct timespec revisionMod;
status = celix_utils_getLastModified(archive->resourceCacheRoot, &revisionMod);
if (status != CELIX_SUCCESS && errno != ENOENT) {
fw_logCode(archive->fw->logger, CELIX_LOG_LEVEL_ERROR, status, "Failed to get last modified time for bundle archive revision directory '%s'", archive->resourceCacheRoot);
return status;
}
//check if bundle location is newer than current revision
if (status == CELIX_SUCCESS) {
extractBundle = celix_framework_utils_isBundleUrlNewerThan(archive->fw, bundleUrl, &revisionMod);
}
if (!extractBundle) {
fw_log(archive->fw->logger, CELIX_LOG_LEVEL_TRACE, "Bundle archive %s is up to date, no need to extract bundle.", bundleUrl);
return status;
}
/*
* Note always remove the current revision dir. This is needed to remove files that are not present
* in the new bundle zip, but it seems this is also needed to ensure that the lib files get a new inode.
* If dlopen/dlsym is used with newer files, but with the same inode already used in dlopen/dlsym this leads to
* segfaults.
*/
status = celix_bundleArchive_removeResourceCache(archive);
if (status != CELIX_SUCCESS) {
return status;
}
status = celix_framework_utils_extractBundle(archive->fw, bundleUrl, archive->resourceCacheRoot);
if (status != CELIX_SUCCESS) {
fw_log(archive->fw->logger, CELIX_LOG_LEVEL_ERROR, "Failed to initialize archive. Failed to extract bundle zip to revision directory.");
return status;
}
return status;
}
/**
* Initialize archive by creating the bundle cache directory, optionally extracting the bundle from the bundle file,
* reading the bundle state properties, reading the bundle manifest and updating the bundle state properties.
*/
static celix_status_t celix_bundleArchive_createCacheDirectory(celix_bundle_archive_t* archive, celix_bundle_manifest_t** manifestOut) {
*manifestOut = NULL;
if (celix_utils_fileExists(archive->archiveRoot)) {
fw_log(archive->fw->logger, CELIX_LOG_LEVEL_TRACE, "Bundle archive root for bundle id %li already exists.",
archive->id);
}
//create archive root
const char* errorStr = NULL;
celix_status_t status = celix_utils_createDirectory(archive->archiveRoot, false, &errorStr);
if (status != CELIX_SUCCESS) {
fw_log(archive->fw->logger, CELIX_LOG_LEVEL_ERROR, "Failed to initialize archive. Failed to create bundle root archive dir: %s", errorStr);
return status;
}
//create store directory
status = celix_utils_createDirectory(archive->storeRoot, false, &errorStr);
if (status != CELIX_SUCCESS) {
fw_log(archive->fw->logger, CELIX_LOG_LEVEL_ERROR, "Failed to initialize archive. Failed to create bundle store dir: %s", errorStr);
return status;
}
//extract bundle zip to revision directory
status = celix_bundleArchive_extractBundle(archive, archive->location);
if (status != CELIX_SUCCESS) {
fw_log(archive->fw->logger, CELIX_LOG_LEVEL_ERROR, "Failed to initialize archive. Failed to extract bundle.");
return status;
}
//read manifest from extracted bundle zip
celix_autoptr(celix_bundle_manifest_t) manifest = NULL;
char pathBuffer[512];
char* manifestPath = celix_utils_writeOrCreateString(pathBuffer, sizeof(pathBuffer), "%s/%s", archive->resourceCacheRoot, CELIX_BUNDLE_MANIFEST_REL_PATH);
if (manifestPath == NULL) {
fw_log(archive->fw->logger, CELIX_LOG_LEVEL_ERROR, "Failed to initialize archive. Failed to create manifest path.");
return CELIX_ENOMEM;
}
status = celix_bundleManifest_createFromFile(manifestPath, &manifest);
celix_utils_freeStringIfNotEqual(pathBuffer, manifestPath);
if (status != CELIX_SUCCESS) {
celix_framework_logTssErrors(archive->fw->logger, CELIX_LOG_LEVEL_ERROR);
fw_log(archive->fw->logger, CELIX_LOG_LEVEL_ERROR, "Failed to initialize archive. Cannot read manifest.");
return status;
}
*manifestOut = celix_steal_ptr(manifest);
return status;
}
celix_status_t celix_bundleArchive_create(celix_framework_t* fw, const char *archiveRoot, long id, const char *location, celix_bundle_archive_t** bundle_archive) {
celix_status_t status = CELIX_SUCCESS;
celix_autoptr(celix_bundle_archive_t) archive = calloc(1, sizeof(*archive));
bool isFrameworkBundle = id == CELIX_FRAMEWORK_BUNDLE_ID;
if (!archive) {
fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Could not allocate memory for the bundle archive.");
return ENOMEM;
}
archive->fw = fw;
archive->id = id;
if (isFrameworkBundle) {
archive->resourceCacheRoot = getcwd(NULL, 0);
archive->storeRoot = getcwd(NULL, 0);
if (archive->resourceCacheRoot == NULL || archive->storeRoot == NULL) {
status = CELIX_ENOMEM;
}
} else {
status = CELIX_ENOMEM;
do {
archive->location = celix_utils_strdup(location);
if (archive->location == NULL) {
break;
}
archive->archiveRoot = celix_utils_strdup(archiveRoot);
if (archive->archiveRoot == NULL) {
break;
}
if (asprintf(&archive->savedBundleStatePropertiesPath, "%s/%s", archiveRoot,
CELIX_BUNDLE_ARCHIVE_STATE_PROPERTIES_FILE_NAME) < 0) {
break;
}
if (asprintf(&archive->storeRoot, "%s/%s", archive->archiveRoot, CELIX_BUNDLE_ARCHIVE_STORE_DIRECTORY_NAME) < 0) {
break;
}
if (asprintf(&archive->resourceCacheRoot, "%s/%s", archive->archiveRoot, CELIX_BUNDLE_ARCHIVE_RESOURCE_CACHE_NAME) < 0) {
break;
}
status = CELIX_SUCCESS;
} while (0);
}
if (status != CELIX_SUCCESS) {
fw_log(fw->logger, CELIX_LOG_LEVEL_ERROR, "Failed to create bundle archive; Out of memory.");
return status;
}
if (isFrameworkBundle) {
status = celix_bundleManifest_createFrameworkManifest(&archive->manifest);
} else {
status = celix_bundleArchive_createCacheDirectory(archive, &archive->manifest);
}
if (status != CELIX_SUCCESS) {
fw_logCode(fw->logger, CELIX_LOG_LEVEL_ERROR, status, "Failed to create bundle archive.");
if (!isFrameworkBundle) {
celix_utils_deleteDirectory(archive->archiveRoot, NULL);
}
return status;
}
if (!isFrameworkBundle) {
status = celix_bundleArchive_storeBundleStateProperties(archive);
if (status != CELIX_SUCCESS) {
fw_logCode(fw->logger, CELIX_LOG_LEVEL_ERROR, status, "Failed to store bundle state properties.");
celix_utils_deleteDirectory(archive->archiveRoot, NULL);
return status;
}
}
archive->cacheValid = true;
archive->valid = true;
*bundle_archive = celix_steal_ptr(archive);
return CELIX_SUCCESS;
}
void celix_bundleArchive_destroy(celix_bundle_archive_t* archive) {
if (archive != NULL) {
free(archive->location);
free(archive->savedBundleStatePropertiesPath);
free(archive->archiveRoot);
free(archive->resourceCacheRoot);
free(archive->storeRoot);
celix_bundleManifest_destroy(archive->manifest);
free(archive);
}
}
long celix_bundleArchive_getId(const celix_bundle_archive_t* archive) {
return archive->id;
}
celix_bundle_manifest_t* celix_bundleArchive_getManifest(const celix_bundle_archive_t* archive) {
return archive->manifest;
}
const char* celix_bundleArchive_getSymbolicName(const celix_bundle_archive_t* archive) {
return celix_bundleManifest_getBundleSymbolicName(celix_bundleArchive_getManifest(archive));
}
const char* celix_bundleArchive_getLocation(const celix_bundle_archive_t* archive) {
return archive->location;
}
const char* celix_bundleArchive_getArchiveRoot(const celix_bundle_archive_t* archive) {
return archive->archiveRoot;
}
celix_status_t celix_bundleArchive_getLastModified(const celix_bundle_archive_t* archive, struct timespec* lastModified) {
return celix_utils_getLastModified(archive->resourceCacheRoot, lastModified);
}
const char* celix_bundleArchive_getPersistentStoreRoot(const celix_bundle_archive_t* archive) {
return archive->storeRoot;
}
const char* celix_bundleArchive_getCurrentRevisionRoot(const celix_bundle_archive_t* archive) {
return archive->resourceCacheRoot;
}
void celix_bundleArchive_invalidate(celix_bundle_archive_t* archive) {
archive->valid = false;
archive->cacheValid = false;
}
void celix_bundleArchive_invalidateCache(celix_bundle_archive_t* archive) {
archive->cacheValid = false;
}
bool celix_bundleArchive_isCacheValid(const celix_bundle_archive_t* archive) {
return archive->cacheValid;
}
void celix_bundleArchive_removeInvalidDirs(celix_bundle_archive_t* archive) {
if (archive->id == CELIX_FRAMEWORK_BUNDLE_ID) {
return;
}
if (!archive->valid) {
celix_status_t status = CELIX_SUCCESS;
const char* err = NULL;
status = celix_utils_deleteDirectory(archive->archiveRoot, &err);
framework_logIfError(archive->fw->logger, status, NULL, "Failed to remove invalid archive root '%s': %s", archive->archiveRoot, err);
} else if (!archive->cacheValid){
(void)celix_bundleArchive_removeResourceCache(archive);
}
}