From 17ef5e24ad6642728bea54779dd8ff8ee45c175f Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Mon, 26 Sep 2022 21:44:57 +0400 Subject: Filesystem --- source/kit/CMakeLists.txt | 6 +- source/kit/dynamic_array.h | 3 + source/kit/file.c | 417 +++++++++++++++++++++++++++++++++++ source/kit/file.h | 83 +++++++ source/kit/options.h | 16 ++ source/kit/threads.win32.c | 2 +- source/test/unittests/CMakeLists.txt | 2 +- source/test/unittests/file.test.c | 198 +++++++++++++++++ 8 files changed, 723 insertions(+), 4 deletions(-) create mode 100644 source/kit/file.c create mode 100644 source/kit/file.h create mode 100644 source/kit/options.h create mode 100644 source/test/unittests/file.test.c diff --git a/source/kit/CMakeLists.txt b/source/kit/CMakeLists.txt index 7bc3ba5..1dbaaf8 100644 --- a/source/kit/CMakeLists.txt +++ b/source/kit/CMakeLists.txt @@ -3,8 +3,8 @@ target_sources( PRIVATE input_buffer.c threads.win32.c time.c atomic.win32.c threads.posix.c condition_variable.c move_back.c input_stream.c - lower_bound.c string_ref.c async_function.c allocator.c array_ref.c - dynamic_array.c mutex.c mersenne_twister_64.c + lower_bound.c file.c string_ref.c async_function.c allocator.c + array_ref.c dynamic_array.c mutex.c mersenne_twister_64.c PUBLIC $ $ @@ -19,5 +19,7 @@ target_sources( $ $ $ + $ + $ $ $) diff --git a/source/kit/dynamic_array.h b/source/kit/dynamic_array.h index 409e7df..cc35a28 100644 --- a/source/kit/dynamic_array.h +++ b/source/kit/dynamic_array.h @@ -101,10 +101,13 @@ void kit_da_resize(kit_da_void_t *array, ptrdiff_t element_size, KIT_DA_RESIZE((array_), (array_).size - 1); \ } while (0) +KIT_DA_TYPE(kit_string_t, char); + #ifndef KIT_DISABLE_SHORT_NAMES # define da_void_t kit_da_void_t # define da_init kit_da_init # define da_resize kit_da_resize +# define string_t kit_string_t # define DA_TYPE KIT_DA_TYPE # define DA KIT_DA diff --git a/source/kit/file.c b/source/kit/file.c new file mode 100644 index 0000000..17aae79 --- /dev/null +++ b/source/kit/file.c @@ -0,0 +1,417 @@ +#include "file.h" + +#include +#include +#include + +enum { PATH_BUF_SIZE = 4096 }; + +#if defined(_WIN32) && !defined(__CYGWIN__) +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN 1 +# endif +# include +#else +# include +# include +# include +#endif + +static int is_delim(char const c) { + return c == '/' || c == '\\'; +} + +kit_string_t kit_path_norm(kit_str_t const path, + kit_allocator_t const alloc) { + SZ(parent, ".."); + + kit_string_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 (ptrdiff_t i1 = 0, i = 0; i < path.size; i++) { + if (!is_delim(path.values[i])) + continue; + + str_t const s = { .size = i - i1 - 1, + .values = path.values + i1 + 1 }; + if (AR_EQUAL(s, parent)) { + int have_parent = 0; + ptrdiff_t i0 = 0; + + for (ptrdiff_t 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; + } + + ptrdiff_t size = 0; + + for (ptrdiff_t i = 0; i < norm.size; i++) { + if (norm.values[i] != '\0') { + if (is_delim(norm.values[i])) + norm.values[size] = KIT_PATH_DELIM; + else + norm.values[size] = norm.values[i]; + size++; + } + } + + norm.size = size; + return norm; +} + +kit_string_t kit_path_join(kit_str_t const left, + kit_str_t const right, + kit_allocator_t const alloc) { + ptrdiff_t left_size = left.size; + ptrdiff_t right_size = right.size; + char const *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_string_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; + memcpy(joined.values + left_size + 1, right_values, right_size); + + return joined; +} + +kit_string_t kit_path_user(kit_allocator_t const alloc) { + char *home = getenv(KIT_ENV_HOME); + ptrdiff_t const size = home != NULL ? (ptrdiff_t) strlen(home) : 0; + + string_t user; + DA_INIT(user, size, alloc); + assert(user.size == size); + + if (user.size > 0) + memcpy(user.values, home, user.size); + else { + DA_RESIZE(user, 1); + assert(user.size == 1); + + if (user.size == 1) + user.values[0] = '.'; + } + + return user; +} + +kit_str_t kit_path_index(kit_str_t const path, + ptrdiff_t const index) { + str_t s = { .size = 0, .values = NULL }; + + ptrdiff_t i0 = 0; + ptrdiff_t i = 0; + ptrdiff_t 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 const path, ptrdiff_t const count) { + str_t s = { .size = 0, .values = path.values }; + + ptrdiff_t i0 = 0; + ptrdiff_t i = 0; + ptrdiff_t 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; +} + +#if defined(_WIN32) && !defined(__CYGWIN__) +static void win32_prepare_path_(WCHAR *const buf, + kit_str_t const path) { + memset(buf, 0, PATH_BUF_SIZE); + buf[0] = L'\\'; + buf[1] = L'\\'; + buf[2] = L'?'; + buf[3] = L'\\'; + if (path.size > 0 && path.size + 5 < PATH_BUF_SIZE) + for (ptrdiff_t i = 0; i < path.size; i++) { + if (path.values[i] == '/') + buf[4 + i] = L'\\'; + else + buf[4 + i] = path.values[i]; + } +} +# define PREPARE_PATH_BUF_ \ + WCHAR buf[PATH_BUF_SIZE]; \ + win32_prepare_path_(buf, path) +#else +static void unix_prepare_path_(char *const buf, + kit_str_t const path) { + 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]; \ + unix_prepare_path_(buf, path) +#endif + +kit_status_t kit_file_create_folder(kit_str_t const path) { + PREPARE_PATH_BUF_; +#if defined(_WIN32) && !defined(__CYGWIN__) + if (CreateDirectoryW(temp, NULL)) + return KIT_OK; +#else + if (mkdir(buf, 0755) == 0) + return KIT_OK; +#endif + return KIT_ERROR; +} + +kit_status_t kit_file_create_folder_recursive(kit_str_t const path) { + for (ptrdiff_t i = 0;; i++) { + str_t const part = kit_path_take(path, i); + int const type = kit_path_type(part); + if (type == KIT_PATH_FILE) + return KIT_ERROR; + if (type == KIT_PATH_NONE) { + kit_status_t const s = kit_file_create_folder(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 const path) { + PREPARE_PATH_BUF_; +#if defined(_WIN32) && !defined(__CYGWIN__) + if (DeleteFileW(buf)) + return KIT_OK; +#else + if (unlink(buf) == 0) + return KIT_OK; +#endif + return KIT_ERROR; +} + +kit_status_t kit_file_remove_folder(kit_str_t const path) { + PREPARE_PATH_BUF_; +#if defined(_WIN32) && !defined(__CYGWIN__) + if (DeleteDirectoryW(buf)) + return KIT_OK; +#else + if (rmdir(buf) == 0) + return KIT_OK; +#endif + return KIT_ERROR; +} + +kit_status_t kit_file_remove_recursive(kit_str_t const path, + kit_allocator_t const alloc) { + int type = kit_path_type(path); + + switch (type) { + case KIT_PATH_FILE: return kit_file_remove(path); + + case KIT_PATH_FOLDER: { + kit_path_list_t list = kit_file_enum_folder(path, alloc); + for (ptrdiff_t i = 0; i < list.size; i++) { + str_t const s = { .size = list.values[i].size, + .values = list.values[i].values }; + kit_file_remove_recursive(s, alloc); + } + kit_path_list_destroy(list); + return kit_file_remove_folder(path); + } + + default:; + } + + return KIT_ERROR; +} + +int kit_path_type(kit_str_t const path) { + PREPARE_PATH_BUF_; +#if defined(_WIN32) && !defined(__CYGWIN__) + if (PathFileExistsW(buf)) { + if ((GetFileAttributesW(buf) & FILE_ATTRIBUTE_DIRECTORY) != 0) + return KIT_PATH_FOLDER; + else + return KIT_PATH_FILE; + } +#else + struct stat info; + if (stat(buf, &info) == 0) { + if ((info.st_mode & S_IFREG) != 0) + return KIT_PATH_FILE; + if ((info.st_mode & S_IFDIR) != 0) + return KIT_PATH_FOLDER; + } +#endif + return KIT_PATH_NONE; +} + +ptrdiff_t kit_file_size(kit_str_t const path) { + PREPARE_PATH_BUF_; +#if defined(_WIN32) && !defined(__CYGWIN__) + HFILE f = CreateFileW(buf, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL); + if (f != INVALID_HANDLE_VALUE) { + DWORD high; + DWORD low = GetFileSize(f, &high); + CloseHandle(f); + return (ptrdiff_t) (((uint64_t) high << 32) | (uint64_t) low); + } +#else + struct stat info; + if (stat(buf, &info) == 0) + return info.st_size; +#endif + return KIT_FILE_SIZE_ERROR; +} + +kit_path_list_t kit_file_enum_folder(kit_str_t const path, + kit_allocator_t const alloc) { + PREPARE_PATH_BUF_; + + kit_path_list_t list; + DA_INIT(list, 0, alloc); + +#if defined(_WIN32) && !defined(__CYGWIN__) + if (path.size + 7 >= PATH_BUF_SIZE) + return list; + + buf[path.size + 4] = '\\'; + buf[path.size + 5] = '*'; + + WIN32_FIND_DATAW data; + HANDLE find = FindFirstFileW(buf, &data); + + if (find == INVALID_HANDLE_VALUE) + return list; + + do { + ptrdiff_t const n = list.size; + DA_RESIZE(list, n + 1); + if (list.size != n + 1) + break; + + ptrdiff_t size = 0; + while (size < MAX_PATH && data.cFileName[size] != L'\0') size++; + DA_INIT(list.values[n], size, alloc); + if (list.values[n].size != size) { + DA_RESIZE(list, n); + break; + } + + for (ptrdiff_t i = 0; i < size; i++) + list.values[n].values[i] = data.cFileName[i]; + } while (FindNextFileW(find, &data) != 0); + + FindClose(find); +#else + DIR *directory = opendir(buf); + + if (directory == NULL) + return list; + + for (;;) { + struct dirent *entry = readdir(directory); + + if (entry == NULL) + break; + + if (entry->d_name[0] == '.') + continue; + + ptrdiff_t const n = list.size; + DA_RESIZE(list, n + 1); + if (list.size != n + 1) + break; + + ptrdiff_t const size = (ptrdiff_t) strlen(entry->d_name); + DA_INIT(list.values[n], size, alloc); + if (list.values[n].size != size) { + DA_RESIZE(list, n); + break; + } + + if (size > 0) + memcpy(list.values[n].values, entry->d_name, size); + } + + closedir(directory); +#endif + + return list; +} + +void kit_path_list_destroy(kit_path_list_t list) { + for (ptrdiff_t i = 0; i < list.size; i++) + DA_DESTROY(list.values[i]); + DA_DESTROY(list); +} diff --git a/source/kit/file.h b/source/kit/file.h new file mode 100644 index 0000000..6313c2e --- /dev/null +++ b/source/kit/file.h @@ -0,0 +1,83 @@ +#ifndef KIT_FILE_H +#define KIT_FILE_H + +#include "dynamic_array.h" +#include "options.h" +#include "string_ref.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WIN32) && !defined(__CYGWIN__) +# define KIT_PATH_DELIM '\\' +# define KIT_ENV_HOME "USERPROFILE" +#else +# define KIT_PATH_DELIM '/' +# define KIT_ENV_HOME "HOME" +#endif + +enum { + KIT_PATH_NONE, + KIT_PATH_FILE, + KIT_PATH_FOLDER, + KIT_FILE_SIZE_ERROR = -1 +}; + +kit_string_t kit_path_norm(kit_str_t path, kit_allocator_t alloc); + +kit_string_t kit_path_join(kit_str_t left, kit_str_t right, + kit_allocator_t alloc); + +kit_string_t kit_path_user(kit_allocator_t alloc); + +kit_str_t kit_path_index(kit_str_t path, ptrdiff_t index); + +kit_str_t kit_path_take(kit_str_t path, ptrdiff_t count); + +kit_status_t kit_file_create_folder(kit_str_t path); + +kit_status_t kit_file_create_folder_recursive(kit_str_t path); + +kit_status_t kit_file_remove(kit_str_t path); + +kit_status_t kit_file_remove_folder(kit_str_t path); + +kit_status_t kit_file_remove_recursive(kit_str_t path, + kit_allocator_t alloc); + +int kit_path_type(kit_str_t path); + +ptrdiff_t kit_file_size(kit_str_t path); + +DA_TYPE(kit_path_list_t, kit_string_t); + +kit_path_list_t kit_file_enum_folder(kit_str_t path, + kit_allocator_t alloc); + +void kit_path_list_destroy(kit_path_list_t list); + +#ifndef KIT_DISABLE_SHORT_NAMES +# define path_norm kit_path_norm +# define path_join kit_path_join +# define path_user kit_path_user +# define path_index kit_path_index +# define path_take kit_path_take +# define file_create_folder kit_file_create_folder +# define file_create_folder_recursive \ + kit_file_create_folder_recursive +# define file_remove kit_file_remove +# define file_remove_folder kit_file_remove_folder +# define file_remove_recursive kit_file_remove_recursive +# define path_type kit_path_type +# define file_size kit_file_size +# define path_list_t kit_path_list_t +# define file_enum_folder kit_file_enum_folder +# define path_list_destroy kit_path_list_destroy +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/kit/options.h b/source/kit/options.h new file mode 100644 index 0000000..d90ea5b --- /dev/null +++ b/source/kit/options.h @@ -0,0 +1,16 @@ +#ifndef KIT_OPTIONS_H +#define KIT_OPTIONS_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum { KIT_OK, KIT_ERROR }; + +typedef int kit_status_t; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/kit/threads.win32.c b/source/kit/threads.win32.c index 0f54d6c..534b01c 100644 --- a/source/kit/threads.win32.c +++ b/source/kit/threads.win32.c @@ -45,7 +45,7 @@ # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN 1 # endif -# include +# include /* Configuration macro: diff --git a/source/test/unittests/CMakeLists.txt b/source/test/unittests/CMakeLists.txt index 29dd1b2..fe4c902 100644 --- a/source/test/unittests/CMakeLists.txt +++ b/source/test/unittests/CMakeLists.txt @@ -5,4 +5,4 @@ target_sources( main.test.c string_ref.test.c atomic.test.c thread.test.c array_ref.test.c input_stream.test.c lower_bound.test.c condition_variable.test.c mersenne_twister_64.test.c input_buffer.test.c - move_back.test.c dynamic_array.test.c) + move_back.test.c dynamic_array.test.c file.test.c) diff --git a/source/test/unittests/file.test.c b/source/test/unittests/file.test.c new file mode 100644 index 0000000..d6b72f6 --- /dev/null +++ b/source/test/unittests/file.test.c @@ -0,0 +1,198 @@ +#include "../../kit/file.h" +#include "../../kit/string_ref.h" +#include + +#define KIT_TEST_FILE file +#include "../../kit_test/test.h" + +#if defined(_WIN32) && !defined(__CYGWIN__) +# define S_DELIM_ "\\" +#else +# define S_DELIM_ "/" +#endif + +TEST("file path normalize one") { + SZ(foo, "foo/bar/../baz"); + SZ(foo_norm, "foo" S_DELIM_ "baz"); + + string_t bar = path_norm(foo, kit_alloc_default()); + + REQUIRE(AR_EQUAL(foo_norm, bar)); + + DA_DESTROY(bar); +} + +TEST("file path normalize two") { + SZ(foo, "foo/bar/../../baz"); + SZ(foo_norm, "baz"); + + string_t bar = path_norm(foo, kit_alloc_default()); + + REQUIRE(AR_EQUAL(foo_norm, bar)); + + DA_DESTROY(bar); +} + +TEST("file path normalize parent") { + SZ(foo, "../baz"); + SZ(foo_norm, ".." S_DELIM_ "baz"); + + string_t bar = path_norm(foo, kit_alloc_default()); + + REQUIRE(AR_EQUAL(foo_norm, bar)); + + DA_DESTROY(bar); +} + +TEST("file path normalize double parent") { + SZ(foo, "foo/../../baz"); + SZ(foo_norm, ".." S_DELIM_ "baz"); + + string_t bar = path_norm(foo, kit_alloc_default()); + + REQUIRE(AR_EQUAL(foo_norm, bar)); + + DA_DESTROY(bar); +} + +TEST("file path normalize windows delim") { + SZ(foo, "foo\\bar\\..\\baz"); + SZ(foo_norm, "foo" S_DELIM_ "baz"); + + string_t bar = path_norm(foo, kit_alloc_default()); + + REQUIRE(AR_EQUAL(foo_norm, bar)); + + DA_DESTROY(bar); +} + +TEST("file path join no delim") { + SZ(foo, "foo"); + SZ(bar, "bar"); + SZ(joined, "foo/bar"); + + string_t foobar = path_join(foo, bar, kit_alloc_default()); + + REQUIRE(AR_EQUAL(joined, foobar)); + + DA_DESTROY(foobar); +} + +TEST("file path join delim left") { + SZ(foo, "foo/"); + SZ(bar, "bar"); + SZ(joined, "foo/bar"); + + string_t foobar = path_join(foo, bar, kit_alloc_default()); + + REQUIRE(AR_EQUAL(joined, foobar)); + + DA_DESTROY(foobar); +} + +TEST("file path join delim right") { + SZ(foo, "foo"); + SZ(bar, "/bar"); + SZ(joined, "foo/bar"); + + string_t foobar = path_join(foo, bar, kit_alloc_default()); + + REQUIRE(AR_EQUAL(joined, foobar)); + + DA_DESTROY(foobar); +} + +TEST("file path join delim both") { + SZ(foo, "foo/"); + SZ(bar, "/bar"); + SZ(joined, "foo/bar"); + + string_t foobar = path_join(foo, bar, kit_alloc_default()); + + REQUIRE(AR_EQUAL(joined, foobar)); + + DA_DESTROY(foobar); +} + +TEST("file path user") { + string_t user = path_user(kit_alloc_default()); + + REQUIRE(user.size > 0); + + DA_DESTROY(user); +} + +TEST("file path index relative") { + SZ(foobar, "foo/bar"); + SZ(foo, "foo"); + SZ(bar, "bar"); + + REQUIRE(AR_EQUAL(path_index(foobar, 0), foo)); + REQUIRE(AR_EQUAL(path_index(foobar, 1), bar)); + REQUIRE(path_index(foobar, 2).size == 0); +} + +TEST("file path index absolute") { + SZ(foobar, "/foo/bar"); + SZ(foo, "foo"); + SZ(bar, "bar"); + + REQUIRE(AR_EQUAL(path_index(foobar, 0), foo)); + REQUIRE(AR_EQUAL(path_index(foobar, 1), bar)); + REQUIRE(path_index(foobar, 2).size == 0); +} + +TEST("file path index windows disk name") { + SZ(foobar, "c:\\foo\\bar"); + SZ(disk, "c:"); + SZ(foo, "foo"); + SZ(bar, "bar"); + + REQUIRE(AR_EQUAL(path_index(foobar, 0), disk)); + REQUIRE(AR_EQUAL(path_index(foobar, 1), foo)); + REQUIRE(AR_EQUAL(path_index(foobar, 2), bar)); + REQUIRE(path_index(foobar, 3).size == 0); +} + +TEST("file path take relative") { + SZ(foobar, "foo/bar/"); + SZ(foo, "foo"); + SZ(bar, "foo/bar"); + SZ(bar_end, "foo/bar/"); + + REQUIRE(AR_EQUAL(path_take(foobar, 0), foo)); + REQUIRE(AR_EQUAL(path_take(foobar, 1), bar)); + REQUIRE(AR_EQUAL(path_take(foobar, 2), bar_end)); +} + +TEST("file path take absolute") { + SZ(foobar, "/foo/bar"); + SZ(foo, "/foo"); + SZ(bar, "/foo/bar"); + + REQUIRE(AR_EQUAL(path_take(foobar, 0), foo)); + REQUIRE(AR_EQUAL(path_take(foobar, 1), bar)); +} + +TEST("file path take windows disk name") { + SZ(foobar, "c:\\foo\\bar"); + SZ(disk, "c:"); + SZ(foo, "c:\\foo"); + SZ(bar, "c:\\foo\\bar"); + + REQUIRE(AR_EQUAL(path_take(foobar, 0), disk)); + REQUIRE(AR_EQUAL(path_take(foobar, 1), foo)); + REQUIRE(AR_EQUAL(path_take(foobar, 2), bar)); +} + +TEST("file create folder") { } + +TEST("file create folder recursive") { } + +TEST("file remove") { } + +TEST("file remove folder") { } + +TEST("file remove recursive") { } + +TEST("file enum folder") { } -- cgit v1.2.3