From 2f5d51c92cb020f995509df0a2680cb0dd8860fd Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Sat, 4 Jun 2022 21:32:35 +0200 Subject: [PATCH] Attempt to add more native file handling for MSVC (#459) * Fix clean and update MSVC function calls. --- src/build/build_options.c | 16 +- src/build/build_options.h | 1 + src/build/builder.c | 13 +- src/build/project_creation.c | 74 +-- src/compiler/compiler.c | 9 + src/compiler/compiler.h | 1 + src/main.c | 22 +- src/utils/dirent.h | 1028 ---------------------------------- src/utils/file_utils.c | 201 ++++--- src/utils/lib.h | 10 +- 10 files changed, 199 insertions(+), 1176 deletions(-) delete mode 100644 src/utils/dirent.h diff --git a/src/build/build_options.c b/src/build/build_options.c index 133b82b8a..4272056d5 100644 --- a/src/build/build_options.c +++ b/src/build/build_options.c @@ -137,23 +137,11 @@ static const char* check_dir(const char *path) { original_path = getcwd(NULL, 0); } - if (chdir(path) == -1) error_exit("The path \"%s\" does not point to a valid directory.", path); - int err = chdir(original_path); - if (err) FAIL_WITH_ERR("Failed to change path to %s.", original_path); + if (!dir_change(path)) error_exit("The path \"%s\" does not point to a valid directory.", path); + if (!dir_change(original_path)) FAIL_WITH_ERR("Failed to change path to %s.", original_path); return path; } -static const char* check_file(const char *file_path) -{ - FILE *file = fopen(file_path, "rb"); - if (file == NULL) - { - error_exit("Could not open file \"%s\".\n", file_path); - } - fclose(file); - return file_path; -} - static inline bool at_end() { return arg_index == arg_count - 1; diff --git a/src/build/build_options.h b/src/build/build_options.h index 744fbb2dc..9ea3303f4 100644 --- a/src/build/build_options.h +++ b/src/build/build_options.h @@ -308,6 +308,7 @@ typedef struct const char **linker_libdirs; const char *cpu; const char **link_args; + const char *build_dir; bool run_after_compile : 1; bool test_output : 1; bool output_headers : 1; diff --git a/src/build/builder.c b/src/build/builder.c index 169912efa..f0492e9cc 100644 --- a/src/build/builder.c +++ b/src/build/builder.c @@ -1,15 +1,9 @@ // Copyright (c) 2019 Christoffer Lerno. All rights reserved. // Use of this source code is governed by a LGPLv3.0 // a copy of which can be found in the LICENSE file. -#ifndef _MSC_VER -#include -#else -#include "utils/dirent.h" -#endif #include "build_internal.h" #include "build_options.h" - void load_library_files(void) {} void load_files(void) {} @@ -237,7 +231,12 @@ void init_build_target(BuildTarget *target, BuildOptions *options) Project *project = project_load(); *target = *project_select_target(project, options->target_select); + target->build_dir = "build"; update_build_target_from_options(target, options); - + if (target->build_dir && !file_exists(target->build_dir)) + { + if (!dir_make(target->build_dir)) error_exit("Failed to create build directory '%s'.", target->build_dir); + if (!file_is_dir(target->build_dir)) error_exit("Expected '%s' to be a directory.", target->build_dir); + } load_library_files(); } \ No newline at end of file diff --git a/src/build/project_creation.c b/src/build/project_creation.c index 392aab82e..137912458 100644 --- a/src/build/project_creation.c +++ b/src/build/project_creation.c @@ -2,14 +2,11 @@ // Use of this source code is governed by the GNU LGPLv3.0 license // a copy of which can be found in the LICENSE file. -#include -#include #include #include #ifndef _MSC_VER #include #endif -#include #include "project_creation.h" #include "build_options.h" #include "../utils/lib.h" @@ -83,88 +80,75 @@ void create_project(BuildOptions *build_options) } } - if (chdir(build_options->path)) + if (!dir_change(build_options->path)) { fprintf(stderr, "Can't open path %s\n", build_options->path); exit_compiler(EXIT_FAILURE); } - int error = mkdir(build_options->project_name, 0755); - if (error) + if (!dir_make(build_options->project_name)) { - fprintf(stderr, "Could not create directory %s: %s\n", build_options->project_name, strerror(errno)); + fprintf(stderr, "Could not create directory %s.\n", build_options->project_name); exit_compiler(EXIT_FAILURE); } - if (chdir(build_options->project_name)) goto ERROR; + if (!dir_change(build_options->project_name)) goto ERROR; - FILE *file = fopen("LICENCE", "a"); - if (!file) goto ERROR; - if (fclose(file)) goto ERROR; + if (!file_touch("LICENCE")) goto ERROR; - file = fopen("README.md", "a"); - if (!file) goto ERROR; - if (fclose(file)) goto ERROR; + if (!file_touch("README.md")) goto ERROR; - file = fopen("project.c3p", "a"); + FILE *file = fopen("project.c3p", "a"); if (!file) goto ERROR; (void) fprintf(file, JSON, build_options->project_name); if (fclose(file)) goto ERROR; - if (mkdir("lib", 0755)) goto ERROR; + if (!dir_make("lib")) goto ERROR; - if (mkdir("build", 0755)) goto ERROR; + if (!dir_make("build")) goto ERROR; - if (mkdir("resources", 0755)) goto ERROR; + if (!dir_make("resources")) goto ERROR; - if (mkdir("test", 0755)) goto ERROR; + if (!dir_make("test")) goto ERROR; - if (chdir("test")) goto ERROR; + if (!dir_change("test")) goto ERROR; - if (mkdir(build_options->project_name, 0755)) goto ERROR; + if (!dir_make(build_options->project_name)) goto ERROR; - if (chdir("..")) goto ERROR; + if (!dir_change("..")) goto ERROR; - if (mkdir("directives", 0755)) goto ERROR; + if (!dir_make("directives")) goto ERROR; - if (chdir("directives") == -1) goto ERROR; + if (!dir_change("directives")) goto ERROR; - file = fopen("about.md", "a"); - if (!file) goto ERROR; - if (fclose(file)) goto ERROR; + if (!file_touch("about.md")) goto ERROR; - if (mkdir("src", 0755)) goto ERROR; + if (!dir_make("src")) goto ERROR; - if (chdir("src")) goto ERROR; + if (!dir_change("src")) goto ERROR; - file = fopen("index.html", "a"); - if (!file) goto ERROR; - if (fclose(file)) goto ERROR; + if (!file_touch("index.html")) goto ERROR; - if (chdir("../..")) goto ERROR; + if (!dir_change("../..")) goto ERROR; - if (mkdir("src", 0755)) goto ERROR; + if (!dir_make("src")) goto ERROR; - if (chdir("src")) goto ERROR; + if (!dir_change("src")) goto ERROR; - if (mkdir(build_options->project_name, 0755)) goto ERROR; + if (!dir_make(build_options->project_name)) goto ERROR; - if (chdir(build_options->project_name)) goto ERROR; + if (!dir_change(build_options->project_name)) goto ERROR; - file = fopen("main.c3", "a"); - if (!file) goto ERROR; - if (fclose(file)) goto ERROR; + if (!file_touch("main.c3")) goto ERROR; - if (chdir("../..")) goto ERROR; + if (!dir_change("../..")) goto ERROR; (void) printf("Project '%s' created.\n", build_options->project_name); exit_compiler(COMPILER_SUCCESS_EXIT); ERROR: - fprintf(stderr, "Err: %s\n", strerror(errno)); - - printf("Something went wrong creating the project.\n"); - if (!chdir(build_options->path)) + fprintf(stderr, "Error creating the project.\n"); + if (dir_change(build_options->path)) { (void)rmdir(build_options->project_name); } diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index ab2c9e61c..78c0c5919 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -382,9 +382,18 @@ void compile_target(BuildOptions *options) compile(); } +void compile_clean(BuildOptions *options) +{ + init_build_target(&active_target, options); + file_delete_all_files_in_dir_with_suffix(active_target.build_dir, get_object_extension()); +} void compile_file_list(BuildOptions *options) { init_build_target(&active_target, options); + if (options->command == COMMAND_CLEAN_RUN) + { + file_delete_all_files_in_dir_with_suffix(active_target.build_dir, get_object_extension()); + } compile(); } diff --git a/src/compiler/compiler.h b/src/compiler/compiler.h index 7131f0c25..258de68f6 100644 --- a/src/compiler/compiler.h +++ b/src/compiler/compiler.h @@ -10,6 +10,7 @@ void compiler_init(const char *std_lib_dir); void compile(); void compile_target(BuildOptions *options); void compile_file_list(BuildOptions *options); +void compile_clean(BuildOptions *options); void init_build_target(BuildTarget *build_target, BuildOptions *build_options); void init_default_build_target(BuildTarget *target, BuildOptions *options); void symtab_init(uint32_t max_size); diff --git a/src/main.c b/src/main.c index fd66e697b..13fbe6187 100644 --- a/src/main.c +++ b/src/main.c @@ -61,10 +61,12 @@ int main_real(int argc, const char *argv[]) case COMMAND_COMPILE_RUN: compile_target(&build_options); break; + case COMMAND_CLEAN: + compile_clean(&build_options); + break; + case COMMAND_CLEAN_RUN: case COMMAND_BUILD: case COMMAND_RUN: - case COMMAND_CLEAN_RUN: - case COMMAND_CLEAN: case COMMAND_DIST: case COMMAND_DOCS: case COMMAND_BENCH: @@ -79,7 +81,23 @@ int main_real(int argc, const char *argv[]) return 0; } +#if (_MSC_VER) + +int wmain(int argc, const uint16_t *argv[]) +{ + char** args = malloc(sizeof(void*) * (unsigned)argc); + for (unsigned i = 0; i < (unsigned)argc; i++) + { + args[i] = win_utf16to8(argv[i]); + } + main_real(argc, (const char **)args); +} + +#else + int main(int argc, const char *argv[]) { return main_real(argc, argv); } + +#endif \ No newline at end of file diff --git a/src/utils/dirent.h b/src/utils/dirent.h deleted file mode 100644 index d5b488b79..000000000 --- a/src/utils/dirent.h +++ /dev/null @@ -1,1028 +0,0 @@ -/* - * Dirent interface for Microsoft Visual Studio - * - * Copyright (C) 1998-2019 Toni Ronkko - * This file is part of dirent. Dirent may be freely distributed - * under the MIT license. For all details and documentation, see - * https://github.com/tronkko/dirent - */ -#ifndef DIRENT_H -#define DIRENT_H - -/* Hide warnings about unreferenced local functions */ -#if defined(__clang__) -# pragma clang diagnostic ignored "-Wunused-function" -#elif defined(_MSC_VER) -# pragma warning(disable:4505) -#elif defined(__GNUC__) -# pragma GCC diagnostic ignored "-Wunused-function" -#endif - -/* - * Include windows.h without Windows Sockets 1.1 to prevent conflicts with - * Windows Sockets 2.0. - */ -#ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -#endif -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "lib.h" - -/* Indicates that d_type field is available in dirent structure */ -#define _DIRENT_HAVE_D_TYPE - -/* Indicates that d_namlen field is available in dirent structure */ -#define _DIRENT_HAVE_D_NAMLEN - -/* Entries missing from MSVC 6.0 */ -#if !defined(FILE_ATTRIBUTE_DEVICE) -# define FILE_ATTRIBUTE_DEVICE 0x40 -#endif - -/* File type and permission flags for stat(), general mask */ -#if !defined(S_IFMT) -# define S_IFMT _S_IFMT -#endif - -/* Directory bit */ -#if !defined(S_IFDIR) -# define S_IFDIR _S_IFDIR -#endif - -/* Character device bit */ -#if !defined(S_IFCHR) -# define S_IFCHR _S_IFCHR -#endif - -/* Pipe bit */ -#if !defined(S_IFFIFO) -# define S_IFFIFO _S_IFFIFO -#endif - -/* Regular file bit */ -#if !defined(S_IFREG) -# define S_IFREG _S_IFREG -#endif - -/* Read permission */ -#if !defined(S_IREAD) -# define S_IREAD _S_IREAD -#endif - -/* Write permission */ -#if !defined(S_IWRITE) -# define S_IWRITE _S_IWRITE -#endif - -/* Execute permission */ -#if !defined(S_IEXEC) -# define S_IEXEC _S_IEXEC -#endif - -/* Pipe */ -#if !defined(S_IFIFO) -# define S_IFIFO _S_IFIFO -#endif - -/* Block device */ -#if !defined(S_IFBLK) -# define S_IFBLK 0 -#endif - -/* Link */ -#if !defined(S_IFLNK) -# define S_IFLNK 0 -#endif - -/* Socket */ -#if !defined(S_IFSOCK) -# define S_IFSOCK 0 -#endif - -/* Read user permission */ -#if !defined(S_IRUSR) -# define S_IRUSR S_IREAD -#endif - -/* Write user permission */ -#if !defined(S_IWUSR) -# define S_IWUSR S_IWRITE -#endif - -/* Execute user permission */ -#if !defined(S_IXUSR) -# define S_IXUSR 0 -#endif - -/* Read group permission */ -#if !defined(S_IRGRP) -# define S_IRGRP 0 -#endif - -/* Write group permission */ -#if !defined(S_IWGRP) -# define S_IWGRP 0 -#endif - -/* Execute group permission */ -#if !defined(S_IXGRP) -# define S_IXGRP 0 -#endif - -/* Read others permission */ -#if !defined(S_IROTH) -# define S_IROTH 0 -#endif - -/* Write others permission */ -#if !defined(S_IWOTH) -# define S_IWOTH 0 -#endif - -/* Execute others permission */ -#if !defined(S_IXOTH) -# define S_IXOTH 0 -#endif - -/* Maximum length of file name */ -#if !defined(PATH_MAX) -# define PATH_MAX MAX_PATH -#endif -#if !defined(FILENAME_MAX) -# define FILENAME_MAX MAX_PATH -#endif -#if !defined(NAME_MAX) -# define NAME_MAX FILENAME_MAX -#endif - -/* File type flags for d_type */ -#define DT_UNKNOWN 0 -#define DT_REG S_IFREG -#define DT_DIR S_IFDIR -#define DT_FIFO S_IFIFO -#define DT_SOCK S_IFSOCK -#define DT_CHR S_IFCHR -#define DT_BLK S_IFBLK -#define DT_LNK S_IFLNK - -/* Macros for converting between st_mode and d_type */ -#define IFTODT(mode) ((mode) & S_IFMT) -#define DTTOIF(type) (type) - -/* - * File type macros. Note that block devices, sockets and links cannot be - * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are - * only defined for compatibility. These macros should always return false - * on Windows. - */ -#if !defined(S_ISFIFO) -# define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) -#endif -#if !defined(S_ISDIR) -# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) -#endif -#if !defined(S_ISREG) -# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) -#endif -#if !defined(S_ISLNK) -# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) -#endif -#if !defined(S_ISSOCK) -# define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) -#endif -#if !defined(S_ISCHR) -# define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) -#endif -#if !defined(S_ISBLK) -# define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) -#endif - -/* Return the exact length of the file name without zero terminator */ -#define _D_EXACT_NAMLEN(p) ((p)->d_namlen) - -/* Return the maximum size of a file name */ -#define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1) - - -#ifdef __cplusplus -extern "C" { -#endif - - -/* Wide-character version */ -struct _wdirent { - /* Always zero */ - long d_ino; - - /* File position within stream */ - long d_off; - - /* Structure size */ - unsigned short d_reclen; - - /* Length of name without \0 */ - size_t d_namlen; - - /* File type */ - int d_type; - - /* File name */ - wchar_t d_name[PATH_MAX+1]; -}; -typedef struct _wdirent _wdirent; - -struct _WDIR { - /* Current directory entry */ - struct _wdirent ent; - - /* Private file data */ - WIN32_FIND_DATAW data; - - /* True if data is valid */ - int cached; - - /* Win32 search handle */ - HANDLE handle; - - /* Initial directory name */ - wchar_t *patt; -}; -typedef struct _WDIR _WDIR; - -/* Multi-byte character version */ -struct dirent { - /* Always zero */ - long d_ino; - - /* File position within stream */ - long d_off; - - /* Structure size */ - unsigned short d_reclen; - - /* Length of name without \0 */ - size_t d_namlen; - - /* File type */ - int d_type; - - /* File name */ - char d_name[PATH_MAX+1]; -}; -typedef struct dirent dirent; - -struct DIR { - struct dirent ent; - struct _WDIR *wdirp; -}; -typedef struct DIR DIR; - - -/* Dirent functions */ -static DIR *opendir(const char *dirname); -static _WDIR *_wopendir(const wchar_t *dirname); - -static struct dirent *readdir(DIR *dirp); -static struct _wdirent *_wreaddir(_WDIR *dirp); - -static int readdir_r( - DIR *dirp, struct dirent *entry, struct dirent **result); -static int _wreaddir_r( - _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result); - -static int closedir(DIR *dirp); -static int _wclosedir(_WDIR *dirp); - -static void rewinddir(DIR* dirp); -static void _wrewinddir(_WDIR* dirp); - -static int scandir(const char *dirname, struct dirent ***namelist, - int (*filter)(const struct dirent*), - int (*compare)(const struct dirent**, const struct dirent**)); - -static int alphasort(const struct dirent **a, const struct dirent **b); - -static int versionsort(const struct dirent **a, const struct dirent **b); - -static int strverscmp(const char *a, const char *b); - -/* For compatibility with Symbian */ -#define wdirent _wdirent -#define WDIR _WDIR -#define wopendir _wopendir -#define wreaddir _wreaddir -#define wclosedir _wclosedir -#define wrewinddir _wrewinddir - -/* Compatibility with older Microsoft compilers and non-Microsoft compilers */ -#if !defined(_MSC_VER) || _MSC_VER < 1400 -# define wcstombs_s dirent_wcstombs_s -# define mbstowcs_s dirent_mbstowcs_s -#endif - -/* Optimize dirent_set_errno() away on modern Microsoft compilers */ -#if defined(_MSC_VER) && _MSC_VER >= 1400 -# define dirent_set_errno _set_errno -#endif - - -/* Internal utility functions */ -static WIN32_FIND_DATAW *dirent_first(_WDIR *dirp); -static WIN32_FIND_DATAW *dirent_next(_WDIR *dirp); - -#if !defined(_MSC_VER) || _MSC_VER < 1400 -static int dirent_mbstowcs_s( - size_t *pReturnValue, wchar_t *wcstr, size_t sizeInWords, - const char *mbstr, size_t count); -#endif - -#if !defined(_MSC_VER) || _MSC_VER < 1400 -static int dirent_wcstombs_s( - size_t *pReturnValue, char *mbstr, size_t sizeInBytes, - const wchar_t *wcstr, size_t count); -#endif - -#if !defined(_MSC_VER) || _MSC_VER < 1400 -static void dirent_set_errno(int error); -#endif - - -/* - * Open directory stream DIRNAME for read and return a pointer to the - * internal working area that is used to retrieve individual directory - * entries. - */ -static _WDIR *_wopendir(const wchar_t *dirname) -{ - wchar_t *p; - - /* Must have directory name */ - if (dirname == NULL || dirname[0] == '\0') { - dirent_set_errno(ENOENT); - return NULL; - } - - /* Allocate new _WDIR structure */ - _WDIR *dirp = (_WDIR*) cmalloc(sizeof(struct _WDIR)); - if (!dirp) - return NULL; - - /* Reset _WDIR structure */ - dirp->handle = INVALID_HANDLE_VALUE; - dirp->patt = NULL; - dirp->cached = 0; - - /* - * Compute the length of full path plus zero terminator - * - * Note that on WinRT there's no way to convert relative paths - * into absolute paths, so just assume it is an absolute path. - */ -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) - /* Desktop */ - DWORD n = GetFullPathNameW(dirname, 0, NULL, NULL); -#else - /* WinRT */ - size_t n = wcslen(dirname); -#endif - - /* Allocate room for absolute directory name and search pattern */ - dirp->patt = (wchar_t*) cmalloc(sizeof(wchar_t) * n + 16); - if (dirp->patt == NULL) - goto exit_closedir; - - /* - * Convert relative directory name to an absolute one. This - * allows rewinddir() to function correctly even when current - * working directory is changed between opendir() and rewinddir(). - * - * Note that on WinRT there's no way to convert relative paths - * into absolute paths, so just assume it is an absolute path. - */ -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) - /* Desktop */ - n = GetFullPathNameW(dirname, n, dirp->patt, NULL); - if (n <= 0) - goto exit_closedir; -#else - /* WinRT */ - wcsncpy_s(dirp->patt, n+1, dirname, n); -#endif - - /* Append search pattern \* to the directory name */ - p = dirp->patt + n; - switch (p[-1]) { - case '\\': - case '/': - case ':': - /* Directory ends in path separator, e.g. c:\temp\ */ - /*NOP*/; - break; - - default: - /* Directory name doesn't end in path separator */ - *p++ = '\\'; - } - *p++ = '*'; - *p = '\0'; - - /* Open directory stream and retrieve the first entry */ - if (!dirent_first(dirp)) - goto exit_closedir; - - /* Success */ - return dirp; - - /* Failure */ -exit_closedir: - _wclosedir(dirp); - return NULL; -} - -/* - * Read next directory entry. - * - * Returns pointer to static directory entry which may be overwritten by - * subsequent calls to _wreaddir(). - */ -static struct _wdirent *_wreaddir(_WDIR *dirp) -{ - /* - * Read directory entry to buffer. We can safely ignore the return - * value as entry will be set to NULL in case of error. - */ - struct _wdirent *entry; - (void) _wreaddir_r(dirp, &dirp->ent, &entry); - - /* Return pointer to statically allocated directory entry */ - return entry; -} - -/* - * Read next directory entry. - * - * Returns zero on success. If end of directory stream is reached, then sets - * result to NULL and returns zero. - */ -static int _wreaddir_r( - _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result) -{ - /* Read next directory entry */ - WIN32_FIND_DATAW *datap = dirent_next(dirp); - if (!datap) { - /* Return NULL to indicate end of directory */ - *result = NULL; - return /*OK*/0; - } - - /* - * Copy file name as wide-character string. If the file name is too - * long to fit in to the destination buffer, then truncate file name - * to PATH_MAX characters and zero-terminate the buffer. - */ - size_t n = 0; - while (n < PATH_MAX && datap->cFileName[n] != 0) { - entry->d_name[n] = datap->cFileName[n]; - n++; - } - entry->d_name[n] = 0; - - /* Length of file name excluding zero terminator */ - entry->d_namlen = n; - - /* File type */ - DWORD attr = datap->dwFileAttributes; - if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) - entry->d_type = DT_CHR; - else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) - entry->d_type = DT_DIR; - else - entry->d_type = DT_REG; - - /* Reset dummy fields */ - entry->d_ino = 0; - entry->d_off = 0; - entry->d_reclen = sizeof(struct _wdirent); - - /* Set result address */ - *result = entry; - return /*OK*/0; -} - -/* - * Close directory stream opened by opendir() function. This invalidates the - * DIR structure as well as any directory entry read previously by - * _wreaddir(). - */ -static int _wclosedir(_WDIR *dirp) -{ - if (!dirp) { - dirent_set_errno(EBADF); - return /*failure*/-1; - } - - /* Release search handle */ - if (dirp->handle != INVALID_HANDLE_VALUE) - FindClose(dirp->handle); - - /* Release search pattern */ - free(dirp->patt); - - /* Release directory structure */ - free(dirp); - return /*success*/0; -} - -/* - * Rewind directory stream such that _wreaddir() returns the very first - * file name again. - */ -static void _wrewinddir(_WDIR* dirp) -{ - if (!dirp) - return; - - /* Release existing search handle */ - if (dirp->handle != INVALID_HANDLE_VALUE) - FindClose(dirp->handle); - - /* Open new search handle */ - dirent_first(dirp); -} - -/* Get first directory entry */ -static WIN32_FIND_DATAW *dirent_first(_WDIR *dirp) -{ - if (!dirp) - return NULL; - - /* Open directory and retrieve the first entry */ - dirp->handle = FindFirstFileExW( - dirp->patt, FindExInfoStandard, &dirp->data, - FindExSearchNameMatch, NULL, 0); - if (dirp->handle == INVALID_HANDLE_VALUE) - goto error; - - /* A directory entry is now waiting in memory */ - dirp->cached = 1; - return &dirp->data; - -error: - /* Failed to open directory: no directory entry in memory */ - dirp->cached = 0; - - /* Set error code */ - DWORD errorcode = GetLastError(); - switch (errorcode) { - case ERROR_ACCESS_DENIED: - /* No read access to directory */ - dirent_set_errno(EACCES); - break; - - case ERROR_DIRECTORY: - /* Directory name is invalid */ - dirent_set_errno(ENOTDIR); - break; - - case ERROR_PATH_NOT_FOUND: - default: - /* Cannot find the file */ - dirent_set_errno(ENOENT); - } - return NULL; -} - -/* Get next directory entry */ -static WIN32_FIND_DATAW *dirent_next(_WDIR *dirp) -{ - /* Is the next directory entry already in cache? */ - if (dirp->cached) { - /* Yes, a valid directory entry found in memory */ - dirp->cached = 0; - return &dirp->data; - } - - /* No directory entry in cache */ - if (dirp->handle == INVALID_HANDLE_VALUE) - return NULL; - - /* Read the next directory entry from stream */ - if (FindNextFileW(dirp->handle, &dirp->data) == FALSE) - goto exit_close; - - /* Success */ - return &dirp->data; - - /* Failure */ -exit_close: - FindClose(dirp->handle); - dirp->handle = INVALID_HANDLE_VALUE; - return NULL; -} - -/* Open directory stream using plain old C-string */ -static DIR *opendir(const char *dirname) -{ - /* Must have directory name */ - if (dirname == NULL || dirname[0] == '\0') { - dirent_set_errno(ENOENT); - return NULL; - } - - /* Allocate memory for DIR structure */ - struct DIR *dirp = (DIR*) cmalloc(sizeof(struct DIR)); - if (!dirp) - return NULL; - - /* Convert directory name to wide-character string */ - wchar_t wname[PATH_MAX + 1]; - size_t n; - int error = mbstowcs_s(&n, wname, PATH_MAX + 1, dirname, PATH_MAX+1); - if (error) - goto exit_failure; - - /* Open directory stream using wide-character name */ - dirp->wdirp = _wopendir(wname); - if (!dirp->wdirp) - goto exit_failure; - - /* Success */ - return dirp; - - /* Failure */ -exit_failure: - free(dirp); - return NULL; -} - -/* Read next directory entry */ -static struct dirent *readdir(DIR *dirp) -{ - /* - * Read directory entry to buffer. We can safely ignore the return - * value as entry will be set to NULL in case of error. - */ - struct dirent *entry; - (void) readdir_r(dirp, &dirp->ent, &entry); - - /* Return pointer to statically allocated directory entry */ - return entry; -} - -/* - * Read next directory entry into called-allocated buffer. - * - * Returns zero on success. If the end of directory stream is reached, then - * sets result to NULL and returns zero. - */ -static int readdir_r( - DIR *dirp, struct dirent *entry, struct dirent **result) -{ - /* Read next directory entry */ - WIN32_FIND_DATAW *datap = dirent_next(dirp->wdirp); - if (!datap) { - /* No more directory entries */ - *result = NULL; - return /*OK*/0; - } - - /* Attempt to convert file name to multi-byte string */ - size_t n; - int error = wcstombs_s( - &n, entry->d_name, PATH_MAX + 1, - datap->cFileName, PATH_MAX + 1); - - /* - * If the file name cannot be represented by a multi-byte string, then - * attempt to use old 8+3 file name. This allows the program to - * access files although file names may seem unfamiliar to the user. - * - * Be ware that the code below cannot come up with a short file name - * unless the file system provides one. At least VirtualBox shared - * folders fail to do this. - */ - if (error && datap->cAlternateFileName[0] != '\0') { - error = wcstombs_s( - &n, entry->d_name, PATH_MAX + 1, - datap->cAlternateFileName, PATH_MAX + 1); - } - - if (!error) { - /* Length of file name excluding zero terminator */ - entry->d_namlen = n - 1; - - /* File attributes */ - DWORD attr = datap->dwFileAttributes; - if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) - entry->d_type = DT_CHR; - else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) - entry->d_type = DT_DIR; - else - entry->d_type = DT_REG; - - /* Reset dummy fields */ - entry->d_ino = 0; - entry->d_off = 0; - entry->d_reclen = sizeof(struct dirent); - } else { - /* - * Cannot convert file name to multi-byte string so construct - * an erroneous directory entry and return that. Note that - * we cannot return NULL as that would stop the processing - * of directory entries completely. - */ - entry->d_name[0] = '?'; - entry->d_name[1] = '\0'; - entry->d_namlen = 1; - entry->d_type = DT_UNKNOWN; - entry->d_ino = 0; - entry->d_off = -1; - entry->d_reclen = 0; - } - - /* Return pointer to directory entry */ - *result = entry; - return /*OK*/0; -} - -/* Close directory stream */ -static int closedir(DIR *dirp) -{ - int ok; - - if (!dirp) - goto exit_failure; - - /* Close wide-character directory stream */ - ok = _wclosedir(dirp->wdirp); - dirp->wdirp = NULL; - - /* Release multi-byte character version */ - free(dirp); - return ok; - -exit_failure: - /* Invalid directory stream */ - dirent_set_errno(EBADF); - return /*failure*/-1; -} - -/* Rewind directory stream to beginning */ -static void rewinddir(DIR* dirp) -{ - if (!dirp) - return; - - /* Rewind wide-character string directory stream */ - _wrewinddir(dirp->wdirp); -} - -/* Scan directory for entries */ -static int scandir( - const char *dirname, struct dirent ***namelist, - int (*filter)(const struct dirent*), - int (*compare)(const struct dirent**, const struct dirent**)) -{ - int result; - - /* Open directory stream */ - DIR *dir = opendir(dirname); - if (!dir) { - /* Cannot open directory */ - return /*Error*/ -1; - } - - /* Read directory entries to memory */ - struct dirent *tmp = NULL; - struct dirent **files = NULL; - size_t size = 0; - size_t allocated = 0; - while (1) { - /* Allocate room for a temporary directory entry */ - if (!tmp) { - tmp = (struct dirent*) cmalloc(sizeof(struct dirent)); - if (!tmp) - goto exit_failure; - } - - /* Read directory entry to temporary area */ - struct dirent *entry; - if (readdir_r(dir, tmp, &entry) != /*OK*/0) - goto exit_failure; - - /* Stop if we already read the last directory entry */ - if (entry == NULL) - goto exit_success; - - /* Determine whether to include the entry in results */ - if (filter && !filter(tmp)) - continue; - - /* Enlarge pointer table to make room for another pointer */ - if (size >= allocated) { - /* Compute number of entries in the new table */ - size_t num_entries = size * 2 + 16; - - /* Allocate new pointer table or enlarge existing */ - void *p = realloc(files, sizeof(void*) * num_entries); - if (!p) - goto exit_failure; - - /* Got the memory */ - files = (dirent**) p; - allocated = num_entries; - } - - /* Store the temporary entry to ptr table */ - files[size++] = tmp; - tmp = NULL; - } - -exit_failure: - /* Release allocated file entries */ - for (size_t i = 0; i < size; i++) { - free(files[i]); - } - - /* Release the pointer table */ - free(files); - files = NULL; - - /* Exit with error code */ - result = /*error*/ -1; - goto exit_status; - -exit_success: - /* Sort directory entries */ - qsort(files, size, sizeof(void*), - (int (*) (const void*, const void*)) compare); - - /* Pass pointer table to caller */ - if (namelist) - *namelist = files; - - /* Return the number of directory entries read */ - result = (int) size; - -exit_status: - /* Release temporary directory entry, if we had one */ - free(tmp); - - /* Close directory stream */ - closedir(dir); - return result; -} - -/* Alphabetical sorting */ -static int alphasort(const struct dirent **a, const struct dirent **b) -{ - return strcoll((*a)->d_name, (*b)->d_name); -} - -/* Sort versions */ -static int versionsort(const struct dirent **a, const struct dirent **b) -{ - return strverscmp((*a)->d_name, (*b)->d_name); -} - -/* Compare strings */ -static int strverscmp(const char *a, const char *b) -{ - size_t i = 0; - size_t j; - - /* Find first difference */ - while (a[i] == b[i]) { - if (a[i] == '\0') { - /* No difference */ - return 0; - } - ++i; - } - - /* Count backwards and find the leftmost digit */ - j = i; - while (j > 0 && isdigit(a[j-1])) { - --j; - } - - /* Determine mode of comparison */ - if (a[j] == '0' || b[j] == '0') { - /* Find the next non-zero digit */ - while (a[j] == '0' && a[j] == b[j]) { - j++; - } - - /* String with more digits is smaller, e.g 002 < 01 */ - if (isdigit(a[j])) { - if (!isdigit(b[j])) { - return -1; - } - } else if (isdigit(b[j])) { - return 1; - } - } else if (isdigit(a[j]) && isdigit(b[j])) { - /* Numeric comparison */ - size_t k1 = j; - size_t k2 = j; - - /* Compute number of digits in each string */ - while (isdigit(a[k1])) { - k1++; - } - while (isdigit(b[k2])) { - k2++; - } - - /* Number with more digits is bigger, e.g 999 < 1000 */ - if (k1 < k2) - return -1; - else if (k1 > k2) - return 1; - } - - /* Alphabetical comparison */ - return (int) ((unsigned char) a[i]) - ((unsigned char) b[i]); -} - -/* Convert multi-byte string to wide character string */ -#if !defined(_MSC_VER) || _MSC_VER < 1400 -static int dirent_mbstowcs_s( - size_t *pReturnValue, wchar_t *wcstr, - size_t sizeInWords, const char *mbstr, size_t count) -{ - /* Older Visual Studio or non-Microsoft compiler */ - size_t n = mbstowcs(wcstr, mbstr, sizeInWords); - if (wcstr && n >= count) - return /*error*/ 1; - - /* Zero-terminate output buffer */ - if (wcstr && sizeInWords) { - if (n >= sizeInWords) - n = sizeInWords - 1; - wcstr[n] = 0; - } - - /* Length of multi-byte string with zero terminator */ - if (pReturnValue) { - *pReturnValue = n + 1; - } - - /* Success */ - return 0; -} -#endif - -/* Convert wide-character string to multi-byte string */ -#if !defined(_MSC_VER) || _MSC_VER < 1400 -static int dirent_wcstombs_s( - size_t *pReturnValue, char *mbstr, - size_t sizeInBytes, const wchar_t *wcstr, size_t count) -{ - /* Older Visual Studio or non-Microsoft compiler */ - size_t n = wcstombs(mbstr, wcstr, sizeInBytes); - if (mbstr && n >= count) - return /*error*/1; - - /* Zero-terminate output buffer */ - if (mbstr && sizeInBytes) { - if (n >= sizeInBytes) { - n = sizeInBytes - 1; - } - mbstr[n] = '\0'; - } - - /* Length of resulting multi-bytes string WITH zero-terminator */ - if (pReturnValue) { - *pReturnValue = n + 1; - } - - /* Success */ - return 0; -} -#endif - -/* Set errno variable */ -#if !defined(_MSC_VER) || _MSC_VER < 1400 -static void dirent_set_errno(int error) -{ - /* Non-Microsoft compiler or older Microsoft compiler */ - errno = error; -} -#endif - -#ifdef __cplusplus -} -#endif -#endif /*DIRENT_H*/ diff --git a/src/utils/file_utils.c b/src/utils/file_utils.c index f19ab6d72..44361159f 100644 --- a/src/utils/file_utils.c +++ b/src/utils/file_utils.c @@ -3,24 +3,21 @@ // a copy of which can be found in the LICENSE file. #include +#include #include "common.h" #include "errors.h" #include "lib.h" #if PLATFORM_WINDOWS - #include - #endif #ifndef _MSC_VER - -#include #include -#include - +#include #else -#include "utils/dirent.h" +#include +#include // copied from https://github.com/kindkaktus/libconfig/commit/d6222551c5c01c326abc99627e151d549e0f0958 #ifndef S_ISDIR @@ -30,39 +27,74 @@ #if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif - #endif #include #include "whereami.h" -#if _MSC_VER +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif -/** - * remove C: drive prefix (C:, D:, etc.) from a path. - * If that is an issue, I think dirent will have to be replaced or the dirent - * port in use will have to be replaced. - */ -const char *strip_drive_prefix(const char *path) +uint16_t *win_utf8to16(const char *value) { - if (char_is_letter(path[0]) && path[1] == ':') +#if (_MSC_VER) + size_t len = strlen(value); + int needed = MultiByteToWideChar(CP_UTF8, 0, value, len + 1, NULL, 0); + if (needed <= 0) { - return path + 2; // remove first two characters + error_exit("Failed to convert name '%s'.", value); } - - if (path[1] == ':' && (path[2] == '/' || path[2] == '\\')) - { // I don't *think* a relative path can start with '[char]:/' ? right? - // nothing can be done about this currently - error_exit("Illegal path %s - absolute path must start with /, \\, " - "c:, or C: (file a github issue if this is a problem)", path); + uint16_t *wide = malloc(needed * sizeof(uint16_t)); + if (MultiByteToWideChar(CP_UTF8, 0, value, len + 1, wide, needed) <= 0) + { + error_exit("Failed to convert name '%s'.", value); } - - // path is ok - return path; + return wide; +#else + UNREACHABLE +#endif } +#include +char *win_utf16to8(const uint16_t *wname) +{ +#if (_MSC_VER) + size_t len = wcslen(wname); + int needed = WideCharToMultiByte(CP_UTF8, 0, wname, len + 1, NULL, 0, NULL, NULL); + if (needed <= 0) + { + error_exit("Failed to convert wide name."); + } + char *chars = malloc(needed); + if (WideCharToMultiByte(CP_UTF8, 0, wname, len + 1, chars, needed, NULL, NULL) <= 0) + { + error_exit("Failed to convert wide name."); + } + return chars; +#else + UNREACHABLE #endif +} + +bool dir_make(const char *path) +{ +#if (_MSC_VER) + return CreateDirectoryW(win_utf8to16(path), NULL); +#else + return mkdir(path, 0755) == 0; +#endif +} + +bool dir_change(const char *path) +{ +#if (_MSC_VER) + return SetCurrentDirectoryW(win_utf8to16(path)); +#else + return chdir(path) == 0; +#endif +} static inline bool is_path_separator(char c) { @@ -133,10 +165,29 @@ const char *file_expand_path(const char *path) return path; } +FILE *file_open_read(const char *path) +{ +#if (_MSC_VER) + return _wfopen(win_utf8to16(path), L"rb"); +#else + return fopen(path, "rb"); +#endif +} + +bool file_touch(const char *path) +{ +#if (_MSC_VER) + FILE *file = _wfopen(win_utf8to16(path), L"a"); +#else + FILE *file = fopen(path, "a"); +#endif + if (!file) return false; + return fclose(file) == 0; +} char *file_read_all(const char *path, size_t *return_size) { - FILE *file = fopen(path, "rb"); + FILE *file = file_open_read(path); if (file == NULL) { @@ -236,7 +287,6 @@ void file_get_dir_and_filename_from_full(const char *full_path, char **filename, } } - void file_find_top_dir() { while (1) @@ -257,7 +307,7 @@ void file_find_top_dir() // Note that symbolically linked files are ignored. char start_path[PATH_MAX + 1]; getcwd(start_path, PATH_MAX); - if (chdir("..")) + if (!dir_change("..")) { error_exit("Can't change directory to search for %s: %s.", PROJECT_JSON, strerror(errno)); } @@ -307,61 +357,56 @@ extern int _getdrive(void); extern int _chdrive(int drive); #endif -const char *file_first(const char *path) +bool file_delete_all_files_in_dir_with_suffix(const char *path, const char *suffix) { - -#ifdef _MSC_VER - int drive = _getdrive(); - const char *no_drive_prefix = strip_drive_prefix(path); - if (no_drive_prefix != path) - { - _chdrive(path[0] - 'A' + 1); - } - else - { - drive = -1; - } - DIR *dir = opendir(no_drive_prefix); + assert(path); +#if PLATFORM_WINDOWS + const char *cmd = "del /q"; #else - DIR *dir = opendir(path); + const char *cmd = "rm -f"; #endif - const char *result = NULL; - if (!dir) goto EXIT; - struct dirent *ent; - while ((ent = readdir(dir))) - { - size_t name_len = strlen(ent->d_name); - if (name_len > 2) - { - result = str_printf("%.*s", (int)name_len, ent->d_name); - goto EXIT; - } - } -EXIT: - closedir(dir); -#ifdef _MSC_VER - if (drive >= 0) _chdrive(drive); -#endif - return result; + return execute_cmd(str_printf("%s %s/*%s", cmd, path, suffix)) == 0; } +#if (_MSC_VER) + +#include + void file_add_wildcard_files(const char ***files, const char *path, bool recursive, const char **suffix_list, int suffix_count) { -#ifdef _MSC_VER - int drive = _getdrive(); - const char *no_drive_prefix = strip_drive_prefix(path); - if (no_drive_prefix != path) + bool path_ends_with_slash = is_path_separator(path[strlen(path) - 1]); + struct _wfinddata_t file_data; + intptr_t file_handle; + const char *search = str_printf(path_ends_with_slash ? "%s*.*" : "%s/*.*", path); + if ((file_handle = _wfindfirst(win_utf8to16(search), &file_data)) == -1L) return; + do { - _chdrive(path[0] - 'A' + 1); - } - else - { - drive = -1; - } - DIR *dir = opendir(no_drive_prefix); + if ((file_data.attrib & _A_SUBDIR)) + { + if (recursive) + { + if (file_data.name[0] == L'.') + { + continue; + } + char *format = path_ends_with_slash ? "%s%ls" : "%s/%ls"; + char *new_path = str_printf(format, path, file_data.name); + file_add_wildcard_files(files, new_path, true, suffix_list, suffix_count); + } + continue; + } + char *name = win_utf16to8(file_data.name); + if (!file_has_suffix_in_list(name, strlen(name), suffix_list, suffix_count)) continue; + char *format = path_ends_with_slash ? "%s%s" : "%s/%s"; + vec_add(*files, str_printf(format, path, name)); + } while (_wfindnext(file_handle, &file_data) == 0); + _findclose(file_handle); +} #else + +void file_add_wildcard_files(const char ***files, const char *path, bool recursive, const char **suffix_list, int suffix_count) +{ DIR *dir = opendir(path); -#endif bool path_ends_with_slash = is_path_separator(path[strlen(path) - 1]); if (!dir) { @@ -395,12 +440,10 @@ void file_add_wildcard_files(const char ***files, const char *path, bool recursi vec_add(*files, str_printf(format, path, ent->d_name)); } closedir(dir); -#ifdef _MSC_VER - if (drive >= 0) _chdrive(drive); -#endif - } +#endif + #if PLATFORM_WINDOWS const char *execute_cmd(const char *cmd) { @@ -450,7 +493,7 @@ char *realpath(const char *path, char *const resolved_path) { char *result = NULL == resolved_path ? ccalloc(PATH_MAX + 1, 1) : resolved_path; if (NULL == result) return NULL; - if (!GetFullPathNameA(path, MAX_PATH, result, NULL)) + if (!GetFullPathNameA(path, PATH_MAX, result, NULL)) { if (NULL == resolved_path) free(result); return NULL; diff --git a/src/utils/lib.h b/src/utils/lib.h index a979260b7..ea35128d5 100644 --- a/src/utils/lib.h +++ b/src/utils/lib.h @@ -56,17 +56,25 @@ typedef struct Task_ typedef void *TaskQueueRef; +uint16_t *win_utf8to16(const char *name); +char *win_utf16to8(const uint16_t *name); +// Use as if it was mkdir(..., 0755) == 0 +bool dir_make(const char *path); +// Use as if it was chdir(...) == 0 +bool dir_change(const char *path); bool file_namesplit(const char *path, char** filename_ptr, char** directory_ptr); const char* file_expand_path(const char* path); const char* find_lib_dir(void); +bool file_delete_all_files_in_dir_with_suffix(const char *dir, const char *suffix); bool file_is_dir(const char *file); bool file_exists(const char *path); +FILE *file_open_read(const char *path); +bool file_touch(const char *path); char *file_read_all(const char *path, size_t *return_size); void file_get_dir_and_filename_from_full(const char *full_path, char **filename, char **dir_path); void file_find_top_dir(); bool file_has_suffix_in_list(const char *file_name, int name_len, const char **suffix_list, int suffix_count); void file_add_wildcard_files(const char ***files, const char *path, bool recursive, const char **suffix_list, int suffix_count); -const char *file_first(const char *path); const char *file_append_path(const char *path, const char *name); const char *execute_cmd(const char *cmd);