You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@celix.apache.org by pn...@apache.org on 2021/02/26 19:13:44 UTC
[celix] branch feature/utils_zip_extract created (now 409a828)
This is an automated email from the ASF dual-hosted git repository.
pnoltes pushed a change to branch feature/utils_zip_extract
in repository https://gitbox.apache.org/repos/asf/celix.git.
at 409a828 Adds zip support to the celix utils library.
This branch includes the following new commits:
new 409a828 Adds zip support to the celix utils library.
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
[celix] 01/01: Adds zip support to the celix utils library.
Posted by pn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
pnoltes pushed a commit to branch feature/utils_zip_extract
in repository https://gitbox.apache.org/repos/asf/celix.git
commit 409a82809bed73f301639814bd040160d029fb77
Author: Pepijn Noltes <pe...@gmail.com>
AuthorDate: Fri Feb 26 20:05:26 2021 +0100
Adds zip support to the celix utils library.
Adds support for extraction zip files and (in memory_ zip data.
This is realized with use of libzip.
---
.github/workflows/coverage.yml | 1 +
.github/workflows/coverity-scan.yml | 3 +-
.github/workflows/macos-nightly.yml | 2 +-
.github/workflows/macos.yml | 2 +-
.github/workflows/ubuntu-nightly.yml | 3 +-
.github/workflows/ubuntu.yml | 3 +-
cmake/Modules/FindLIBZIP.cmake | 51 ++++++
libs/utils/CMakeLists.txt | 5 +
libs/utils/gtest/CMakeLists.txt | 31 ++++
libs/utils/gtest/src/FileUtilsTestSuite.cc | 194 ++++++++++++++++++++
libs/utils/include/celix_file_utils.h | 88 +++++++++
libs/utils/src/celix_file_utils.c | 280 +++++++++++++++++++++++++++++
12 files changed, 658 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 84318d8..157ddaf 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -23,6 +23,7 @@ jobs:
libxml2-dev \
libczmq-dev \
libcpputest-dev \
+ libzip-dev \
lcov
- name: Build
env:
diff --git a/.github/workflows/coverity-scan.yml b/.github/workflows/coverity-scan.yml
index 109ace2..460b897 100644
--- a/.github/workflows/coverity-scan.yml
+++ b/.github/workflows/coverity-scan.yml
@@ -23,7 +23,8 @@ jobs:
libffi-dev \
libxml2-dev \
libczmq-dev \
- libcpputest-dev
+ libcpputest-dev \
+ libzip-dev
- name: Download Coverity Build Tool
run: |
mkdir -p build/cov-analysis-linux64
diff --git a/.github/workflows/macos-nightly.yml b/.github/workflows/macos-nightly.yml
index 98ec7ab..c67c760 100644
--- a/.github/workflows/macos-nightly.yml
+++ b/.github/workflows/macos-nightly.yml
@@ -19,7 +19,7 @@ jobs:
- name: Install dependencies
run: |
brew update
- brew install lcov zeromq czmq openssl cpputest jansson
+ brew install lcov zeromq czmq openssl cpputest jansson libzip
brew unlink openssl && brew link openssl --force
- name: Build
env:
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 363d47a..c92c3e8 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -17,7 +17,7 @@ jobs:
- name: Install dependencies
run: |
brew update
- brew install lcov zeromq czmq openssl cpputest jansson
+ brew install lcov zeromq czmq openssl cpputest jansson libzip
brew unlink openssl && brew link openssl --force
- name: Build
env:
diff --git a/.github/workflows/ubuntu-nightly.yml b/.github/workflows/ubuntu-nightly.yml
index 709d111..57dbd09 100644
--- a/.github/workflows/ubuntu-nightly.yml
+++ b/.github/workflows/ubuntu-nightly.yml
@@ -47,7 +47,8 @@ jobs:
libffi-dev \
libxml2-dev \
libczmq-dev \
- libcpputest-dev
+ libcpputest-dev \
+ libzip-dev
- name: Build
env:
CC: ${{ matrix.compiler }}
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml
index 3ace04c..bb6aadc 100644
--- a/.github/workflows/ubuntu.yml
+++ b/.github/workflows/ubuntu.yml
@@ -45,7 +45,8 @@ jobs:
libffi-dev \
libxml2-dev \
libczmq-dev \
- libcpputest-dev
+ libcpputest-dev \
+ libzip-dev
- name: Build
env:
CC: ${{ matrix.compiler }}
diff --git a/cmake/Modules/FindLIBZIP.cmake b/cmake/Modules/FindLIBZIP.cmake
new file mode 100644
index 0000000..fc8742f
--- /dev/null
+++ b/cmake/Modules/FindLIBZIP.cmake
@@ -0,0 +1,51 @@
+# 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.
+
+
+# - Try to find libzip
+#
+# Once done this will define
+# LIBZIP_FOUND - System has libffi
+# libzip::libzip target (if found)
+
+mark_as_advanced(GNUTLS_INCLUDE_DIR GNUTLS_LIBRARY)
+
+find_library(LIBZIP_LIBRARY NAMES zip
+ PATHS $ENV{LIBZIP_DIR} ${LIBZIP_DIR} /usr /usr/local /opt/local
+ PATH_SUFFIXES lib lib64 x86_64-linux-gnu lib/x86_64-linux-gnu
+ )
+
+find_path(LIBZIP_INCLUDE_DIR zip.h
+ PATHS $ENV{LIBZIP_DIR} ${LIBZIP_DIR} /usr /usr/local /opt/local
+ PATH_SUFFIXES include include/ffi include/x86_64-linux-gnu x86_64-linux-gnu
+ )
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set LIBZIP_FOUND to TRUE
+# if all listed variables are TRUE
+find_package_handle_standard_args(LIBZIP DEFAULT_MSG
+ LIBZIP_LIBRARY LIBZIP_INCLUDE_DIR)
+
+if(LIBZIP_FOUND)
+ add_library(libzip::libzip IMPORTED STATIC GLOBAL)
+ set_target_properties(libzip::libzip PROPERTIES
+ IMPORTED_LOCATION "${LIBZIP_LIBRARY}"
+ INTERFACE_INCLUDE_DIRECTORIES "${LIBZIP_INCLUDE_DIR}"
+ )
+endif()
+
+unset(LIBZIP_LIBRARY)
+unset(LIBZIP_INCLUDE_DIR)
\ No newline at end of file
diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt
index ffb247b..2835203 100644
--- a/libs/utils/CMakeLists.txt
+++ b/libs/utils/CMakeLists.txt
@@ -15,6 +15,8 @@
# specific language governing permissions and limitations
# under the License.
+find_package(LIBZIP REQUIRED)
+
set(MEMSTREAM_SOURCES )
set(MEMSTREAM_INCLUDES )
include(CheckFunctionExists)
@@ -39,12 +41,15 @@ add_library(utils SHARED
src/ip_utils.c
src/filter.c
src/celix_log_utils.c
+ src/celix_file_utils.c
${MEMSTREAM_SOURCES}
)
set_target_properties(utils PROPERTIES OUTPUT_NAME "celix_utils")
+target_link_libraries(utils PRIVATE libzip::libzip)
if (NOT OPEN_MEMSTREAM_EXISTS)
target_compile_definitions(utils PUBLIC -DNO_MEMSTREAM_AVAILABLE)
+ target_compile_definitions(utils PUBLIC -DNO_MEMSTREAM_AVAILABLE)
endif ()
if (ANDROID)
diff --git a/libs/utils/gtest/CMakeLists.txt b/libs/utils/gtest/CMakeLists.txt
index 566ce56..4b36c0d 100644
--- a/libs/utils/gtest/CMakeLists.txt
+++ b/libs/utils/gtest/CMakeLists.txt
@@ -20,11 +20,42 @@ add_executable(test_utils
src/LogUtilsTestSuite.cc
src/VersionRangeTestSuite.cc
src/TimeUtilsTestSuite.cc
+ src/FileUtilsTestSuite.cc
)
target_link_libraries(test_utils PRIVATE Celix::utils GTest::gtest GTest::gtest_main)
target_include_directories(test_utils PRIVATE ../src) #for version_private (needs refactoring of test)
+####### generating zip file used for testing ##########################################################################
+file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/zip_content/top.properties" CONTENT "level=1\n")
+file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/zip_content/subdir/sub.properties" CONTENT "level=2\n")
+set(TEST_ZIP_FILE "${CMAKE_CURRENT_BINARY_DIR}/test.zip")
+if(ZIP_COMMAND)
+ add_custom_command(OUTPUT ${TEST_ZIP_FILE}
+ COMMAND ${ZIP_COMMAND} -rq ${TEST_ZIP_FILE} *
+ COMMENT "Creating ${TEST_ZIP_FILE} for testing"
+ DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/zip_content/top.properties ${CMAKE_CURRENT_BINARY_DIR}/zip_content/subdir/sub.properties
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/zip_content
+ )
+else()
+ add_custom_command(OUTPUT ${TEST_ZIP_FILE}
+ COMMAND ${JAR_COMMAND} -cf ${TEST_ZIP_FILE} -C ${CMAKE_CURRENT_BINARY_DIR}/zip_content .
+ COMMENT "Creating ${TEST_ZIP_FILE} for testing"
+ DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/zip_content/top.properties ${CMAKE_CURRENT_BINARY_DIR}/zip_content/subdir/sub.properties
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+endif()
+add_custom_target(test_utils_resources DEPENDS ${TEST_ZIP_FILE})
+add_dependencies(test_utils test_utils_resources)
+target_compile_definitions(test_utils PRIVATE -DTEST_ZIP_LOCATION=\"${TEST_ZIP_FILE}\")
+
+####### linkig zip against test executable to text extract zip from data ###############################################
+if (UNIX AND NOT APPLE)
+ target_link_libraries(test_utils PRIVATE -Wl,--format=binary -Wl,"test.zip" -Wl,--format=default)
+else ()
+ target_link_libraries(test_utils PRIVATE -Wl,-sectcreate -Wl,resources -Wl,test_zip -Wl,${TEST_ZIP_FILE})
+endif ()
+
add_test(NAME test_utils COMMAND test_utils)
setup_target_for_coverage(test_utils SCAN_DIR ..)
diff --git a/libs/utils/gtest/src/FileUtilsTestSuite.cc b/libs/utils/gtest/src/FileUtilsTestSuite.cc
new file mode 100644
index 0000000..d16b77f
--- /dev/null
+++ b/libs/utils/gtest/src/FileUtilsTestSuite.cc
@@ -0,0 +1,194 @@
+/*
+ * 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 "celix_file_utils.h"
+#include "celix_properties.h"
+
+class FileUtilsTestSuite : public ::testing::Test {};
+
+TEST_F(FileUtilsTestSuite, TestFileAndDirectoryExists) {
+ EXPECT_TRUE(celix_utils_fileExists("/etc"));
+ EXPECT_TRUE(celix_utils_fileExists("/etc/shadow"));
+ EXPECT_FALSE(celix_utils_fileExists("/file-does-not-exists/check"));
+
+ EXPECT_TRUE(celix_utils_directoryExists("/etc"));
+ EXPECT_FALSE(celix_utils_directoryExists("/etc/shadow"));
+ EXPECT_FALSE(celix_utils_directoryExists("/file-does-not-exists/check"));
+}
+
+TEST_F(FileUtilsTestSuite, CreateAndDeleteDirectory) {
+ const char* testDir = "celix_file_utils_test/directory";
+ const char* testDir2 = "celix_file_utils_test/directory2/";
+ const char* testDir3 = "";//invalid
+ celix_utils_deleteDirectory(testDir, nullptr);
+ celix_utils_deleteDirectory(testDir2, nullptr);
+
+ //I can create a new directory.
+ const char* error = nullptr;
+ EXPECT_FALSE(celix_utils_directoryExists(testDir));
+ auto status = celix_utils_createDirectory(testDir, true, &error);
+ EXPECT_EQ(status, CELIX_SUCCESS);
+ EXPECT_EQ(error, nullptr);
+ EXPECT_TRUE(celix_utils_fileExists(testDir));
+ EXPECT_TRUE(celix_utils_directoryExists(testDir));
+
+
+ //Creating a directory if it already exists fails when using failIfPresent=true.
+ status = celix_utils_createDirectory(testDir, true, &error);
+ EXPECT_NE(status, CELIX_SUCCESS);
+ EXPECT_NE(error, nullptr);
+ status = celix_utils_createDirectory(testDir, true, nullptr);
+ EXPECT_NE(status, CELIX_SUCCESS);
+
+ //Creating a directory if it already exists succeeds when using failIfPresent=false.
+ status = celix_utils_createDirectory(testDir, false, &error);
+ EXPECT_EQ(status, CELIX_SUCCESS);
+ EXPECT_EQ(error, nullptr);
+
+ //Can I delete dir and when deleting a non existing dir no error is given
+ status = celix_utils_deleteDirectory(testDir, &error);
+ EXPECT_EQ(status, CELIX_SUCCESS);
+ EXPECT_EQ(error, nullptr);
+ status = celix_utils_deleteDirectory(testDir, nullptr);
+ EXPECT_EQ(status, CELIX_SUCCESS);
+
+ //Can i create and delete a dir with ends with a /
+ status = celix_utils_createDirectory(testDir2, true, nullptr);
+ EXPECT_EQ(status, CELIX_SUCCESS);
+ EXPECT_TRUE(celix_utils_fileExists(testDir2));
+ status = celix_utils_deleteDirectory(testDir2, nullptr);
+ EXPECT_EQ(status, CELIX_SUCCESS);
+
+ status = celix_utils_createDirectory(testDir3, true, nullptr);
+ EXPECT_EQ(status, CELIX_SUCCESS);
+ status = celix_utils_deleteDirectory(testDir3, nullptr);
+ EXPECT_EQ(status, CELIX_SUCCESS);
+}
+
+TEST_F(FileUtilsTestSuite, ExtractZipFileTest) {
+ std::cout << "Using test zip location " << TEST_ZIP_LOCATION << std::endl;
+ const char* extractLocation = "extract_location";
+ const char* file1 = "extract_location/top.properties";
+ const char* file2 = "extract_location/subdir/sub.properties";
+ celix_utils_deleteDirectory(extractLocation, nullptr);
+
+ //Given a test zip file, I can extract this to a provided location and the correct files are extracted
+ EXPECT_FALSE(celix_utils_fileExists(extractLocation));
+ auto status = celix_utils_extractZipFile(TEST_ZIP_LOCATION, extractLocation, nullptr);
+ EXPECT_EQ(status, CELIX_SUCCESS);
+
+ EXPECT_TRUE(celix_utils_fileExists(file1));
+ auto* props = celix_properties_load(file1);
+ EXPECT_NE(props, nullptr);
+ EXPECT_EQ(celix_properties_getAsLong(props, "level", 0), 1);
+ celix_properties_destroy(props);
+
+ EXPECT_TRUE(celix_utils_fileExists(file2));
+ props = celix_properties_load(file2);
+ EXPECT_NE(props, nullptr);
+ EXPECT_EQ(celix_properties_getAsLong(props, "level", 0), 2);
+ celix_properties_destroy(props);
+
+ //Given a incorrect path extractZipFile returns a error
+ const char* invalidPath = "does-not-exists.zip";
+ const char* error = nullptr;
+ EXPECT_FALSE(celix_utils_fileExists(invalidPath));
+ status = celix_utils_extractZipFile(invalidPath, extractLocation, &error);
+ EXPECT_NE(status, CELIX_SUCCESS);
+ EXPECT_NE(error, nullptr);
+
+ //Given an non zip file to extractZipFile fails
+ const char* invalidZip = "/etc/shadow";
+ EXPECT_TRUE(celix_utils_fileExists(invalidZip));
+ status = celix_utils_extractZipFile(invalidZip, extractLocation, &error);
+ EXPECT_NE(status, CELIX_SUCCESS);
+ EXPECT_NE(error, nullptr);
+}
+
+#ifdef __APPLE__
+//TODO
+TEST_F(FileUtilsTestSuite, ExtractZipDataTest) {
+ static uint8_t *zipContent = nullptr;//get library info using a addr in the lib (the bundle register function)
+
+ Dl_info info;
+ dladdr((void*)celix_utils_fileExists, &info);
+
+ //get mach header from Dl_info
+ auto *header = static_cast<struct mach_header_64*>(info.dli_fbase);
+
+ //get section from mach header based on the seg and sect name
+ const struct section_64 *sect = getsectbynamefromheader_64(header, "resources", "test_zip");
+
+ //NOTE reading directly form the sect->addr is not possible (BAD_ACCESS), so copy sect part from dylib file.
+
+ //alloc buffer to store resources zip
+ zipContent = (uint8_t*)malloc(sect->size);
+
+ //read from dylib. note that the dylib location is in the Dl_info struct.
+ errno = 0;
+ FILE *dylib = fopen(info.dli_fname, "r");
+ size_t read = 0;
+ if (dylib != nullptr) {
+ fseek(dylib, sect->offset, SEEK_SET);
+ read = fread(zipContent, 1, sect->size, dylib);
+ }
+ if (dylib == nullptr || read != sect->size) {
+ "Error reading resources from dylib."
+ free(zipContent);
+ zipContent = nullptr;
+ resourcesLen = 0;
+ }
+ if (dylib != nullptr) {
+ fclose(dylib);
+ }
+
+ //TODO extract zip
+
+ free(zipContent);
+}
+#else
+extern const uint8_t test_data_start[] asm("_binary_test_zip_start");
+extern const uint8_t test_data_end[] asm("_binary_test_zip_end");
+
+TEST_F(FileUtilsTestSuite, ExtractZipDataTest) {
+ const char* extractLocation = "extract_location";
+ const char* file1 = "extract_location/top.properties";
+ const char* file2 = "extract_location/subdir/sub.properties";
+ celix_utils_deleteDirectory(extractLocation, nullptr);
+
+ //Given test zip data, I can extract this to a provided location and the correct files are extracted
+ EXPECT_FALSE(celix_utils_fileExists(extractLocation));
+ auto status = celix_utils_extractZipData(test_data_start, test_data_end - test_data_start, extractLocation, nullptr);
+ EXPECT_EQ(status, CELIX_SUCCESS);
+
+ EXPECT_TRUE(celix_utils_fileExists(file1));
+ auto* props = celix_properties_load(file1);
+ EXPECT_NE(props, nullptr);
+ EXPECT_EQ(celix_properties_getAsLong(props, "level", 0), 1);
+ celix_properties_destroy(props);
+
+ EXPECT_TRUE(celix_utils_fileExists(file2));
+ props = celix_properties_load(file2);
+ EXPECT_NE(props, nullptr);
+ EXPECT_EQ(celix_properties_getAsLong(props, "level", 0), 2);
+ celix_properties_destroy(props);
+}
+#endif
diff --git a/libs/utils/include/celix_file_utils.h b/libs/utils/include/celix_file_utils.h
new file mode 100644
index 0000000..4aad789
--- /dev/null
+++ b/libs/utils/include/celix_file_utils.h
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+#ifndef CELIX_FILE_UTILS_H
+#define CELIX_FILE_UTILS_H
+
+#include <stdbool.h>
+#include "celix_errno.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Returns whether a file at path exists.
+ */
+bool celix_utils_fileExists(const char* path);
+
+/**
+ * @brief Returns whether a file at path exists and is a directory.
+ */
+bool celix_utils_directoryExists(const char* path);
+
+/**
+ * @brief Create dir and if needed subdirs.
+ * @param path The directory to create.
+ * @param failIfPresent Whether to fail if the directory already exists.
+ * @param errorOut An optional error output argument. If an error occurs this will point to a (static) error message.
+ * @return CELIX_SUCCESS if the directory was successfully created.
+ */
+celix_status_t celix_utils_createDirectory(const char* path, bool failIfPresent, const char** errorOut);
+
+/**
+ * @brief Recursively delete the directory at path.
+ *
+ * @param path A directory path. Directory should exist.
+ * @param errorOut An optional error output argument. If an error occurs this will point to a (static) error message.
+ * @return CELIX_SUCCESS if the directory was found and successfully deleted.
+ */
+celix_status_t celix_utils_deleteDirectory(const char* path, const char** errorOut);
+
+/**
+ * @brief Extract the zip file to the target dir.
+ *
+ * Will create the targetDir if it does not already exist.
+ *
+ * @param zipPath The path to the zip file.
+ * @param extractToDir The path where the zip file will be extracted.
+ * @param errorOut An optional error output argument. If an error occurs this will point to a (static) error message.
+ * @return CELIX_SUCCESS if the zip file was extracted successfully.
+ */
+celix_status_t celix_utils_extractZipFile(const char* zipPath, const char* extractToDir, const char** errorOut);
+
+
+/**
+ * @brief Extract the zip data to the target dir.
+ *
+ * Will create the targetDir if it does not already exist.
+ *
+ * @param zipData pointer to the beginning of the zip data
+ * @param zipDataLen length of the zip data.
+ * @param extractToDir The path where the zip data will be extracted.
+ * @param errorOut An optional error output argument. If an error occurs this will point to a (static) error message.
+ * @return CELIX_SUCCESS if the zip data was extracted successfully.
+ */
+celix_status_t celix_utils_extractZipData(const void *zipData, size_t zipDataLen, const char* extractToDir, const char** errorOut);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //CELIX_FILE_UTILS_H
diff --git a/libs/utils/src/celix_file_utils.c b/libs/utils/src/celix_file_utils.c
new file mode 100644
index 0000000..aeafdb8
--- /dev/null
+++ b/libs/utils/src/celix_file_utils.c
@@ -0,0 +1,280 @@
+/**
+ *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_file_utils.h"
+
+#include <sys/stat.h>
+#include <string.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <zip.h>
+
+#include "celix_utils.h"
+
+static const char * const DIRECTORY_ALREADY_EXISTS_ERROR = "Directory already exists.";
+static const char * const FILE_ALREADY_EXISTS_AND_NOT_DIR_ERROR = "File already exists, but is not a directory.";
+static const char * const CANNOT_DELETE_DIRECTORY_PATH_IS_FILE = "Cannot delete directory; Path points to a file.";
+static const char * const ERROR_OPENING_ZIP = "Error opening zip file.";
+
+bool celix_utils_fileExists(const char* path) {
+ struct stat st;
+ return stat(path, &st) == 0;
+}
+
+bool celix_utils_directoryExists(const char* path) {
+ struct stat st;
+ return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
+}
+
+celix_status_t celix_utils_createDirectory(const char* path, bool failIfPresent, const char** errorOut) {
+ const char *dummyErrorOut = NULL;
+ if (errorOut) {
+ //reset errorOut
+ *errorOut = NULL;
+ } else {
+ errorOut = &dummyErrorOut;
+ }
+
+ //check already exists
+ bool alreadyExists = celix_utils_fileExists(path);
+ if (alreadyExists) {
+ bool isDirectory = celix_utils_directoryExists(path);
+ if (isDirectory && failIfPresent) {
+ //directory already exists, but this should fail
+ *errorOut = DIRECTORY_ALREADY_EXISTS_ERROR;
+ return CELIX_FILE_IO_EXCEPTION;
+ } else if (isDirectory) {
+ //directory already exists, nothing to do
+ return CELIX_SUCCESS;
+ } else {
+ //file already exists, but is not an error
+ *errorOut = FILE_ALREADY_EXISTS_AND_NOT_DIR_ERROR;
+ return CELIX_FILE_IO_EXCEPTION;
+ }
+ }
+
+ celix_status_t status = CELIX_SUCCESS;
+
+ //loop over al finding of / and create intermediate dirs
+ for (char* slashAt = strchr(path, '/'); slashAt != NULL; slashAt = strchr(slashAt + 1, '/')) {
+ char* subPath = strndup(path, slashAt - path);
+ if (!celix_utils_directoryExists(subPath)) {
+ int rc = mkdir(subPath, S_IRWXU);
+ if (rc != 0) {
+ status = CELIX_FILE_IO_EXCEPTION;
+ *errorOut = strerror(errno);
+ }
+ }
+ free(subPath);
+ }
+
+ //create last part of the dir (expect when path ends with / then this is already done in the loop)
+ if (status == CELIX_SUCCESS && strlen(path) >0 && path[strlen(path)-1] != '/') {
+ int rc = mkdir(path, S_IRWXU);
+ if (rc != 0) {
+ status = CELIX_FILE_IO_EXCEPTION;
+ *errorOut = strerror(errno);
+ }
+ }
+ return status;
+}
+
+celix_status_t celix_utils_deleteDirectory(const char* path, const char** errorOut) {
+ const char *dummyErrorOut = NULL;
+ if (errorOut) {
+ //reset errorOut
+ *errorOut = NULL;
+ } else {
+ errorOut = &dummyErrorOut;
+ }
+
+ bool fileExists = celix_utils_fileExists(path);
+ bool dirExists = celix_utils_directoryExists(path);
+ if (!fileExists) {
+ //done
+ return CELIX_SUCCESS;
+ } else if (!dirExists) {
+ //found file not directory;
+ *errorOut = CANNOT_DELETE_DIRECTORY_PATH_IS_FILE;
+ return CELIX_FILE_IO_EXCEPTION;
+ }
+
+ //file exist and is directory
+ DIR *dir;
+ dir = opendir(path);
+ if (dir == NULL) {
+ *errorOut = strerror(errno);
+ return CELIX_FILE_IO_EXCEPTION;
+ }
+
+ celix_status_t status = CELIX_SUCCESS;
+ struct dirent* dent = NULL;
+ errno = 0;
+ dent = readdir(dir);
+ if (errno != 0) {
+ status = CELIX_FILE_IO_EXCEPTION;
+ *errorOut = strerror(errno);
+ }
+ while (status == CELIX_SUCCESS && dent != NULL) {
+ if ((strcmp((dent->d_name), ".") != 0) && (strcmp((dent->d_name), "..") != 0)) {
+ char* subdir = NULL;
+ asprintf(&subdir, "%s/%s", path, dent->d_name);
+
+ struct stat st;
+ if (stat(subdir, &st) == 0) {
+ if (S_ISDIR (st.st_mode)) {
+ status = celix_utils_deleteDirectory(subdir, errorOut);
+ } else {
+ if (remove(subdir) != 0) {
+ status = CELIX_FILE_IO_EXCEPTION;
+ *errorOut = strerror(errno);
+ }
+ }
+ }
+ free(subdir);
+ if (status != CELIX_SUCCESS) {
+ break;
+ }
+ }
+ errno = 0;
+ dent = readdir(dir);
+ if (errno != 0) {
+ status = CELIX_FILE_IO_EXCEPTION;
+ *errorOut = strerror(errno);
+ }
+ }
+ closedir(dir);
+
+ if (status == CELIX_SUCCESS) {
+ if (remove(path) != 0) {
+ status = CELIX_FILE_IO_EXCEPTION;
+ *errorOut = strerror(errno);
+ }
+ }
+ return status;
+}
+
+static celix_status_t celix_utils_extractZipInternal(zip_t *zip, const char* extractToDir, const char** errorOut) {
+ celix_status_t status = CELIX_SUCCESS;
+ zip_int64_t nrOfEntries = zip_get_num_entries(zip, 0);
+
+ //buffer used for read/write.
+ char buf[5120];
+ size_t bufSize = 5112;
+
+ for (zip_int64_t i = 0; status == CELIX_SUCCESS && i < nrOfEntries; ++i) {
+ zip_stat_t st;
+ zip_stat_index(zip, i, 0, &st);
+
+ char* path = NULL;
+ asprintf(&path, "%s/%s", extractToDir, st.name);
+ if (st.name[strlen(st.name) - 1] == '/') {
+ status = celix_utils_createDirectory(path, false, errorOut);
+ } else {
+ //file
+ zip_file_t *zf = zip_fopen_index(zip, i, 0);
+ if (!zf) {
+ status = CELIX_FILE_IO_EXCEPTION;
+ *errorOut = zip_strerror(zip);
+ } else {
+ FILE* f = fopen(path, "w+");
+ if (f) {
+ zip_int64_t read = zip_fread(zf, buf, bufSize);
+ while (read > 0) {
+ fwrite(buf, read, 1, f);
+ read = zip_fread(zf, buf, bufSize);
+ }
+ if (read < 0) {
+ status = CELIX_FILE_IO_EXCEPTION;
+ *errorOut = zip_file_strerror(zf);
+ }
+ fclose(f);
+ } else {
+ status = CELIX_FILE_IO_EXCEPTION;
+ *errorOut = strerror(errno);
+ }
+ zip_fclose(zf);
+ }
+ }
+ free(path);
+ }
+ return status;
+}
+
+celix_status_t celix_utils_extractZipFile(const char* zipPath, const char* extractToDir, const char** errorOut) {
+ const char *dummyErrorOut = NULL;
+ if (errorOut) {
+ //reset errorOut
+ *errorOut = NULL;
+ } else {
+ errorOut = &dummyErrorOut;
+ }
+
+ celix_status_t status = CELIX_SUCCESS;
+ int error;
+ zip_t* zip = zip_open(zipPath, ZIP_RDONLY, &error);
+
+ if (zip) {
+ status = celix_utils_extractZipInternal(zip, extractToDir, errorOut);
+ zip_close(zip);
+ } else {
+ //note libzip can give more info with zip_error_to_str if needed (but this requires a allocated string buf).
+ status = CELIX_FILE_IO_EXCEPTION;
+ *errorOut = ERROR_OPENING_ZIP;
+ }
+
+ return status;
+}
+
+celix_status_t celix_utils_extractZipData(const void *zipData, size_t zipDataLen, const char* extractToDir, const char** errorOut) {
+ const char *dummyErrorOut = NULL;
+ if (errorOut) {
+ //reset errorOut
+ *errorOut = NULL;
+ } else {
+ errorOut = &dummyErrorOut;
+ }
+
+ celix_status_t status = CELIX_SUCCESS;
+ zip_error_t zipError;
+ zip_source_t* source = zip_source_buffer_create(zipData, zipDataLen, 0, &zipError);
+ zip_t* zip = NULL;
+ if (source) {
+ zip = zip_open_from_source(source, 0, &zipError);
+ if (zip) {
+ status = celix_utils_extractZipInternal(zip, extractToDir, errorOut);
+ }
+ }
+
+ if (source == NULL || zip == NULL) {
+ status = CELIX_FILE_IO_EXCEPTION;
+ *errorOut = zip_error_strerror(&zipError);
+ }
+ if (zip != NULL) {
+ zip_close(zip);
+ }
+ if (source != NULL) {
+ zip_source_close(source);
+ }
+
+ return status;
+}
+
+