blob: 692be8d61c77d20010144af527f515c0c49513a9 [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_file_utils.h"
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <dirent.h>
#include <fts.h>
#include <stdlib.h>
#include <zip.h>
#include <sys/time.h>
#include <unistd.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.";
static const char * const ERROR_QUERYING_FILE_ZIP = "Error querying file in zip.";
static const char * const ERROR_OPENING_FILE_ZIP = "Error opening file in zip.";
static const char * const ERROR_READING_FILE_ZIP = "Error reading file in zip.";
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);
}
// https://gist.github.com/JonathonReinhart/8c0d90191c38af2dcadb102c4e202950
/* Make a directory; already existing dir okay */
static int maybe_mkdir(const char* path, mode_t mode)
{
struct stat st;
errno = 0;
/* Try to make the directory */
if (mkdir(path, mode) == 0) {
return 0;
}
/* If it fails for any reason but EEXIST, fail */
if (errno != EEXIST) {
return -1;
}
/* Check if the existing path is a directory */
if (stat(path, &st) != 0) {
return -1;
}
/* If not, fail with ENOTDIR */
if (!S_ISDIR(st.st_mode)) {
errno = ENOTDIR;
return -1;
}
errno = 0;
return 0;
}
celix_status_t celix_utils_createDirectory(const char* path, bool failIfPresent, const char** errorOut) {
const char *dummyErrorOut = NULL;
if (*path == '\0') {
//empty path, nothing to do
return CELIX_SUCCESS;
}
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;
char *_path = NULL;
char *p;
int result = -1;
errno = 0;
/* Copy string so it's mutable */
_path = celix_utils_strdup(path);
if (_path == NULL) {
goto out;
}
/* Iterate the string */
for (p = _path + 1; *p; p++) {
if (*p == '/') {
/* Temporarily truncate */
*p = '\0';
if (maybe_mkdir(_path, S_IRWXU) != 0) {
goto out;
}
*p = '/';
}
}
if (maybe_mkdir(_path, S_IRWXU) != 0) {
goto out;
}
result = 0;
out:
free(_path);
if (result != 0) {
status = CELIX_ERROR_MAKE(CELIX_FACILITY_CERRNO,errno);
*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
celix_status_t status = CELIX_SUCCESS;
char *paths[] = { (char*)path, NULL };
FTS *fts = fts_open(paths, FTS_PHYSICAL | FTS_XDEV | FTS_NOCHDIR | FTS_NOSTAT, NULL);
if (fts == NULL) {
goto out;
}
FTSENT *ent = NULL;
while ((ent = fts_read(fts)) != NULL) {
switch (ent->fts_info) {
case FTS_DP:
case FTS_NSOK:
case FTS_SL:
case FTS_SLNONE:
if (remove(ent->fts_accpath) != 0) {
goto out;
}
break;
case FTS_DNR:
case FTS_ERR:
errno = ent->fts_errno;
goto out;
default:
break;
}
}
out:
if (errno != 0) {
status = CELIX_ERROR_MAKE(CELIX_FACILITY_CERRNO,errno);
*errorOut = strerror(errno);
}
if (fts != NULL) {
fts_close(fts); // it may change 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;
status = celix_utils_createDirectory(extractToDir, false, errorOut);
if (status != CELIX_SUCCESS) {
return status;
}
for (zip_int64_t i = 0; status == CELIX_SUCCESS && i < nrOfEntries; ++i) {
zip_stat_t st;
if(zip_stat_index(zip, i, 0, &st) == -1) {
status = CELIX_ERROR_MAKE(CELIX_FACILITY_ZIP, zip_error_code_zip(zip_get_error(zip)));
*errorOut = ERROR_QUERYING_FILE_ZIP;
continue;
}
char* path = celix_utils_writeOrCreateString(buf, bufSize, "%s/%s", extractToDir, st.name);
if (path == NULL) {
status = CELIX_ERROR_MAKE(CELIX_FACILITY_CERRNO,errno);
*errorOut = strerror(errno);
continue;
}
if (st.name[strlen(st.name) - 1] == '/') {
status = celix_utils_createDirectory(path, false, errorOut);
goto clean_string_buf;
}
FILE* f = fopen(path, "w+");
if (f == NULL) {
status = CELIX_ERROR_MAKE(CELIX_FACILITY_CERRNO,errno);
*errorOut = strerror(errno);
goto clean_string_buf;
}
zip_file_t *zf = zip_fopen_index(zip, i, 0);
if (!zf) {
status = CELIX_ERROR_MAKE(CELIX_FACILITY_ZIP, zip_error_code_zip(zip_get_error(zip)));
*errorOut = ERROR_OPENING_FILE_ZIP;
goto close_output_file;
}
zip_int64_t read = zip_fread(zf, buf, bufSize);
while (read > 0) {
if (fwrite(buf, read, 1, f) == 0) {
status = CELIX_ERROR_MAKE(CELIX_FACILITY_CERRNO,errno);
*errorOut = strerror(errno);
goto close_zip_file;
}
read = zip_fread(zf, buf, bufSize);
}
if (read < 0) {
status = CELIX_ERROR_MAKE(CELIX_FACILITY_ZIP, zip_error_code_zip(zip_file_get_error(zf)));
*errorOut = ERROR_READING_FILE_ZIP;
}
close_zip_file:
zip_fclose(zf);
close_output_file:
fclose(f);
clean_string_buf:
celix_utils_freeStringIfNotEqual(buf, 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 an allocated string buf).
status = CELIX_ERROR_MAKE(CELIX_FACILITY_ZIP, error);
*errorOut = ERROR_OPENING_ZIP;
}
return status;
}
celix_status_t celix_utils_extractZipData(const void *zipData, size_t zipDataSize, 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, zipDataSize, 0, &zipError);
zip_t* zip = NULL;
if (source) {
zip = zip_open_from_source(source, 0, &zipError);
if (zip) {
// so that we can call zip_source_free no matter whether zip_open_from_source succeeded or not
zip_source_keep(source);
status = celix_utils_extractZipInternal(zip, extractToDir, errorOut);
}
}
if (source == NULL || zip == NULL) {
status = CELIX_ERROR_MAKE(CELIX_FACILITY_ZIP, zip_error_code_zip(&zipError));
*errorOut = ERROR_OPENING_ZIP;
}
if (zip != NULL) {
zip_close(zip);
}
if (source != NULL) {
zip_source_free(source);
}
return status;
}
celix_status_t celix_utils_getLastModified(const char* path, struct timespec* lastModified) {
celix_status_t status = CELIX_SUCCESS;
struct stat st;
if (stat(path, &st) == 0) {
#ifdef __APPLE__
*lastModified = st.st_mtimespec;
#else
*lastModified = st.st_mtim;
#endif
} else {
lastModified->tv_sec = 0;
lastModified->tv_nsec = 0;
status = CELIX_FILE_IO_EXCEPTION;
}
return status;
}
celix_status_t celix_utils_touch(const char* path) {
celix_status_t status = CELIX_SUCCESS;
int rc = utimes(path, NULL);
if (rc != 0) {
status = CELIX_FILE_IO_EXCEPTION;
}
return status;
}