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:05:37 UTC

[celix] 01/01: Adds zip support to the celix utils library.

This is an automated email from the ASF dual-hosted git repository.

pnoltes pushed a commit to branch feature/updated_bundle_handling
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;
+}
+
+