#include "common/common.h" #if KYTY_PLATFORM != KYTY_PLATFORM_LINUX // #error "KYTY_PLATFORM != KYTY_PLATFORM_LINUX" #else #include "common/assert.h" #include "common/platform/sysFileIO.h" #include "common/platform/sysTimer.h" #include "common/stringUtils.h" #include #include #include #include #include #include #include #include #include // NOLINTNEXTLINE(readability-identifier-naming) enum sys_file_type_t { SYS_FILE_ERROR, // NOLINT(readability-identifier-naming) SYS_FILE_MEMORY_STAT, // NOLINT(readability-identifier-naming) SYS_FILE_FILE, // NOLINT(readability-identifier-naming) SYS_FILE_MEMORY_DYN // NOLINT(readability-identifier-naming) }; // NOLINTNEXTLINE(readability-identifier-naming) struct sys_file_mem_buf_t { uint8_t* base; uint8_t* ptr; uint32_t size; }; // NOLINTNEXTLINE(readability-identifier-naming) struct sys_file_t { sys_file_type_t type; union { FILE* f; sys_file_mem_buf_t* buf; }; }; // Darwin uses BSD timestamp member names. #if defined(__APPLE__) #define KYTY_STAT_ATIME_NS(st) ((st).st_atimespec.tv_nsec) #define KYTY_STAT_MTIME_NS(st) ((st).st_mtimespec.tv_nsec) #else #define KYTY_STAT_ATIME_NS(st) ((st).st_atim.tv_nsec) #define KYTY_STAT_MTIME_NS(st) ((st).st_mtim.tv_nsec) #endif static std::filesystem::path get_internal_name(const std::filesystem::path& name) { return name.is_absolute() ? name : (std::filesystem::path(".") / name); } // Pass access-pattern hints to the host. static void apply_cache_hint(FILE* f, sys_file_cache_type_t cache_type) { if (f == nullptr) { return; } #if !defined(__APPLE__) int advice = POSIX_FADV_NORMAL; switch (cache_type) { case SYS_FILE_CACHE_RANDOM_ACCESS: advice = POSIX_FADV_RANDOM; break; case SYS_FILE_CACHE_SEQUENTIAL_SCAN: advice = POSIX_FADV_SEQUENTIAL; break; case SYS_FILE_CACHE_AUTO: default: return; } ::posix_fadvise(fileno(f), 0, 0, advice); #else if (cache_type == SYS_FILE_CACHE_SEQUENTIAL_SCAN) { ::fcntl(fileno(f), F_RDAHEAD, 1); } else if (cache_type == SYS_FILE_CACHE_RANDOM_ACCESS) { ::fcntl(fileno(f), F_RDAHEAD, 0); } #endif } void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) { if (f.type == SYS_FILE_FILE) { size_t w = fread(data, 1, size, f.f); if (bytes_read != nullptr) { *bytes_read = w; } } else if (f.type == SYS_FILE_MEMORY_STAT) { uint32_t s = size; if (f.buf->size != 0) { uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base); if (s > l) { s = l; } } memcpy(data, f.buf->ptr, s); f.buf->ptr += s; if (bytes_read != nullptr) { *bytes_read = s; } } else if (f.type == SYS_FILE_MEMORY_DYN) { uint32_t s = size; if (f.buf->size != 0) { uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base); if (s > l) { s = l; } } else { s = 0; } memcpy(data, f.buf->ptr, s); f.buf->ptr += s; if (bytes_read != nullptr) { *bytes_read = s; } } } void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_written) { if (f.type == SYS_FILE_FILE) { size_t w = fwrite(data, 1, size, f.f); if (bytes_written != nullptr) { *bytes_written = w; } } else if (f.type == SYS_FILE_MEMORY_STAT) { uint32_t s = size; if (f.buf->size != 0) { uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base); if (s > l) { s = l; } } memcpy(f.buf->ptr, data, s); f.buf->ptr += s; if (bytes_written != nullptr) { *bytes_written = s; } } else if (f.type == SYS_FILE_MEMORY_DYN) { uint32_t pos = f.buf->ptr - f.buf->base; if (f.buf->size < pos + size) { f.buf->base = static_cast(std::realloc(f.buf->base, pos + size)); f.buf->ptr = f.buf->base + pos; f.buf->size = pos + size; } memcpy(f.buf->ptr, data, size); f.buf->ptr += size; if (bytes_written != nullptr) { *bytes_written = size; } } } void SysFileWrite(uint32_t n, sys_file_t& f) { SysFileWrite(&n, 4, f); } sys_file_t* SysFileCreate(const std::filesystem::path& file_name) { auto* ret = new sys_file_t; auto real_name = get_internal_name(file_name); auto real_name_str = real_name.string(); ret->f = fopen(real_name_str.c_str(), "w+"); if (ret->f == nullptr) { printf("can't create file: %s\n", real_name_str.c_str()); } ret->type = SYS_FILE_FILE; return ret; } sys_file_t* SysFileOpenR(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) { auto* ret = new sys_file_t; ret->type = SYS_FILE_FILE; auto internal_name = get_internal_name(file_name); auto internal_name_str = internal_name.string(); FILE* f = fopen(internal_name_str.c_str(), "r"); if (f == nullptr) { ret->type = SYS_FILE_ERROR; } apply_cache_hint(f, cache_type); ret->f = f; return ret; } sys_file_t* SysFileOpen(uint8_t* buf, uint32_t buf_size) { auto* ret = new sys_file_t; ret->type = SYS_FILE_MEMORY_STAT; ret->buf = new sys_file_mem_buf_t; ret->buf->base = buf; ret->buf->ptr = buf; ret->buf->size = buf_size; return ret; } sys_file_t* SysFileCreate() { auto* ret = new sys_file_t; ret->type = SYS_FILE_MEMORY_DYN; ret->buf = new sys_file_mem_buf_t; ret->buf->base = nullptr; ret->buf->ptr = nullptr; ret->buf->size = 0; return ret; } sys_file_t* SysFileOpenW(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) { auto* ret = new sys_file_t; auto real_name = get_internal_name(file_name); auto real_name_str = real_name.string(); FILE* f = fopen(real_name_str.c_str(), "r+"); if (f == nullptr) { ret->type = SYS_FILE_ERROR; } else { ret->type = SYS_FILE_FILE; } apply_cache_hint(f, cache_type); ret->f = f; return ret; } sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) { auto* ret = new sys_file_t; auto real_name = get_internal_name(file_name); auto real_name_str = real_name.string(); FILE* f = fopen(real_name_str.c_str(), "r+"); if (f == nullptr) { ret->type = SYS_FILE_ERROR; } else { ret->type = SYS_FILE_FILE; } apply_cache_hint(f, cache_type); ret->f = f; return ret; } void SysFileClose(sys_file_t* f) { [[maybe_unused]] int result = 0; if (f->type == SYS_FILE_FILE && f->f != nullptr) { result = fclose(f->f); } else if (f->type == SYS_FILE_MEMORY_STAT) { delete f->buf; } else if (f->type == SYS_FILE_MEMORY_DYN) { std::free(f->buf->base); delete f->buf; } // f.type = SYS_FILE_ERROR; delete f; } uint64_t SysFileSize(sys_file_t& f) { [[maybe_unused]] int result = 0; if (f.type == SYS_FILE_FILE) { // Preserve sizes above 4 GiB. const off_t pos = ftello(f.f); if (pos < 0) { return 0; } if (fseeko(f.f, 0, SEEK_END) != 0) { return 0; } const off_t size = ftello(f.f); result = fseeko(f.f, pos, SEEK_SET); return (size < 0 ? 0 : static_cast(size)); } if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) { return f.buf->size; } return 0; } uint64_t SysFileSize(const std::filesystem::path& file_name) { sys_file_t* f = SysFileOpenR(file_name); uint64_t size = SysFileSize(*f); SysFileClose(f); return size; } bool SysFileTruncate(sys_file_t& f, uint64_t size) { bool ok = false; if (f.type == SYS_FILE_FILE) { // Flush before resizing and restore the caller's position. const auto position = ftell(f.f); fflush(f.f); ok = (ftruncate(fileno(f.f), static_cast(size)) == 0); if (position >= 0) { fseek(f.f, position, SEEK_SET); } } return ok; } bool SysFileUnlink(sys_file_t& /*f*/, const std::filesystem::path& name) { return SysFileDeleteFile(name); } bool SysFileSeek(sys_file_t& f, uint64_t offset) { bool ok = true; if (f.type == SYS_FILE_FILE) { ok = (fseek(f.f, static_cast(offset), SEEK_SET) == 0); // LARGE_INTEGER s; // s.QuadPart = offset; // SetFilePointerEx(f.handle, s, 0, FILE_BEGIN); // printf("seek: %u\n", offset); } else if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) { f.buf->ptr = f.buf->base + offset; } return ok; } uint64_t SysFileTell(sys_file_t& f) { if (f.type == SYS_FILE_FILE) { return ftell(f.f); } if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) { return f.buf->ptr - f.buf->base; } return 0; } bool SysFileIsError(sys_file_t& f) { return f.type == SYS_FILE_ERROR || (f.type == SYS_FILE_FILE && f.f == nullptr); } bool SysFileIsDirectoryExisting(const std::filesystem::path& path) { auto real_name = get_internal_name(path); auto real_name_str = real_name.string(); struct stat s {}; if (0 != stat(real_name_str.c_str(), &s)) { return false; } return S_ISDIR(s.st_mode); // NOLINT } bool SysFileIsFileExisting(const std::filesystem::path& name) { auto real_name = get_internal_name(name); auto real_name_str = real_name.string(); struct stat s {}; if (0 != stat(real_name_str.c_str(), &s)) { return false; } return !S_ISDIR(s.st_mode); // NOLINT } bool SysFileCreateDirectory(const std::filesystem::path& path) { auto real_name = get_internal_name(path); auto real_name_str = real_name.string(); mode_t m = S_IRWXU | S_IRWXG | S_IRWXO; // NOLINT int r = mkdir(real_name_str.c_str(), m); if (r != 0) { int e = errno; if (e == EEXIST) { printf("mkdir(%s, %" PRIx32 ") failed: The named file exists\n", real_name_str.c_str(), static_cast(m)); } else { printf("mkdir(%s, %" PRIx32 ") failed: %d\n", real_name_str.c_str(), static_cast(m), e); return false; } } return true; } bool SysFileDeleteDirectory(const std::filesystem::path& path) { auto real_name = get_internal_name(path); auto real_name_str = real_name.string(); return 0 == remove(real_name_str.c_str()); } bool SysFileDeleteFile(const std::filesystem::path& name) { auto real_name = get_internal_name(name); auto real_name_str = real_name.string(); return 0 == unlink(real_name_str.c_str()); } bool SysFileFlush(sys_file_t& f) { if (f.type == SYS_FILE_FILE && f.f != nullptr) { return (fflush(f.f) == 0); } return false; } SysFileTimeStruct SysFileGetLastAccessTimeUtc(const std::filesystem::path& name) { SysFileTimeStruct r {}; auto real_name = get_internal_name(name); auto real_name_str = real_name.string(); struct stat s {}; if (0 != stat(real_name_str.c_str(), &s)) { r.is_invalid = true; } else { r.is_invalid = false; r.time = s.st_atime; r.nanos = KYTY_STAT_ATIME_NS(s); } return r; } SysFileTimeStruct SysFileGetLastWriteTimeUtc(const std::filesystem::path& name) { SysFileTimeStruct r {}; auto real_name = get_internal_name(name); auto real_name_str = real_name.string(); struct stat s {}; if (0 != stat(real_name_str.c_str(), &s)) { r.is_invalid = true; } else { r.is_invalid = false; r.time = s.st_mtime; r.nanos = KYTY_STAT_MTIME_NS(s); } return r; } void SysFileGetLastAccessAndWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& a, SysFileTimeStruct& w) { auto real_name = get_internal_name(name); auto real_name_str = real_name.string(); struct stat s {}; if (0 != stat(real_name_str.c_str(), &s)) { a.is_invalid = true; w.is_invalid = true; } else { a.is_invalid = false; w.is_invalid = false; a.time = s.st_atime; a.nanos = KYTY_STAT_ATIME_NS(s); w.time = s.st_mtime; w.nanos = KYTY_STAT_MTIME_NS(s); } } void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& f, SysFileTimeStruct& a, SysFileTimeStruct& w) { if (f.type == SYS_FILE_FILE) { struct stat s {}; const bool ok = (0 == fstat(fileno(f.f), &s)); a.is_invalid = w.is_invalid = !ok; if (ok) { a.time = s.st_atime; a.nanos = KYTY_STAT_ATIME_NS(s); w.time = s.st_mtime; w.nanos = KYTY_STAT_MTIME_NS(s); } } else if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) { // Memory-backed files use the current time. SysTimeStruct t {}; SysGetSystemTimeUtc(t); SysSystemToFileTimeUtc(t, a); SysSystemToFileTimeUtc(t, w); } else { a.is_invalid = w.is_invalid = true; } } bool SysFileSetLastAccessTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& access) { if (access.is_invalid) { return false; } auto real_name = get_internal_name(name); auto real_name_str = real_name.string(); struct stat s {}; if (0 != stat(real_name_str.c_str(), &s)) { return false; } utimbuf times {}; times.actime = access.time; times.modtime = s.st_mtime; return !(0 != utime(real_name_str.c_str(), ×)); // if (0 != stat(n.data(), &s)) // { // return false; // } // // times.actime += times.actime - s.st_atime; // times.modtime += times.modtime - s.st_mtime; // // if (0 != utime(n.data(), ×)) // { // return false; // } } bool SysFileSetLastWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& write) { if (write.is_invalid) { return false; } auto real_name = get_internal_name(name); auto real_name_str = real_name.string(); struct stat s {}; if (0 != stat(real_name_str.c_str(), &s)) { return false; } utimbuf times {}; times.actime = s.st_atime; times.modtime = write.time; return !(0 != utime(real_name_str.c_str(), ×)); // if (0 != stat(n.data(), &s)) // { // return false; // } // // times.actime += times.actime - s.st_atime; // times.modtime += times.modtime - s.st_mtime; // // if (0 != utime(n.data(), ×)) // { // return false; // } } bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& access, SysFileTimeStruct& write) { if (access.is_invalid || write.is_invalid) { return false; } auto real_name = get_internal_name(name); auto real_name_str = real_name.string(); struct stat s {}; if (0 != stat(real_name_str.c_str(), &s)) { return false; } utimbuf times {}; times.actime = access.time; times.modtime = write.time; return !(0 != utime(real_name_str.c_str(), ×)); // if (0 != stat(n.data(), &s)) // { // return false; // } // // times.actime += times.actime - s.st_atime; // times.modtime += times.modtime - s.st_mtime; // // if (0 != utime(n.data(), ×)) // { // return false; // } } // Recursively collect regular files. void SysFileFindFiles(const std::filesystem::path& path, std::vector& out) { auto real_path = get_internal_name(path); DIR* dir = opendir(real_path.string().c_str()); if (dir == nullptr) { return; } for (const dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) { const std::string file_name(entry->d_name); if (file_name == "." || file_name == "..") { continue; } auto child = real_path / file_name; struct stat s {}; // lstat, so a symlink is never followed into a cycle during the recursive walk. if (0 != lstat(child.string().c_str(), &s)) { continue; } if (S_ISDIR(s.st_mode)) { SysFileFindFiles(child, out); } else if (S_ISREG(s.st_mode)) { sys_file_find_t r {}; r.path_with_name = child; r.size = static_cast(s.st_size); r.last_access_time.is_invalid = false; r.last_access_time.time = s.st_atime; r.last_access_time.nanos = KYTY_STAT_ATIME_NS(s); r.last_write_time.is_invalid = false; r.last_write_time.time = s.st_mtime; r.last_write_time.nanos = KYTY_STAT_MTIME_NS(s); out.push_back(r); } } closedir(dir); } // Keep "." and ".." to match FindFirstFileW. void SysFileGetDents(const std::filesystem::path& path, std::vector& out) { auto real_path = get_internal_name(path); DIR* dir = opendir(real_path.string().c_str()); if (dir == nullptr) { return; } for (const dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) { sys_dir_entry_t r {}; r.name = entry->d_name; if (entry->d_type == DT_UNKNOWN) { // Some filesystems do not populate d_type. struct stat s {}; r.is_file = 0 == lstat((real_path / r.name).string().c_str(), &s) && S_ISREG(s.st_mode); } else { r.is_file = entry->d_type != DT_DIR; } out.push_back(r); } closedir(dir); } bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst) { std::error_code error; return std::filesystem::copy_file(get_internal_name(src), get_internal_name(dst), std::filesystem::copy_options::overwrite_existing, error) && !error; } bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst) { auto real_src = get_internal_name(src); auto real_dst = get_internal_name(dst); // Match MoveFileW: fail when the destination exists. std::error_code error; if (std::filesystem::exists(real_dst, error)) { return false; } return 0 == rename(real_src.string().c_str(), real_dst.string().c_str()); } void SysFileRemoveReadonly(const std::filesystem::path& name) { auto real_name = get_internal_name(name); auto real_name_str = real_name.string(); struct stat s {}; if (0 != stat(real_name_str.c_str(), &s)) { return; } chmod(real_name_str.c_str(), s.st_mode | S_IWUSR); } #endif