#include "../include/FileUtil.hpp" #include #if OS_FAMILY == OS_FAMILY_POSIX #include #include #include #include #else #include // Copied from linux libc sys/stat.h: #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #pragma comment(lib, "Userenv.lib") #endif using namespace std; #if OS_TYPE == OS_TYPE_OPENBSD || OS_TYPE == OS_TYPE_HAIKU #define stat64 stat #endif #if OS_FAMILY == OS_FAMILY_POSIX static int makedir(const _tinydir_char_t *dir) { return mkdir(dir, S_IRWXU); } #elif OS_FAMILY == OS_FAMILY_WINDOWS static int makedir(const _tinydir_char_t *dir) { return _wmkdir(dir); } #endif namespace sibs { #if OS_FAMILY == OS_FAMILY_POSIX std::string toUtf8(const std::string &input) { return input; } std::string toUtf8(const char *input) { return input; } FileString toFileString(const std::string &utf8Str) { return utf8Str; } FileString toFileString(const StringView &utf8Str) { return FileString(utf8Str.data, utf8Str.data + utf8Str.size); } #else std::string toUtf8(const sibs::FileString &input) { std::string result; utf8::utf16to8(input.data(), input.data() + input.size(), std::back_inserter(result)); return result; } std::string toUtf8(const TCHAR *input) { size_t inputSize = wcslen(input); std::string result; utf8::utf16to8(input, input + inputSize, std::back_inserter(result)); return result; } FileString utf8To16(const StringView &utf8Str) { FileString result; utf8::utf8to16(utf8Str.data, utf8Str.data + utf8Str.size, std::back_inserter(result)); return result; } FileString utf8To16(const std::string &utf8Str) { FileString result; utf8::utf8to16(utf8Str.data(), utf8Str.data() + utf8Str.size(), std::back_inserter(result)); return result; } FileString toFileString(const std::string &utf8Str) { return utf8To16(utf8Str); } FileString toFileString(const StringView &utf8Str) { FileString result; utf8::utf8to16(utf8Str.data, utf8Str.data + utf8Str.size, std::back_inserter(result)); return result; } FileString getLastErrorAsString() { DWORD errorMessageId = GetLastError(); if (errorMessageId == 0) return TINYDIR_STRING(""); LPWSTR messageBuffer = nullptr; size_t size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, errorMessageId, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, nullptr); FileString message(messageBuffer, size); LocalFree(messageBuffer); return message; } void replaceChar(FileString &input, wchar_t charToReplace, wchar_t charToReplaceWith) { for (int i = 0; i < input.size(); ++i) { wchar_t c = input[i]; if (c == charToReplace) input[i] = charToReplaceWith; } } #endif #if OS_FAMILY == OS_FAMILY_POSIX FileType getFileType(const _tinydir_char_t *path) { struct stat64 fileStat; if (stat64(path, &fileStat) == 0) return S_ISREG(fileStat.st_mode) ? FileType::REGULAR : FileType::DIRECTORY; else return FileType::FILE_NOT_FOUND; } Result getFileLastModifiedTime(const _tinydir_char_t *path) { struct stat64 fileStat; if (stat64(path, &fileStat) == 0) return Result::Ok(fileStat.st_mtime); else { string errMsg = "File not found: "; errMsg += toUtf8(path); return Result::Err(errMsg); } } #else FileType getFileType(const _tinydir_char_t *path) { struct _stat64i32 fileStat; if (_wstat(path, &fileStat) == 0) return S_ISREG(fileStat.st_mode) ? FileType::REGULAR : FileType::DIRECTORY; else return FileType::FILE_NOT_FOUND; } Result getFileLastModifiedTime(const _tinydir_char_t *path) { struct _stat64i32 fileStat; if (_wstat(path, &fileStat) == 0) return Result::Ok(fileStat.st_mtime); else { string errMsg = "File not found: "; errMsg += toUtf8(path); return Result::Err(errMsg); } } #endif // TODO: Handle failure (directory doesn't exist, no permission etc) void walkDir(const _tinydir_char_t *directory, FileWalkCallbackFunc callbackFunc) { tinydir_dir dir; tinydir_open(&dir, directory); while (dir.has_next) { tinydir_file file; tinydir_readfile(&dir, &file); if(_tinydir_strncmp(file.name, TINYDIR_STRING("."), 1) != 0) callbackFunc(&file); tinydir_next(&dir); } tinydir_close(&dir); } // TODO: Handle failure (directory doesn't exist, no permission etc) void walkDirFiles(const _tinydir_char_t *directory, FileWalkCallbackFunc callbackFunc) { tinydir_dir dir; tinydir_open(&dir, directory); while (dir.has_next) { tinydir_file file; tinydir_readfile(&dir, &file); if(file.is_reg) callbackFunc(&file); tinydir_next(&dir); } tinydir_close(&dir); } // TODO: Handle failure (directory doesn't exist, no permission etc) void walkDirFilesRecursive(const _tinydir_char_t *directory, FileWalkCallbackFunc callbackFunc) { tinydir_dir dir; tinydir_open(&dir, directory); while (dir.has_next) { tinydir_file file; tinydir_readfile(&dir, &file); if(file.is_reg) callbackFunc(&file); else if(_tinydir_strncmp(file.name, TINYDIR_STRING("."), 1) != 0) walkDirFilesRecursive(file.path, callbackFunc); tinydir_next(&dir); } tinydir_close(&dir); } Result getFileContent(const _tinydir_char_t *filepath) { #if OS_FAMILY == OS_FAMILY_POSIX FILE *file = fopen(filepath, "rb"); #else FILE *file = _wfopen(filepath, TINYDIR_STRING("rb")); #endif if(!file) { int error = errno; string errMsg = "Failed to open file: "; errMsg += toUtf8(filepath); errMsg += "; reason: "; errMsg += strerror(error); return Result::Err(errMsg); } fseek(file, 0, SEEK_END); size_t fileSize = ftell(file); fseek(file, 0, SEEK_SET); // TODO: Change this to string so it can be deallocated and use std::move to prevent copies char *result = (char*)malloc(fileSize + 1); if(!result) { std::string errMsg = "Failed to load file content from file: "; errMsg += toUtf8(filepath); throw std::runtime_error(errMsg); } result[fileSize] = '\0'; fread(result, 1, fileSize, file); fclose(file); return Result::Ok(StringView(result, fileSize)); } Result fileWrite(const _tinydir_char_t *filepath, StringView data) { if(getFileType(filepath) != FileType::FILE_NOT_FOUND) { string errMsg = "Failed to write to file: "; errMsg += toUtf8(filepath); errMsg += "; reason: file already exists"; return Result::Err(errMsg); } return fileOverwrite(filepath, data); } Result fileOverwrite(const _tinydir_char_t *filepath, StringView data) { #if OS_FAMILY == OS_FAMILY_POSIX FILE *file = fopen(filepath, "wb"); #else FILE *file = _wfopen(filepath, TINYDIR_STRING("wb")); #endif if(!file) { int error = errno; string errMsg = "Failed to overwrite file: "; errMsg += toUtf8(filepath); errMsg += "; reason: "; errMsg += strerror(error); return Result::Err(errMsg); } setbuf(file, NULL); fwrite(data.data, 1, data.size, file); fclose(file); return Result::Ok(true); } Result createDirectory(const _tinydir_char_t *path) { size_t pathLength = _tinydir_strlen(path); if (pathLength > _TINYDIR_PATH_MAX - 1) { string errMsg = "Directory path too long: "; errMsg += toUtf8(FileString(path, pathLength)); return Result::Err(errMsg, ENAMETOOLONG); } if (makedir(path) != 0) { int error = errno; if (error != EEXIST) { string errMsg = "Failed to create directory: "; errMsg += toUtf8(path); errMsg += "; reason: "; errMsg += strerror(error); return Result::Err(errMsg, error); } } return Result::Ok(true); } Result createDirectoryRecursive(const _tinydir_char_t *path) { _tinydir_char_t pathBuffer[_TINYDIR_PATH_MAX]; size_t pathLength = _tinydir_strlen(path); if (pathLength > sizeof(pathBuffer) - 1) { string errMsg = "Directory path too long: "; errMsg += toUtf8(FileString(path, pathLength)); return Result::Err(errMsg, ENAMETOOLONG); } _tinydir_strcpy(pathBuffer, path); _tinydir_char_t *p = pathBuffer; for (size_t i = 0; i < pathLength; ++i) { if (i > 0 && *p == '/') { *p = '\0'; if (makedir(pathBuffer) != 0) { int error = errno; if (error != EEXIST) { string errMsg = "Failed to create directory: "; errMsg += toUtf8(pathBuffer); errMsg += "; reason: "; errMsg += strerror(error); return Result::Err(errMsg, error); } } *p = '/'; } ++p; } if (makedir(pathBuffer) != 0) { int error = errno; if (error != EEXIST) { string errMsg = "Failed to create directory: "; errMsg += toUtf8(pathBuffer); errMsg += "; reason: "; errMsg += strerror(error); return Result::Err(errMsg, error); } } return Result::Ok(true); } #if OS_FAMILY == OS_FAMILY_POSIX Result getHomeDir() { const char *homeDir = getenv("HOME"); if(!homeDir) { passwd *pw = getpwuid(getuid()); homeDir = pw->pw_dir; } return Result::Ok(homeDir); } Result getCwd() { FileString cwd; cwd.resize(_TINYDIR_PATH_MAX); if(getcwd(&cwd[0], _TINYDIR_PATH_MAX) != 0) { if(cwd.empty()) cwd = "."; cwd.resize(_tinydir_strlen(cwd.c_str())); return Result::Ok(cwd); } return Result::Err(strerror(errno)); } Result getRealPath(const _tinydir_char_t *path) { // TODO: Verify NULL can be passed as 'resolved' argument with different compilers and operating systems (clang, freebsd etc) char *resolved = realpath(path, nullptr); if(!resolved) { int error = errno; FileString errMsg = "Failed to get real path for \""; errMsg += path; errMsg += "\": "; errMsg += strerror(error); return Result::Err(errMsg, error); } string result = resolved; free(resolved); return Result::Ok(result); } #else Result getHomeDir() { BOOL ret; HANDLE hToken; FileString homeDir; DWORD homeDirLen = _TINYDIR_PATH_MAX; homeDir.resize(homeDirLen); if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken)) return Result::Err("Failed to open process token"); if (!GetUserProfileDirectory(hToken, &homeDir[0], &homeDirLen)) { CloseHandle(hToken); return Result::Err("Failed to get home directory"); } CloseHandle(hToken); homeDir.resize(_tinydir_strlen(homeDir.c_str())); return Result::Ok(homeDir); } Result getCwd() { FileString cwd; cwd.resize(_TINYDIR_PATH_MAX); if (GetCurrentDirectory(_TINYDIR_PATH_MAX, &cwd[0]) == 0) { FileString lastErrStr = getLastErrorAsString(); return Result::Err(toUtf8(lastErrStr)); } cwd.resize(_tinydir_strlen(cwd.c_str())); return Result::Ok(cwd); } Result getRealPath(const _tinydir_char_t *path) { FileString fullPath; fullPath.resize(_TINYDIR_PATH_MAX); if (GetFullPathName(path, _TINYDIR_PATH_MAX, &fullPath[0], nullptr) == 0) { int error = GetLastError(); string errMsg = "Failed to get real path for \""; errMsg += toUtf8(path); errMsg += "\": "; errMsg += toUtf8(getLastErrorAsString()); return Result::Err(errMsg, error); } fullPath.resize(_tinydir_strlen(fullPath.c_str())); return Result::Ok(fullPath); } #endif // TODO: Support better path equality check. Maybe check with OS operation if they refer to the same inode? bool pathEquals(const std::string &path, const std::string &otherPath) { size_t pathIndex = 0; size_t otherPathIndex = 0; while (true) { while (pathIndex < path.size() && (path[pathIndex] == '/' || path[pathIndex] == '\\')) { ++pathIndex; } while (otherPathIndex < otherPath.size() && (otherPath[otherPathIndex] == '/' || otherPath[otherPathIndex] == '\\')) { ++otherPathIndex; } if (pathIndex < path.size() && otherPathIndex < otherPath.size()) { if (path[pathIndex] != otherPath[otherPathIndex]) return false; ++pathIndex; ++otherPathIndex; } else { break; } } return pathIndex == path.size() && otherPathIndex == otherPath.size(); } }