/* * Authors: * Aleksandr Lysikov <alexander.lysikov@demlabs.net> * DeM Labs Inc. https://demlabs.net * Kelvin Project https://gitlab.demlabs.net/cellframe * Copyright (c) 2017-2018 * All rights reserved. This file is part of DAP (Deus Applications Prototypes) the open source project DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. DAP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. */ #include <stdlib.h> #include <stdint.h> #include <errno.h> #include <fcntl.h> #include <stdbool.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #if (OS_TARGET == OS_MACOS) #include <stdio.h> #else #include <malloc.h> #endif #include <string.h> #include <sys/stat.h> #ifdef _WIN32 #include <windows.h> #include <io.h> #endif #include "dap_common.h" #include "dap_strfuncs.h" #include "dap_file_utils.h" /** * Check the directory path for unsupported symbols * * @string * @return true, if the directory path contains only ASCII symbols */ bool dap_valid_ascii_symbols(const char *a_string) { if(!a_string) return true; for(size_t i = 0; i < strlen(a_string); i++) { if((uint8_t) a_string[i] > 0x7f) return false; } return true; } /** * Check the file for exists * * @a_file_path filename pathname * @return true, if file exists */ bool dap_file_test(const char * a_file_path) { if(!a_file_path) return false; #ifdef _WIN32 int attr = GetFileAttributesA(a_file_path); if(attr != -1 && (attr & FILE_ATTRIBUTE_NORMAL)) return true; #else struct stat st; if (!stat(a_file_path, &st)) { if (S_ISREG(st.st_mode)) return true; } #endif return false; } /** * Check the directory for exists * * @dir_path directory pathname * @return true, if the file is a directory */ bool dap_dir_test(const char * a_dir_path) { if(!a_dir_path) return false; #ifdef _WIN32 int attr = GetFileAttributesA(a_dir_path); if(attr != -1 && (attr & FILE_ATTRIBUTE_DIRECTORY)) return true; #else struct stat st; if(!stat(a_dir_path, &st)) { if(S_ISDIR(st.st_mode)) return true; } #endif return false; } /** * Create a new directory with intermediate sub-directories * * @dir_path new directory pathname * @return 0, if the directory was created or already exist, else -1 */ int dap_mkdir_with_parents(const char *a_dir_path) { // validation of a pointer if(a_dir_path == NULL || a_dir_path[0] == '\0') { errno = EINVAL; return -1; } char path[strlen(a_dir_path) + 1]; memset(path, '\0', strlen(a_dir_path) + 1); memcpy(path, a_dir_path, strlen(a_dir_path)); char *p; // skip the root component if it is present, i.e. the "/" in Unix or "C:\" in Windows #ifdef _WIN32 if(((path[0] >= 'a' && path[0] <= 'z') || (path[0] >= 'A' && path[0] <= 'Z')) && (path[1] == ':') && DAP_IS_DIR_SEPARATOR(path[2])) { p = path + 3; } #else if (DAP_IS_DIR_SEPARATOR(path[0])) { p = path + 1; } #endif else p = path; do { while(*p && !DAP_IS_DIR_SEPARATOR(*p)) p++; if(!*p) p = NULL; else *p = '\0'; if(!dap_dir_test(path)) { #ifdef _WIN32 int result = mkdir(path); #else int result = mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); #endif if(result == -1) { errno = ENOTDIR; return -1; } } if(p) { *p++ = DAP_DIR_SEPARATOR; while(*p && DAP_IS_DIR_SEPARATOR(*p)) p++; } } while(p); return 0; } /** * dap_path_get_basename: * @a_file_name: the name of the file * * Gets the last component of the filename. * * If @a_file_name ends with a directory separator it gets the component * before the last slash. If @a_file_name consists only of directory * separators (and on Windows, possibly a drive letter), a single * separator is returned. If @a_file_name is empty, it gets ".". * * Returns: a newly allocated string containing the last * component of the filename */ char* dap_path_get_basename(const char *a_file_name) { ssize_t l_base; ssize_t l_last_nonslash; const char *l_retval; dap_return_val_if_fail(a_file_name != NULL, NULL); if(a_file_name[0] == '\0') return dap_strdup("."); l_last_nonslash = strlen(a_file_name) - 1; while(l_last_nonslash >= 0 && DAP_IS_DIR_SEPARATOR(a_file_name[l_last_nonslash])) l_last_nonslash--; if(l_last_nonslash == -1) // string only containing slashes return dap_strdup(DAP_DIR_SEPARATOR_S); #ifdef _WIN32 if (l_last_nonslash == 1 && dap_ascii_isalpha(a_file_name[0]) && a_file_name[1] == ':') // string only containing slashes and a drive return dap_strdup (DAP_DIR_SEPARATOR_S); #endif l_base = l_last_nonslash; while(l_base >= 0 && !DAP_IS_DIR_SEPARATOR(a_file_name[l_base])) l_base--; #ifdef _WIN32 if (l_base == -1 && dap_ascii_isalpha(a_file_name[0]) && a_file_name[1] == ':') l_base = 1; #endif //size_t l_len = l_last_nonslash - l_base; l_retval = a_file_name + l_base + 1; return dap_strdup(l_retval); } /** * dap_path_is_absolute: * @a_file_name: a file name * * Returns true if the given @a_file_name is an absolute file name. * Note that this is a somewhat vague concept on Windows. * * On POSIX systems, an absolute file name is well-defined. It always * starts from the single root directory. For example "/usr/local". * * On Windows, the concepts of current drive and drive-specific * current directory introduce vagueness. This function interprets as * an absolute file name one that either begins with a directory * separator such as "\Users\tml" or begins with the root on a drive, * for example "C:\Windows". The first case also includes UNC paths * such as "\\myserver\docs\foo". In all cases, either slashes or * backslashes are accepted. * * Returns: true if @a_file_name is absolute */ bool dap_path_is_absolute(const char *a_file_name) { dap_return_val_if_fail(a_file_name != NULL, false); if(DAP_IS_DIR_SEPARATOR(a_file_name[0])) return true; #ifdef _WIN32 /* Recognize drive letter on native Windows */ if (dap_ascii_isalpha(a_file_name[0]) && a_file_name[1] == ':' && DAP_IS_DIR_SEPARATOR (a_file_name[2])) return true; #endif return false; } /** * dap_path_get_dirname: * @a_file_name: the name of the file * * Gets the directory components of a file name. * * If the file name has no directory components "." is returned. * The returned string should be freed when no longer needed. * * Returns: the directory components of the file */ char* dap_path_get_dirname(const char *a_file_name) { char *l_base; size_t l_len; dap_return_val_if_fail(a_file_name != NULL, NULL); l_base = strrchr(a_file_name, DAP_DIR_SEPARATOR); #ifdef _WIN32 { char *l_q; l_q = strrchr (a_file_name, '/'); if (l_base == NULL || (l_q != NULL && l_q > l_base)) l_base = l_q; } #endif if(!l_base) { #ifdef _WIN32 if (dap_ascii_isalpha(a_file_name[0]) && a_file_name[1] == ':') { char l_drive_colon_dot[4]; l_drive_colon_dot[0] = a_file_name[0]; l_drive_colon_dot[1] = ':'; l_drive_colon_dot[2] = '.'; l_drive_colon_dot[3] = '\0'; return dap_strdup (l_drive_colon_dot); } #endif return dap_strdup("."); } while(l_base > a_file_name && DAP_IS_DIR_SEPARATOR(*l_base)) l_base--; #ifdef _WIN32 /* base points to the char before the last slash. * * In case file_name is the root of a drive (X:\) or a child of the * root of a drive (X:\foo), include the slash. * * In case file_name is the root share of an UNC path * (\\server\share), add a slash, returning \\server\share\ . * * In case file_name is a direct child of a share in an UNC path * (\\server\share\foo), include the slash after the share name, * returning \\server\share\ . */ if (l_base == a_file_name + 1 && dap_ascii_isalpha(a_file_name[0]) && a_file_name[1] == ':') l_base++; else if (DAP_IS_DIR_SEPARATOR (a_file_name[0]) && DAP_IS_DIR_SEPARATOR (a_file_name[1]) && a_file_name[2] && !DAP_IS_DIR_SEPARATOR (a_file_name[2]) && l_base >= a_file_name + 2) { const char *l_p = a_file_name + 2; while (*l_p && !DAP_IS_DIR_SEPARATOR (*l_p)) l_p++; if (l_p == l_base + 1) { l_len = (uint32_t) strlen (a_file_name) + 1; l_base = DAP_NEW_SIZE (char, l_len + 1); strcpy (l_base, a_file_name); l_base[l_len-1] = DAP_DIR_SEPARATOR; l_base[l_len] = 0; return l_base; } if (DAP_IS_DIR_SEPARATOR (*l_p)) { l_p++; while (*l_p && !DAP_IS_DIR_SEPARATOR (*l_p)) l_p++; if (l_p == l_base + 1) l_base++; } } #endif l_len = (uint32_t) 1 + l_base - a_file_name; l_base = DAP_NEW_SIZE(char, l_len + 1); memmove(l_base, a_file_name, l_len); l_base[l_len] = 0; return l_base; } dap_list_name_directories_t *dap_get_subs(const char *a_path_dir){ dap_list_name_directories_t *list = NULL; dap_list_name_directories_t *element; #ifdef _WIN32 size_t m_size = strlen(a_path_dir); char *m_path = DAP_NEW_SIZE(char, m_size + 2); memcpy(m_path, a_path_dir, m_size); m_path[m_size] = '*'; m_path[m_size + 1] = '\0'; WIN32_FIND_DATA info_file; HANDLE h_find_file = FindFirstFileA(m_path, &info_file); while (FindNextFileA(h_find_file, &info_file)){ if (info_file.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY){ element = (dap_list_name_directories_t *)malloc(sizeof(dap_list_name_directories_t)); element->name_directory = dap_strdup(info_file.cFileName); LL_APPEND(list, element); } } FindClose(h_find_file); DAP_FREE(m_path); #else DIR *dir = opendir(a_path_dir); struct dirent *entry = readdir(dir); while (entry != NULL){ if (strcmp(entry->d_name, "..") != 0 && strcmp(entry->d_name, ".") != 0 && entry->d_type == DT_DIR){ element = (dap_list_name_directories_t *)malloc(sizeof(dap_list_name_directories_t)); element->name_directory = dap_strdup(entry->d_name); LL_APPEND(list, element); } entry = readdir(dir); } closedir(dir); #endif return list; } static bool get_contents_stdio(const char *filename, FILE *f, char **contents, size_t *length) { char buf[4096]; size_t bytes; /* always <= sizeof(buf) */ char *str = NULL; size_t total_bytes = 0; size_t total_allocated = 0; char *tmp; assert(f != NULL); while(!feof(f)) { int save_errno; bytes = fread(buf, 1, sizeof(buf), f); save_errno = errno; if(total_bytes > ULONG_MAX - bytes) goto file_too_large; /* Possibility of overflow eliminated above. */ while(total_bytes + bytes >= total_allocated) { if(str) { if(total_allocated > ULONG_MAX / 2) goto file_too_large; total_allocated *= 2; } else { total_allocated = MIN(bytes + 1, sizeof(buf)); } tmp = DAP_REALLOC(str, total_allocated); if(tmp == NULL) goto error; str = tmp; } if(ferror(f)) goto error; assert(str != NULL); memcpy(str + total_bytes, buf, bytes); total_bytes += bytes; } fclose(f); if(total_allocated == 0) { str = DAP_NEW_SIZE(char, 1); total_bytes = 0; } str[total_bytes] = '\0'; if(length) *length = total_bytes; *contents = str; return true; file_too_large: error: DAP_DELETE(str); fclose(f); return false; } #ifndef _WIN32 static bool dap_get_contents_regfile(const char *filename, struct stat *stat_buf, int fd, char **contents, size_t *length) { char *buf; size_t bytes_read; size_t size; size_t alloc_size; size = stat_buf->st_size; alloc_size = size + 1; buf = DAP_NEW_SIZE(char, alloc_size); if(buf == NULL) { goto error; } bytes_read = 0; while(bytes_read < size) { size_t rc; rc = read(fd, buf + bytes_read, size - bytes_read); if(rc < 0) { if(errno != EINTR) { DAP_DELETE(buf); goto error; } } else if(rc == 0) break; else bytes_read += rc; } buf[bytes_read] = '\0'; if(length) *length = bytes_read; *contents = buf; close(fd); return true; error: close(fd); return false; } static bool dap_get_contents_posix(const char *filename, char **contents, size_t *length) { struct stat stat_buf; int fd; /* O_BINARY useful on Cygwin */ fd = open(filename, O_RDONLY | O_BINARY); if(fd < 0) return false; /* I don't think this will ever fail, aside from ENOMEM, but. */ if(fstat(fd, &stat_buf) < 0) { close(fd); return false; } if(stat_buf.st_size > 0 && S_ISREG(stat_buf.st_mode)) { bool retval = dap_get_contents_regfile(filename, &stat_buf, fd, contents, length); return retval; } else { FILE *f; bool retval; f = fdopen(fd, "r"); if(f == NULL) return false; retval = get_contents_stdio(filename, f, contents, length); return retval; } } #else /* _WIN32 */ static bool dap_get_contents_win32(const char *filename, char **contents, size_t *length) { FILE *f; bool retval; f = fopen(filename, "rb"); if(f == NULL) { return false; } retval = get_contents_stdio (filename, f, contents, length); return retval; } #endif /* * Reads an entire file into allocated memory, with error checking. */ bool dap_file_get_contents(const char *filename, char **contents, size_t *length) { dap_return_val_if_fail(filename != NULL, false); dap_return_val_if_fail(contents != NULL, false); *contents = NULL; if(length) *length = 0; #ifdef _WIN32 return dap_get_contents_win32 (filename, contents, length); #else return dap_get_contents_posix(filename, contents, length); #endif }