summaryrefslogtreecommitdiff
path: root/source/kit/file.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/kit/file.c')
-rw-r--r--source/kit/file.c692
1 files changed, 692 insertions, 0 deletions
diff --git a/source/kit/file.c b/source/kit/file.c
new file mode 100644
index 0000000..e5b834f
--- /dev/null
+++ b/source/kit/file.c
@@ -0,0 +1,692 @@
+#include "file.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+enum { PATH_BUF_SIZE = 4096 };
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
+# ifndef NOMINMAX
+# define NOMINMAX
+# endif
+# include <windows.h>
+# include <shlwapi.h>
+#else
+# include <dirent.h>
+# include <sys/mman.h>
+# include <sys/stat.h>
+# include <fcntl.h>
+# include <unistd.h>
+# include <limits.h>
+#endif
+
+#ifdef __APPLE__
+# define st_mtim st_mtimespec
+#endif
+
+#ifndef PATH_MAX
+# define PATH_MAX MAX_PATH
+#endif
+
+static i32 is_delim(char c) {
+ return c == '/' || c == '\\';
+}
+
+kit_str_builder_t kit_get_env(kit_str_t name,
+ kit_allocator_t *alloc) {
+ char *val = getenv(BS(name));
+ i64 size = val != NULL ? (i64) strlen(val) : 0;
+
+ str_builder_t result;
+ DA_INIT(result, size, alloc);
+ assert(result.size == size);
+
+ if (result.size == size && size > 0)
+ memcpy(result.values, val, result.size);
+ else
+ DA_RESIZE(result, 0);
+
+ return result;
+}
+
+kit_str_builder_t kit_path_norm(kit_str_t path,
+ kit_allocator_t *alloc) {
+ str_t parent = SZ("..");
+ i64 i, i1, j;
+
+ str_builder_t norm;
+ DA_INIT(norm, path.size, alloc);
+ assert(norm.size == path.size);
+
+ if (norm.size != path.size)
+ return norm;
+
+ memcpy(norm.values, path.values, path.size);
+
+ for (i1 = 0, i = 0; i < path.size; i++) {
+ if (!is_delim(path.values[i]))
+ continue;
+
+ str_t s = { .size = i - i1 - 1, .values = path.values + i1 + 1 };
+ if (AR_EQUAL(s, parent)) {
+ i32 have_parent = 0;
+ i64 i0 = 0;
+
+ for (j = 0; j < i1; j++) {
+ if (norm.values[j] != '\0')
+ have_parent = 1;
+ if (is_delim(norm.values[j]))
+ i0 = j;
+ }
+
+ if (have_parent) {
+ memset(norm.values + i0, '\0', i - i0);
+
+ if (!is_delim(path.values[i0]))
+ norm.values[i] = '\0';
+ }
+ }
+
+ i1 = i;
+ }
+
+ i64 size = 0;
+
+ for (i = 0; i < norm.size; i++) {
+ if (norm.values[i] != '\0') {
+ if (is_delim(norm.values[i]))
+ norm.values[size] = KIT_PATH_DELIM_C;
+ else
+ norm.values[size] = norm.values[i];
+ size++;
+ }
+ }
+
+ norm.size = size;
+ return norm;
+}
+
+kit_str_builder_t kit_path_join(kit_str_t left, kit_str_t right,
+ kit_allocator_t *alloc) {
+ i64 left_size = left.size;
+ i64 right_size = right.size;
+ char *right_values = right.values;
+
+ if (left_size > 0 && is_delim(left.values[left_size - 1]))
+ left_size--;
+ if (right_size > 0 && is_delim(right.values[0])) {
+ right_size--;
+ right_values++;
+ }
+
+ kit_str_builder_t joined;
+ DA_INIT(joined, left_size + right_size + 1, alloc);
+ assert(joined.size == left_size + right_size + 1);
+
+ if (joined.size != left_size + right_size + 1)
+ return joined;
+
+ memcpy(joined.values, left.values, left_size);
+ joined.values[left_size] = KIT_PATH_DELIM_C;
+ memcpy(joined.values + left_size + 1, right_values, right_size);
+
+ return joined;
+}
+
+kit_str_builder_t kit_path_user(kit_allocator_t *alloc) {
+ kit_str_builder_t user = kit_get_env(SZ(KIT_ENV_HOME), alloc);
+ if (user.size == 0) {
+ DA_RESIZE(user, 1);
+ if (user.size == 1)
+ user.values[0] = '.';
+ }
+ return user;
+}
+
+kit_str_builder_t kit_path_cache(kit_allocator_t *alloc) {
+ kit_str_builder_t cache, user;
+
+ cache = kit_get_env(SZ("XDG_CACHE_HOME"), alloc);
+ if (cache.size != 0)
+ return cache;
+ DA_DESTROY(cache);
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ cache = kit_get_env(SZ("TEMP"), alloc);
+ if (cache.size != 0)
+ return cache;
+ DA_DESTROY(cache);
+#endif
+
+ user = kit_path_user(alloc);
+ cache =
+#ifdef __APPLE__
+ kit_path_join(WRAP_STR(user), SZ("Library" PATH_DELIM "Caches"),
+ alloc);
+#else
+ kit_path_join(WRAP_STR(user), SZ(".cache"), alloc);
+#endif
+ DA_DESTROY(user);
+
+ return cache;
+}
+
+kit_str_builder_t kit_path_data(kit_allocator_t *alloc) {
+ kit_str_builder_t data, user;
+
+ data = kit_get_env(SZ("XDG_DATA_HOME"), alloc);
+ if (data.size != 0)
+ return data;
+ DA_DESTROY(data);
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ data = kit_get_env(SZ("LOCALAPPDATA"), alloc);
+ if (data.size != 0)
+ return data;
+ DA_DESTROY(data);
+#endif
+
+ user = kit_path_user(alloc);
+ data =
+#ifdef __APPLE__
+ kit_path_join(WRAP_STR(user), SZ("Library"), alloc);
+#else
+ kit_path_join(WRAP_STR(user), SZ(".local" PATH_DELIM "share"),
+ alloc);
+#endif
+ DA_DESTROY(user);
+
+ return data;
+}
+
+kit_str_t kit_path_index(kit_str_t path, i64 index) {
+ str_t s = { .size = 0, .values = NULL };
+
+ i64 i0 = 0;
+ i64 i = 0;
+ i64 n = 0;
+
+ for (; i < path.size; i++) {
+ if (!is_delim(path.values[i]))
+ continue;
+
+ if (i0 < i) {
+ if (n++ == index) {
+ s.values = path.values + i0;
+ s.size = i - i0;
+ return s;
+ }
+ }
+
+ i0 = i + 1;
+ }
+
+ if (n == index) {
+ s.values = path.values + i0;
+ s.size = i - i0;
+ }
+
+ return s;
+}
+
+kit_str_t kit_path_take(kit_str_t path, i64 count) {
+ str_t s = { .size = 0, .values = path.values };
+
+ i64 i0 = 0;
+ i64 i = 0;
+ i64 n = 0;
+
+ for (; i < path.size; i++) {
+ if (!is_delim(path.values[i]))
+ continue;
+
+ if (i0 < i) {
+ if (n++ == count) {
+ s.size = i;
+ return s;
+ }
+ }
+
+ i0 = i + 1;
+ }
+
+ if (n == count)
+ s.size = i;
+
+ return s;
+}
+
+// TODO
+// Long path support for Windows
+//
+static void kit_prepare_path_(char *buf, kit_str_t path) {
+ assert(path.size == 0 || path.values != NULL);
+ assert(path.size + 1 < PATH_BUF_SIZE);
+
+ memset(buf, 0, PATH_BUF_SIZE);
+ if (path.size > 0 && path.size + 1 < PATH_BUF_SIZE)
+ memcpy(buf, path.values, path.size);
+}
+
+#define PREPARE_PATH_BUF_ \
+ char buf[PATH_BUF_SIZE]; \
+ kit_prepare_path_(buf, path)
+
+kit_status_t kit_folder_create(kit_str_t path) {
+ PREPARE_PATH_BUF_;
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ return CreateDirectoryA(buf, NULL) ? KIT_OK
+ : KIT_ERROR_MKDIR_FAILED;
+#else
+ return mkdir(buf, 0775) == 0 ? KIT_OK : KIT_ERROR_MKDIR_FAILED;
+#endif
+}
+
+kit_status_t kit_folder_create_recursive(kit_str_t path) {
+ for (i32 i = 0;; i++) {
+ str_t part = kit_path_take(path, i);
+ i32 type = kit_path_type(part);
+ if (type == KIT_PATH_FILE)
+ return KIT_ERROR_FILE_ALREADY_EXISTS;
+ if (type == KIT_PATH_NONE) {
+ kit_status_t s = kit_folder_create(part);
+ if (s != KIT_OK)
+ return s;
+ }
+ if (part.size == path.size)
+ break;
+ }
+
+ return KIT_OK;
+}
+
+kit_status_t kit_file_remove(kit_str_t path) {
+ PREPARE_PATH_BUF_;
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ return DeleteFileA(buf) ? KIT_OK : KIT_ERROR_UNLINK_FAILED;
+#else
+ return unlink(buf) == 0 ? KIT_OK : KIT_ERROR_UNLINK_FAILED;
+#endif
+}
+
+kit_status_t kit_folder_remove(kit_str_t path) {
+ PREPARE_PATH_BUF_;
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ return RemoveDirectoryA(buf) ? KIT_OK : KIT_ERROR_RMDIR_FAILED;
+#else
+ return rmdir(buf) == 0 ? KIT_OK : KIT_ERROR_RMDIR_FAILED;
+#endif
+}
+
+kit_status_t kit_file_remove_recursive(kit_str_t path,
+ kit_allocator_t *alloc) {
+ i32 type = kit_path_type(path);
+ i64 i;
+
+ switch (type) {
+ case KIT_PATH_FILE: {
+ kit_status_t s = kit_file_remove(path);
+ assert(s == KIT_OK);
+ return s;
+ }
+
+ case KIT_PATH_FOLDER: {
+ kit_path_list_t list = kit_folder_enum(path, alloc);
+
+ assert(list.status == KIT_OK);
+ if (list.status != KIT_OK) {
+ kit_path_list_destroy(list);
+ return list.status;
+ }
+
+ for (i = 0; i < list.files.size; i++) {
+ str_builder_t full_path = kit_path_join(
+ path, WRAP_STR(list.files.values[i]), alloc);
+ kit_status_t s = kit_file_remove_recursive(
+ WRAP_STR(full_path), alloc);
+ DA_DESTROY(full_path);
+ assert(s == KIT_OK);
+ }
+
+ kit_path_list_destroy(list);
+
+ kit_status_t s = kit_folder_remove(path);
+ assert(s == KIT_OK);
+ return s;
+ }
+
+ default:;
+ }
+
+ return KIT_ERROR_FILE_DOES_NOT_EXIST;
+}
+
+kit_path_type_t kit_path_type(kit_str_t path) {
+ PREPARE_PATH_BUF_;
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ if (PathFileExistsA(buf)) {
+ if ((GetFileAttributesA(buf) & FILE_ATTRIBUTE_DIRECTORY) != 0)
+ return KIT_PATH_FOLDER;
+ else
+ return KIT_PATH_FILE;
+ }
+#else
+ struct stat info;
+ if (stat(buf, &info) == 0) {
+ if (S_ISREG(info.st_mode))
+ return KIT_PATH_FILE;
+ if (S_ISDIR(info.st_mode))
+ return KIT_PATH_FOLDER;
+ }
+#endif
+ return KIT_PATH_NONE;
+}
+
+kit_file_info_t kit_file_info(kit_str_t path) {
+ kit_file_info_t result;
+ memset(&result, 0, sizeof result);
+
+ PREPARE_PATH_BUF_;
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ HANDLE f = CreateFileA(buf, GENERIC_READ, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (f != INVALID_HANDLE_VALUE) {
+ FILETIME ft;
+ if (GetFileTime(f, NULL, NULL, &ft) != 0) {
+ i64 nsec100 = (((u64) ft.dwHighDateTime) << 32) |
+ (u64) ft.dwLowDateTime;
+ result.time_modified_sec = (i64) (nsec100 / 10000000);
+ result.time_modified_nsec = (i32) (100 * (nsec100 % 10000000));
+ } else {
+ assert(0);
+ }
+
+ DWORD high;
+ DWORD low = GetFileSize(f, &high);
+
+ result.size = (i64) ((((u64) high) << 32) | (u64) low);
+ result.status = KIT_OK;
+
+ CloseHandle(f);
+ return result;
+ }
+#else
+ struct stat info;
+ if (stat(buf, &info) == 0 && S_ISREG(info.st_mode)) {
+ result.size = (i64) info.st_size;
+# ifndef st_mtime
+ // No support for nanosecond timestamps.
+ //
+ result.time_modified_sec = (i64) info.st_mtime;
+# else
+ result.time_modified_sec = (i64) info.st_mtim.tv_sec;
+ result.time_modified_nsec = (i32) info.st_mtim.tv_nsec;
+# endif
+ result.status = KIT_OK;
+ return result;
+ }
+#endif
+
+ result.status = KIT_ERROR_FILE_DOES_NOT_EXIST;
+ return result;
+}
+
+kit_path_list_t kit_folder_enum(kit_str_t path,
+ kit_allocator_t *alloc) {
+ PREPARE_PATH_BUF_;
+
+ kit_path_list_t result = { .status = KIT_OK };
+ DA_INIT(result.files, 0, alloc);
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ if (path.size + 3 >= PATH_BUF_SIZE) {
+ result.status = KIT_ERROR_PATH_TOO_LONG;
+ return result;
+ }
+
+ buf[path.size] = '\\';
+ buf[path.size + 1] = '*';
+
+ WIN32_FIND_DATAA data;
+ HANDLE find = FindFirstFileA(buf, &data);
+
+ if (find == INVALID_HANDLE_VALUE)
+ return result;
+
+ do {
+ if (strcmp(data.cFileName, ".") == 0 ||
+ strcmp(data.cFileName, "..") == 0)
+ continue;
+
+ i64 n = result.files.size;
+ DA_RESIZE(result.files, n + 1);
+ if (result.files.size != n + 1) {
+ result.status = KIT_ERROR_BAD_ALLOC;
+ break;
+ }
+
+ i64 size = (i64) strlen(data.cFileName);
+ DA_INIT(result.files.values[n], size, alloc);
+ if (result.files.values[n].size != size) {
+ DA_RESIZE(result.files, n);
+ result.status = KIT_ERROR_BAD_ALLOC;
+ break;
+ }
+
+ memcpy(result.files.values[n].values, data.cFileName, size);
+ } while (FindNextFileA(find, &data) != 0);
+
+ FindClose(find);
+#else
+ DIR *directory = opendir(buf);
+
+ if (directory == NULL)
+ return result;
+
+ for (;;) {
+ struct dirent *entry = readdir(directory);
+
+ if (entry == NULL)
+ break;
+
+ if (entry->d_name[0] == '.')
+ continue;
+
+ i64 n = result.files.size;
+ DA_RESIZE(result.files, n + 1);
+ if (result.files.size != n + 1) {
+ result.status = KIT_ERROR_BAD_ALLOC;
+ break;
+ }
+
+ i64 size = (i64) strlen(entry->d_name);
+ DA_INIT(result.files.values[n], size, alloc);
+ if (result.files.values[n].size != size) {
+ DA_RESIZE(result.files, n);
+ result.status = KIT_ERROR_BAD_ALLOC;
+ break;
+ }
+
+ if (size > 0)
+ memcpy(result.files.values[n].values, entry->d_name, size);
+ }
+
+ closedir(directory);
+#endif
+
+ return result;
+}
+
+void kit_path_list_destroy(kit_path_list_t list) {
+ i64 i;
+ for (i = 0; i < list.files.size; i++)
+ DA_DESTROY(list.files.values[i]);
+ DA_DESTROY(list.files);
+}
+
+kit_mapped_file_t kit_file_map(kit_str_t path, i64 size, i32 mode) {
+ assert(size > 0);
+ assert(path.size > 0);
+ assert(path.size <= PATH_MAX);
+ assert(path.values != NULL);
+
+ kit_mapped_file_t mf;
+ memset(&mf, 0, sizeof mf);
+
+ if (size <= 0) {
+ mf.status = KIT_ERROR_INVALID_SIZE;
+ return mf;
+ }
+
+ if (path.size <= 0) {
+ mf.status = KIT_ERROR_INVALID_ARGUMENT;
+ return mf;
+ }
+
+ if (path.size > PATH_MAX) {
+ mf.status = KIT_ERROR_PATH_TOO_LONG;
+ return mf;
+ }
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ char buf[MAX_PATH + 1];
+ memcpy(buf, path.values, path.size);
+ buf[path.size] = '\0';
+
+ HANDLE file = CreateFileA(
+ buf, GENERIC_READ | GENERIC_WRITE,
+ mode == FILE_MAP_SHARED ? FILE_SHARE_READ | FILE_SHARE_WRITE
+ : 0,
+ NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (file == INVALID_HANDLE_VALUE) {
+ mf.status = KIT_ERROR_OPEN_FAILED;
+ return mf;
+ }
+
+ LONG high = (LONG) (size >> 32);
+
+ if (SetFilePointer(file, (LONG) size, &high, FILE_BEGIN) ==
+ INVALID_SET_FILE_POINTER) {
+ CloseHandle(file);
+ assert(0);
+ mf.status = KIT_ERROR_TRUNCATE_FAILED;
+ return mf;
+ }
+
+ if (!SetEndOfFile(file)) {
+ CloseHandle(file);
+ assert(0);
+ mf.status = KIT_ERROR_TRUNCATE_FAILED;
+ return mf;
+ }
+
+ HANDLE map = CreateFileMappingA(file, NULL, PAGE_READWRITE,
+ (DWORD) (size >> 32), (DWORD) size,
+ NULL);
+
+ if (map == INVALID_HANDLE_VALUE) {
+ CloseHandle(file);
+ assert(0);
+ mf.status = KIT_ERROR_MAP_FAILED;
+ return mf;
+ }
+
+ void *p = MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0,
+ (SIZE_T) size);
+
+ if (p == NULL) {
+ CloseHandle(map);
+ CloseHandle(file);
+ assert(0);
+ mf.status = KIT_ERROR_MAP_FAILED;
+ return mf;
+ }
+
+ mf.status = KIT_OK;
+ mf.size = size;
+ mf.bytes = p;
+ mf._file = file;
+ mf._map = map;
+#else
+ char buf[PATH_MAX + 1];
+ memcpy(buf, path.values, path.size);
+ buf[path.size] = '\0';
+
+ i32 fd = open(buf, O_RDWR | O_CREAT, 0664);
+
+ if (fd == -1) {
+ mf.status = KIT_ERROR_OPEN_FAILED;
+ return mf;
+ }
+
+ if (ftruncate(fd, size) == -1) {
+ close(fd);
+ assert(0);
+ mf.status = KIT_ERROR_TRUNCATE_FAILED;
+ return mf;
+ }
+
+ void *p = mmap(
+ NULL, size, PROT_READ | PROT_WRITE,
+ mode == KIT_FILE_MAP_SHARED ? MAP_SHARED : MAP_PRIVATE, fd, 0);
+
+ if (p == MAP_FAILED) {
+ close(fd);
+ assert(0);
+ mf.status = KIT_ERROR_MAP_FAILED;
+ return mf;
+ }
+
+ mf.status = KIT_OK;
+ mf.size = size;
+ mf.bytes = (u8 *) p;
+ mf._fd = fd;
+#endif
+
+ return mf;
+}
+
+kit_status_t kit_file_sync(kit_mapped_file_t *mf) {
+ assert(mf != NULL);
+
+ if (mf == NULL)
+ return KIT_ERROR_INVALID_ARGUMENT;
+
+ kit_status_t status = KIT_OK;
+
+#if !defined(_WIN32) || defined(__CYGWIN__)
+ if (msync(mf->bytes, mf->size, MS_SYNC) != 0)
+ status |= KIT_ERROR_SYNC_FAILED;
+#endif
+
+ return status;
+}
+
+kit_status_t kit_file_unmap(kit_mapped_file_t *mf) {
+ assert(mf != NULL);
+
+ if (mf == NULL)
+ return KIT_ERROR_INVALID_ARGUMENT;
+
+ kit_status_t status = KIT_OK;
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ if (!UnmapViewOfFile(mf->bytes))
+ status |= KIT_ERROR_UNMAP_FAILED;
+ if (!CloseHandle(mf->_map) || !CloseHandle(mf->_file))
+ status |= KIT_ERROR_CLOSE_FAILED;
+#else
+ if (munmap(mf->bytes, mf->size) != 0)
+ status |= KIT_ERROR_UNMAP_FAILED;
+ if (close(mf->_fd) != 0)
+ status |= KIT_ERROR_CLOSE_FAILED;
+#endif
+
+ return status;
+}