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;
+}
+
+