#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 # endif # ifndef NOMINMAX # define NOMINMAX # endif # include # include #else # include # include # include # include # include # include #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) s32 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 } s32 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) { s32 s = kit_folder_create(part); if (s != KIT_OK) return s; } if (part.size == path.size) break; } return KIT_OK; } s32 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 } s32 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 } s32 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: { s32 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); s32 s = kit_file_remove_recursive(WRAP_STR(full_path), alloc); DA_DESTROY(full_path); assert(s == KIT_OK); } kit_path_list_destroy(list); s32 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; } s32 kit_file_sync(kit_mapped_file_t *mf) { assert(mf != NULL); if (mf == NULL) return KIT_ERROR_INVALID_ARGUMENT; s32 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; } s32 kit_file_unmap(kit_mapped_file_t *mf) { assert(mf != NULL); if (mf == NULL) return KIT_ERROR_INVALID_ARGUMENT; s32 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; }