diff options
Diffstat (limited to 'source/base/File.c')
| -rw-r--r-- | source/base/File.c | 922 |
1 files changed, 922 insertions, 0 deletions
diff --git a/source/base/File.c b/source/base/File.c new file mode 100644 index 0000000..f420eb6 --- /dev/null +++ b/source/base/File.c @@ -0,0 +1,922 @@ +// +// File.c - MrsWatson +// Created by Nik Reiman on 09 Dec 12. +// Copyright (c) 2012 Teragon Audio. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// + +// Must be declared before stdlib, shouldn't have any effect on Windows builds +#define _XOPEN_SOURCE 700 +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include "base/File.h" +#include "logging/EventLogger.h" + +#if WINDOWS +#include <Windows.h> +#include <Shellapi.h> +#elif UNIX +#if LINUX +#include <errno.h> +#elif MACOSX +#include <mach-o/dyld.h> +#endif + +#include <dirent.h> +#include <ftw.h> +#include <unistd.h> +#endif + +static const int kFileNameStringLength = 1024; +static const int kFileMaxRecursionDepth = 32; +static const char *kFileNameInvalidCharacters = "*:?<>|"; + +static boolByte _isAbsolutePath(const CharString path) +{ +#if WINDOWS + + if (path->capacity > 3) { + // Check for strings which start with a drive letter, ie C:\file + if (path->data[1] == ':' && path->data[2] == PATH_DELIMITER) { + return true; + } + // Check for network paths, ie \\SERVER\file + else if (path->data[0] == PATH_DELIMITER && path->data[1] == PATH_DELIMITER) { + return true; + } + } + +#else + + if (path->capacity > 1 && path->data[0] == PATH_DELIMITER) { + return true; + } + +#endif + return false; +} + +static CharString _convertRelativePathToAbsolute(const CharString path) +{ + CharString result = newCharStringWithCapacity(kFileNameStringLength); + CharString currentDirectory = fileGetCurrentDirectory(); + snprintf(result->data, result->capacity, "%s%c%s", currentDirectory->data, PATH_DELIMITER, path->data); + freeCharString(currentDirectory); + return result; +} + +static CharString _buildAbsolutePath(const CharString parentDir, const CharString filename) +{ + CharString result = NULL; + CharString parentDirAbsPath = NULL; + + if (parentDir == NULL || charStringIsEmpty(parentDir)) { + logWarn("Attempt to build absolute path with empty directory"); + return result; + } else if (filename == NULL || charStringIsEmpty(filename)) { + logWarn("Attempt to build absolute path with empty file"); + return result; + } else if (_isAbsolutePath(filename)) { + result = newCharStringWithCapacity(kFileNameStringLength); + charStringCopy(result, filename); + return result; + } + + if (_isAbsolutePath(parentDir)) { + parentDirAbsPath = newCharStringWithCapacity(kFileNameStringLength); + charStringCopy(parentDirAbsPath, parentDir); + } else { + parentDirAbsPath = _convertRelativePathToAbsolute(parentDir); + } + + result = newCharStringWithCapacity(kFileNameStringLength); + snprintf(result->data, result->capacity, "%s%c%s", parentDirAbsPath->data, PATH_DELIMITER, filename->data); + freeCharString(parentDirAbsPath); + return result; +} + +static boolByte _isDirectory(const CharString path) +{ + boolByte result = false; + +#if UNIX + struct stat buffer; + + if (stat(path->data, &buffer) == 0) { + result = (boolByte)S_ISDIR(buffer.st_mode); + } + +#elif WINDOWS + DWORD fileAttributes = GetFileAttributesA((LPCSTR)path->data); + + if (fileAttributes != INVALID_FILE_ATTRIBUTES) { + result = (boolByte)(fileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } + +#endif + + return result; +} + +static boolByte _pathContainsInvalidChars(const CharString path) +{ + size_t i = 0; +#if WINDOWS + + // The colon is not allowed in pathnames (even on Windows), however it is + // present in absolute pathnames for the drive letter. Thus we must skip + // the first 3 characters of the path when dealing with absolute paths on + // this platform. + if (_isAbsolutePath(path)) { + i = 3; + } + +#endif + + if (path != NULL) { + for (i = 0; i < strlen(kFileNameInvalidCharacters); ++i) { + if (strchr(path->data, kFileNameInvalidCharacters[i]) != NULL) { + return true; + } + } + } + + return false; +} + +File newFile(void) +{ + File result = (File)malloc(sizeof(FileMembers)); + result->absolutePath = newCharStringWithCapacity(kFileNameStringLength); + result->fileType = kFileTypeInvalid; + result->_fileHandle = NULL; + result->_openMode = kFileOpenModeClosed; + return result; +} + +File newFileWithPath(const CharString path) +{ + File result = newFile(); + CharString currentDirectory = NULL; + CharString absolutePath = NULL; + + if (path != NULL && !charStringIsEmpty(path)) { + if (_isAbsolutePath(path)) { + charStringCopy(result->absolutePath, path); + } else if (_pathContainsInvalidChars(path)) { + logWarn("Could not create file/directory with name '%s'", path->data); + freeFile(result); + return NULL; + } else { + currentDirectory = fileGetCurrentDirectory(); + + if (currentDirectory == NULL) { + logWarn("Could not create file relative to current directory"); + freeFile(result); + return NULL; + } + + absolutePath = _buildAbsolutePath(currentDirectory, path); + charStringCopy(result->absolutePath, absolutePath); + freeCharString(currentDirectory); + freeCharString(absolutePath); + } + + if (fileExists(result)) { + result->fileType = _isDirectory(result->absolutePath) ? kFileTypeDirectory : kFileTypeFile; + } + } + + return result; +} + +File newFileWithPathCString(const char *path) +{ + File result = NULL; + + if (path != NULL) { + CharString pathString = newCharStringWithCString(path); + result = newFileWithPath(pathString); + freeCharString(pathString); + } + + return result; +} + +File newFileWithParent(const File parent, const CharString path) +{ + File result = NULL; + CharString absolutePath = NULL; + + if (parent == NULL) { + logWarn("Cannot create file/directory with null parent"); + return NULL; + } else if (!fileExists(parent)) { + logWarn("Cannot create file/directory under non-existent parent"); + return NULL; + } else if (!_isDirectory(parent->absolutePath)) { + logWarn("Cannot create file/directory under non-directory parent '%s'", parent->absolutePath->data); + return NULL; + } else if (path == NULL || charStringIsEmpty(path)) { + logWarn("Cannot create empty file/directory name with parent"); + return NULL; + } else if (_pathContainsInvalidChars(path)) { + logWarn("Could not create file/directory with name '%s'", path->data); + freeFile(result); + return NULL; + } else if (_isAbsolutePath(path)) { + logWarn("Cannot create file '%s' with absolute directory under a parent", path->data); + freeFile(result); + return NULL; + } + + result = newFile(); + absolutePath = _buildAbsolutePath(parent->absolutePath, path); + charStringCopy(result->absolutePath, absolutePath); + freeCharString(absolutePath); + + if (fileExists(result)) { + result->fileType = _isDirectory(result->absolutePath) ? kFileTypeDirectory : kFileTypeFile; + } + + return result; +} + +boolByte fileExists(File self) +{ +#if WINDOWS + // Visual Studio's compiler is not C99 compliant, so variable declarations + // need to be at the top. + unsigned long fileAttributes; +#endif + + // Normally, we don't do paranoid sanity checks for self != NULL, but in this + // case it's useful since the old file API had a different calling convention + // which supported calling fileExists() with NULL. + if (self == NULL || self->absolutePath == NULL) { + return false; + } + +#if UNIX + struct stat fileStat; + boolByte result = (boolByte)(stat(self->absolutePath->data, &fileStat) == 0); + return result; + +#elif WINDOWS + fileAttributes = GetFileAttributesA((LPCSTR)self->absolutePath->data); + + if (fileAttributes == INVALID_FILE_ATTRIBUTES) { + return false; + } + + return true; +#else + return false; +#endif +} + +boolByte fileCreate(File self, const FileType fileType) +{ + if (fileExists(self)) { + return false; + } else if (charStringIsEmpty(self->absolutePath)) { + logWarn("Attempt to create file/directory without a path"); + return false; + } + + switch (fileType) { + case kFileTypeFile: + self->_fileHandle = fopen(self->absolutePath->data, "wb"); + + if (self->_fileHandle != NULL) { + self->fileType = kFileTypeFile; + self->_openMode = kFileOpenModeWrite; + return true; + } + + break; + + case kFileTypeDirectory: +#if UNIX + if (mkdir(self->absolutePath->data, 0755) == 0) { +#elif WINDOWS + + if (CreateDirectoryA(self->absolutePath->data, NULL)) { +#endif + self->fileType = kFileTypeDirectory; + return true; + } + + break; + + default: + break; + } + + return false; +} + +static File _copyFileToDirectory(File self, const File destination) { + File result = NULL; + CharString basename = NULL; + void *selfContents = NULL; + size_t selfSize = 0; + + // Close and re-open file to make sure that we start reading at the beginning + fileClose(self); + self->_fileHandle = fopen(self->absolutePath->data, "rb"); + + if (self->_fileHandle == NULL) { + logError("Could not re-open file for reading during copy"); + return NULL; + } + + basename = fileGetBasename(self); + + if (basename == NULL) { + logError("Could not get basename during copy"); + } else { + result = newFileWithParent(destination, basename); + freeCharString(basename); + + if (result == NULL) { + logError("Could not create destination file object"); + return NULL; + } + + if (!fileCreate(result, kFileTypeFile)) { + logError("Could not create destination file"); + freeFile(result); + return NULL; + } + + selfSize = fileGetSize(self); + + if (selfSize == 0) { + // If the source file is empty, then creating the result file is good + // enough and we can return here. + logDebug("Source file during copy is 0 bytes"); + return result; + } + + selfContents = fileReadBytes(self, selfSize); + + if (selfContents == NULL) { + logError("Could not read source file during copy"); + freeFile(result); + return NULL; + } + + if (!fileWriteBytes(result, selfContents, selfSize)) { + logError("Could not copy source file"); + freeFile(result); + return NULL; + } + } + + return result; +} + +static File _copyDirectoryToDirectory(File self, const File destination) { + File result = NULL; + // Get the basename first, because if it fails then there's no point in doing + // the actual copy. + CharString basename = fileGetBasename(self); +#if WINDOWS + SHFILEOPSTRUCT fileOperation; +#endif + + if (basename == NULL) { + logError("Could not get basename from directory during copy"); + return NULL; + } + +#if UNIX + // Using nftw() is a real pain here, because the recursive callback will start + // at the top of the tree and work back up, meaning that for each file we'd + // need to recursively mkdir the basename, etc. + // So this is a bit lazy but actually more foolproof + CharString copyCommand = newCharString(); + snprintf(copyCommand->data, copyCommand->capacity, "/bin/cp -r \"%s\" \"%s\"", + self->absolutePath->data, destination->absolutePath->data); + int systemResult = system(copyCommand->data); + freeCharString(copyCommand); + + if (WEXITSTATUS(systemResult) == 0) { + result = newFileWithParent(destination, basename); + + if (result == NULL) { + logError("Copied '%s' to '%s', but could not create a File object for the result", + self->absolutePath->data, destination->absolutePath->data); + freeCharString(basename); + return NULL; + } + } else { + logError("Could not copy directory '%s' to '%s'", + self->absolutePath->data, destination->absolutePath->data); + } + +#elif WINDOWS + memset(&fileOperation, 0, sizeof(fileOperation)); + fileOperation.wFunc = FO_COPY; + fileOperation.pFrom = self->absolutePath->data; + fileOperation.pTo = destination->absolutePath->data; + fileOperation.fFlags = FOF_NO_UI; + + if (SHFileOperationA(&fileOperation) == 0) { + result = newFileWithParent(destination, basename); + } + +#endif + + freeCharString(basename); + return result; +} + +File fileCopyTo(File self, const File destination) { + File result = NULL; + + if (destination == NULL || !fileExists(destination)) { + logError("Attempt to copy file/directory to invalid destination"); + return NULL; + } else if (!_isDirectory(destination->absolutePath)) { + logError("Attempt to copy file/directory to non-directory destination"); + return NULL; + } else if (!fileExists(self)) { + logError("Attempt to copy non-existent file"); + return NULL; + } + + switch (self->fileType) { + case kFileTypeFile: + result = _copyFileToDirectory(self, destination); + break; + + case kFileTypeDirectory: + result = _copyDirectoryToDirectory(self, destination); + break; + + default: + logError("Attempt to copy invalid file type"); + break; + } + + return result; +} + +#if UNIX +static int _removeCallback(const char *path, const struct stat * fileState, int typeflag, struct FTW * ftwBuffer) { + int result = remove(path); + + if (result != 0) { + logWarn("Could not remove '%s'", path); + } + + return result; +} +#endif + +boolByte fileRemove(File self) { + boolByte result = false; +#if WINDOWS + SHFILEOPSTRUCTA fileOperation = {0}; +#endif + + if (fileExists(self)) { + switch (self->fileType) { + case kFileTypeFile: + // Yes, this seems a bit silly, but otherwise we threaten to leak resources + fileClose(self); + result = (boolByte)(remove(self->absolutePath->data) == 0); + break; + + case kFileTypeDirectory: +#if UNIX + result = (boolByte)(nftw(self->absolutePath->data, _removeCallback, kFileMaxRecursionDepth, FTW_DEPTH | FTW_PHYS) == 0); +#elif WINDOWS + memset(&fileOperation, 0, sizeof(fileOperation)); + fileOperation.wFunc = FO_DELETE; + fileOperation.pFrom = self->absolutePath->data; + fileOperation.pTo = NULL; + fileOperation.fFlags = FOF_NO_UI | FOF_NOCONFIRMATION | FOF_SILENT; + result = (SHFileOperationA(&fileOperation) == 0); +#endif + break; + + default: + break; + } + } + + if (result) { + self->fileType = kFileTypeInvalid; + } + + return result; +} + +LinkedList fileListDirectory(File self) { + LinkedList items = newLinkedList(); + CharString path; + File file; + +#if UNIX + DIR *directoryPtr = opendir(self->absolutePath->data); + + if (directoryPtr == NULL) { + freeLinkedList(items); + return 0; + } + + struct dirent *entry; + + while ((entry = readdir(directoryPtr)) != NULL) { + if (entry->d_name[0] != '.') { + path = newCharStringWithCString(entry->d_name); + file = newFileWithParent(self, path); + linkedListAppend(items, file); + freeCharString(path); + } + } + + closedir(directoryPtr); + +#elif WINDOWS + WIN32_FIND_DATAA findData; + HANDLE findHandle; + CharString searchString = newCharStringWithCapacity(kFileNameStringLength); + + snprintf(searchString->data, searchString->capacity, "%s\\*", self->absolutePath->data); + findHandle = FindFirstFileA((LPCSTR)(searchString->data), &findData); + freeCharString(searchString); + + if (findHandle == INVALID_HANDLE_VALUE) { + freeLinkedList(items); + return 0; + } + + do { + if (findData.cFileName[0] != '.') { + path = newCharStringWithCString(findData.cFileName); + file = newFileWithParent(self, path); + linkedListAppend(items, file); + freeCharString(path); + } + } while (FindNextFileA(findHandle, &findData) != 0); + + FindClose(findHandle); + +#else + logUnsupportedFeature("List directory contents"); +#endif + + return items; +} + +size_t fileGetSize(File self) { + size_t result = 0; + +#if UNIX + struct stat fileStat; + + if (self->absolutePath == NULL) { + return 0; + } + + if (stat(self->absolutePath->data, &fileStat) == 0) { + if (S_ISREG(fileStat.st_mode)) { + // Yes, this will result in a loss of precision, but both freah and fwrite + // take a size_t argument, which is what this function is mostly used for. + result = (size_t)fileStat.st_size; + } + } + +#elif WINDOWS + HANDLE handle = CreateFileA(self->absolutePath->data, GENERIC_READ, 0, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (handle != INVALID_HANDLE_VALUE) { + result = GetFileSize(handle, NULL); + CloseHandle(handle); + } + +#else + logUnsupportedFeature("Get file size"); +#endif + + return result; +} + +CharString fileReadContents(File self) { + CharString result = NULL; + size_t fileSize = 0; + size_t itemsRead = 0; + + if (self->fileType != kFileTypeFile) { + logError("Attempt to read contents from non-file object '%s'", self->absolutePath->data); + return NULL; + } + + // Windows has problems opening files multiple times (which is done internally in + // fileGetSize() via CreateFile), so we must close the file first, calculate size, + // and then reopen it for reading. + fileClose(self); + fileSize = (size_t)fileGetSize(self); + fileClose(self); + + if (self->_fileHandle == NULL) { + self->_fileHandle = fopen(self->absolutePath->data, "rb"); + + if (self->_fileHandle == NULL) { + logError("Could not open '%s' for reading", self->absolutePath->data); + return NULL; + } else { + self->_openMode = kFileOpenModeRead; + } + } + + if (fileSize > 0) { + result = newCharStringWithCapacity(fileSize + 1); + itemsRead = fread(result->data, 1, fileSize, self->_fileHandle); + + if (itemsRead != fileSize) { + logError("Expected to read %d items, read %d items instead", fileSize, itemsRead); + } + } + + return result; +} + +LinkedList fileReadLines(File self) { + LinkedList result = NULL; + CharString line = NULL; + boolByte done = false; + char *newline = NULL; + + if (self->fileType != kFileTypeFile) { + logError("Attempt to read contents from non-file object '%s'", self->absolutePath->data); + return NULL; + } + + if (self->_openMode != kFileOpenModeRead && self->_fileHandle != NULL) { + fileClose(self); + } + + if (self->_fileHandle == NULL) { + self->_fileHandle = fopen(self->absolutePath->data, "rb"); + + if (self->_fileHandle == NULL) { + logError("Could not open '%s' for reading", self->absolutePath->data); + return NULL; + } else { + self->_openMode = kFileOpenModeRead; + } + } + + result = newLinkedList(); + + while (!done) { + line = newCharString(); + + if (fgets(line->data, (int)line->capacity, self->_fileHandle) == NULL) { + freeCharString(line); + done = true; + } else { + newline = strrchr(line->data, '\n'); + + if (newline != NULL) { + *newline = '\0'; + } + + // Also trim these characters, in case the file has Windows-style newlines + newline = strrchr(line->data, '\r'); + + if (newline != NULL) { + *newline = '\0'; + } + + linkedListAppend(result, line); + } + } + + return result; +} + +void *fileReadBytes(File self, size_t numBytes) { + void *result = NULL; + size_t itemsRead = 0; + + if (numBytes == 0) { + // Here we log an error (instead of an info message, as is done with the + // same type of error during writes) because the caller is probably + // expecting a non-NULL object back. + logError("Attempt to read 0 bytes from file"); + return NULL; + } + + if (self->fileType != kFileTypeFile) { + logError("Attempt to read bytes from non-file object '%s'", self->absolutePath->data); + return NULL; + } + + if (self->_openMode != kFileOpenModeRead && self->_fileHandle != NULL) { + fileClose(self); + } + + if (self->_fileHandle == NULL) { + self->_fileHandle = fopen(self->absolutePath->data, "rb"); + + if (self->_fileHandle == NULL) { + logError("Could not open '%s' for reading", self->absolutePath->data); + return NULL; + } else { + self->_openMode = kFileOpenModeRead; + } + } + + if (numBytes > 0) { + result = malloc(numBytes + 1); + memset(result, 0, numBytes + 1); + itemsRead = fread(result, 1, numBytes, self->_fileHandle); + + if (itemsRead != numBytes) { + logError("Expected to read %d items, read %d items instead", numBytes, itemsRead); + } + } + + return result; +} + +boolByte fileWrite(File self, const CharString data) { + return fileWriteBytes(self, data->data, strlen(data->data)); +} + +boolByte fileWriteBytes(File self, const void *data, size_t numBytes) { + size_t itemsWritten = 0; + + if (numBytes == 0) { + // Here we only log an info message, as this isn't such a critical error, + // but it is rather weird. + logInfo("Attempt to write 0 bytes to file"); + return false; + } + + if (!fileExists(self)) { + logError("Attempt to write to non-existent file"); + return false; + } + + if (self->_openMode != kFileOpenModeWrite && self->_fileHandle != NULL) { + fileClose(self); + } + + if (self->_fileHandle == NULL) { + self->_fileHandle = fopen(self->absolutePath->data, "wb"); + + if (self->_fileHandle == NULL) { + logError("Could not open '%s' for writing", self->absolutePath->data); + return false; + } else { + self->_openMode = kFileOpenModeWrite; + } + } + + itemsWritten = fwrite(data, 1, numBytes, self->_fileHandle); + return (boolByte)(itemsWritten == numBytes); +} + +CharString fileGetBasename(File self) { + CharString result = NULL; + char *lastDelimiter = NULL; + + if (self->absolutePath == NULL || charStringIsEmpty(self->absolutePath)) { + return NULL; + } + + lastDelimiter = strrchr(self->absolutePath->data, PATH_DELIMITER); + + if (lastDelimiter == NULL) { + result = newCharStringWithCString(self->absolutePath->data); + } else { + result = newCharStringWithCString(lastDelimiter + 1); + } + + return result; +} + +File fileGetParent(File self) { + File result = NULL; + CharString path = NULL; + char *lastDelimiter = NULL; + + if (self->absolutePath == NULL || charStringIsEmpty(self->absolutePath)) { + return NULL; + } + + lastDelimiter = strrchr(self->absolutePath->data, PATH_DELIMITER); + + if (lastDelimiter == NULL) { + result = newFileWithPath(self->absolutePath); + } else { + path = newCharStringWithCapacity(kFileNameStringLength); + strncpy(path->data, self->absolutePath->data, lastDelimiter - self->absolutePath->data); + result = newFileWithPath(path); + freeCharString(path); + } + + return result; +} + +CharString fileGetExtension(File self) { + CharString basename = fileGetBasename(self); + CharString result = NULL; + char *dot = NULL; + + if (basename == NULL || charStringIsEmpty(basename)) { + freeCharString(basename); + return NULL; + } + + dot = strrchr(basename->data, '.'); + + if (dot != NULL) { + result = newCharStringWithCString(dot + 1); + } + + freeCharString(basename); + return result; +} + +CharString fileGetExecutablePath(void) { + CharString executablePath = newCharString(); +#if LINUX + ssize_t result = readlink("/proc/self/exe", executablePath->data, executablePath->capacity); + + if (result < 0) { + logWarn("Could not find executable path, %s", stringForLastError(errno)); + return NULL; + } + +#elif MACOSX + _NSGetExecutablePath(executablePath->data, (uint32_t *)&executablePath->capacity); +#elif WINDOWS + GetModuleFileNameA(NULL, executablePath->data, (DWORD)executablePath->capacity); +#endif + return executablePath; +} + +CharString fileGetCurrentDirectory(void) { + CharString currentDirectory = newCharString(); +#if UNIX + + if (getcwd(currentDirectory->data, currentDirectory->capacity) == NULL) { + logError("Could not get current working directory"); + freeCharString(currentDirectory); + return NULL; + } + +#elif WINDOWS + GetCurrentDirectoryA((DWORD)currentDirectory->capacity, currentDirectory->data); +#endif + return currentDirectory; +} + +void fileClose(File self) { + if (self->_fileHandle != NULL && self->fileType == kFileTypeFile) { + fflush(self->_fileHandle); + fclose(self->_fileHandle); + self->_fileHandle = NULL; + self->_openMode = kFileOpenModeClosed; + } +} + +void freeFile(File self) { + if (self) { + fileClose(self); + freeCharString(self->absolutePath); + free(self); + } +} |
