From 88779335927a360244be492ec564c8b8ad1b7fcb Mon Sep 17 00:00:00 2001 From: MITSUNARI Shigeo Date: Wed, 15 Jan 2020 14:54:40 +0900 Subject: [PATCH] add cybozu/file.hpp --- include/cybozu/file.hpp | 621 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 621 insertions(+) create mode 100644 include/cybozu/file.hpp diff --git a/include/cybozu/file.hpp b/include/cybozu/file.hpp new file mode 100644 index 0000000..ff17b6f --- /dev/null +++ b/include/cybozu/file.hpp @@ -0,0 +1,621 @@ +#pragma once +/** + @file + @brief file class and operations + + @author MITSUNARI Shigeo(@herumi) +*/ + +#include +#include // for stat +#include +#include +#include +#ifdef _WIN32 + #include + #include + #include + #include + #include + #include + #pragma comment(lib, "shlwapi.lib") + #pragma comment(lib, "shell32.lib") + #pragma comment(lib, "User32.lib") +#else + #include + #include + #include + #include + #include +#endif +#ifdef __APPLE__ + #include +#endif + +namespace cybozu { + +class File { + std::string name_; // used for only errmsg +#ifdef _WIN32 + typedef HANDLE handleType; +#else + typedef int handleType; + enum { + INVALID_HANDLE_VALUE = -1 + }; +#endif + handleType hdl_; + bool doClose_; + bool isReadOnly_; + File(const File&); + void operator=(const File&); +#ifdef _WIN32 + void openSub() + { + } +#endif + void verifyMode(std::ios::openmode mode) + { + doClose_ = true; + bool isCorrectMode = true; + if (!!(mode & std::ios::in) == !!(mode & std::ios::out)) { + isCorrectMode = false; + } else { + if (mode & std::ios::in) { + isReadOnly_ = true; + if ((mode & std::ios::app) || (mode & std::ios::trunc)) isCorrectMode = false; + } else { + isReadOnly_ = false; + if ((mode & std::ios::app) && (mode & std::ios::trunc)) isCorrectMode = false; + } + } + if (!isCorrectMode) { + throw cybozu::Exception("File:open:bad mode") << name_ << mode; + } + } +#ifdef _WIN32 + HANDLE createFile(const std::string& name, DWORD access, DWORD share, DWORD disposition) + { + return ::CreateFileA(name.c_str(), access, share, NULL, disposition, FILE_ATTRIBUTE_NORMAL, NULL); + } + HANDLE createFile(const std::wstring& name, DWORD access, DWORD share, DWORD disposition) + { + return ::CreateFileW(name.c_str(), access, share, NULL, disposition, FILE_ATTRIBUTE_NORMAL, NULL); + } + template + void setHandle(const T& name, std::ios::openmode mode) + { + DWORD access = GENERIC_READ; + DWORD disposition = OPEN_EXISTING; + DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE; + if (mode & std::ios::out) { + access = GENERIC_WRITE; + disposition = CREATE_ALWAYS; + if (mode & std::ios::app) { + disposition = OPEN_ALWAYS; + } + } + hdl_ = createFile(name, access, share, disposition); + } +#else + void setHandle(const std::string& name, std::ios::openmode mode) + { + int flags = O_RDONLY; // | O_NOATIME; /* can't use on NFS */ + mode_t access = 0644; + if (mode & std::ios::out) { + flags = O_WRONLY | O_CREAT; + if (mode & std::ios::app) { + flags |= O_APPEND; + } else { + flags |= O_TRUNC; + } + } + hdl_ = ::open(name.c_str(), flags, access); + } +#endif + template + void openSub(const T& name, std::ios::openmode mode) + { + if (isOpen()) throw cybozu::Exception("File:open:alread opened") << name_; + verifyMode(mode); + setHandle(name, mode); + if (isOpen()) { + if (mode & std::ios::app) { + seek(getSize(), std::ios::beg); + } + return; + } + throw cybozu::Exception("File:open") << name_ << cybozu::ErrorNo() << static_cast(mode); + } +public: + File() + : hdl_(INVALID_HANDLE_VALUE) + , doClose_(true) + , isReadOnly_(false) + { + } + File(const std::string& name, std::ios::openmode mode) + : hdl_(INVALID_HANDLE_VALUE) + , doClose_(true) + , isReadOnly_(false) + { + open(name, mode); + } + /* + construct with file handle + @param hdl [in] file handle + */ + explicit File(handleType hdl) + : hdl_(hdl) + , doClose_(false) + , isReadOnly_(false) + + { + } + ~File() CYBOZU_NOEXCEPT + { + if (!doClose_) return; + try { + sync(); + close(); + } catch (std::exception& e) { + fprintf(stderr, "File:dstr:%s\n", e.what()); + } catch (...) { + fprintf(stderr, "File:dstr:unknown\n"); + } + } + bool isOpen() const CYBOZU_NOEXCEPT { return hdl_ != INVALID_HANDLE_VALUE; } + /** + support mode + always binary mode + ios::in : read only + ios::out : write only(+truncate) + ios::out + ios::app(append) + */ + void open(const std::string& name, std::ios::openmode mode) + { + name_ = name; + openSub(name, mode); + } + void openW(const std::string& name) + { + open(name, std::ios::out | std::ios::binary | std::ios::trunc); + } + void openR(const std::string& name) + { + open(name, std::ios::in | std::ios::binary); + } +#ifdef _WIN32 + void open(const std::wstring& name, std::ios::openmode mode) + { + name_ = cybozu::exception::wstr2str(name); + openSub(name, mode); + } + File(const std::wstring& name, std::ios::openmode mode) + : hdl_(INVALID_HANDLE_VALUE) + { + open(name, mode); + } + void openW(const std::wstring& name) + { + open(name, std::ios::out | std::ios::binary | std::ios::trunc); + } + void openR(const std::wstring& name) + { + open(name, std::ios::in | std::ios::binary); + } +#endif + void close() + { + if (!isOpen()) return; +#ifdef _WIN32 + bool isOK = ::CloseHandle(hdl_) != 0; +#else + bool isOK = ::close(hdl_) == 0; +#endif + hdl_ = INVALID_HANDLE_VALUE; + if (isOK) return; + throw cybozu::Exception("File:close") << name_ << cybozu::ErrorNo(); + } + /* + sync + @param doFullSync [in] call sync(for only Linux) + */ + void sync(bool doFullSync = false) + { + cybozu::disable_warning_unused_variable(doFullSync); + if (!isOpen()) return; + if (isReadOnly_) return; +#ifdef _WIN32 + /* fail if isReadOnly_ */ + if (!::FlushFileBuffers(hdl_)) goto ERR_EXIT; +#elif defined(__linux__) || defined(__CYGWIN__) + if (doFullSync) { + if (::fsync(hdl_)) goto ERR_EXIT; + } else { + if (::fdatasync(hdl_)) goto ERR_EXIT; + } +#else + if (::fcntl(hdl_, F_FULLFSYNC)) goto ERR_EXIT; +#endif + return; + ERR_EXIT: + throw cybozu::Exception("File:sync") << name_ << cybozu::ErrorNo(); + } + void write(const void *buf, size_t bufSize) + { + const char *p = static_cast(buf); + while (bufSize > 0) { + uint32_t size = static_cast(std::min(0x7fffffff, bufSize)); +#ifdef _WIN32 + DWORD writeSize; + if (!::WriteFile(hdl_, p, size, &writeSize, NULL)) goto ERR_EXIT; +#else + ssize_t writeSize = ::write(hdl_, p, size); + if (writeSize < 0) { + if (errno == EINTR) continue; + goto ERR_EXIT; + } +#endif + p += writeSize; + bufSize -= writeSize; + } + return; + ERR_EXIT: + throw cybozu::Exception("File:write") << name_ << cybozu::ErrorNo(); + } + size_t readSome(void *buf, size_t bufSize) + { + uint32_t size = static_cast(std::min(0x7fffffff, bufSize)); +#ifdef _WIN32 + DWORD readSize; + if (!::ReadFile(hdl_, buf, size, &readSize, NULL)) goto ERR_EXIT; +#else + RETRY: + ssize_t readSize = ::read(hdl_, buf, size); + if (readSize < 0) { + if (errno == EINTR) goto RETRY; + goto ERR_EXIT; + } +#endif + return readSize; + ERR_EXIT: + throw cybozu::Exception("File:read") << name_ << cybozu::ErrorNo(); + } + void read(void *buf, size_t bufSize) + { + char *p = static_cast(buf); + while (bufSize > 0) { + size_t readSize = readSome(p, bufSize); + p += readSize; + bufSize -= readSize; + } + } + void seek(int64_t pos, std::ios::seek_dir dir) + { +#ifdef _WIN32 + LARGE_INTEGER largePos; + largePos.QuadPart = pos; + DWORD posMode = FILE_BEGIN; + switch (dir) { + case std::ios::beg: + posMode = FILE_BEGIN; + break; + case std::ios::cur: + posMode = FILE_CURRENT; + break; + case std::ios::end: + posMode = FILE_END; + break; + default: + __assume(0); + } + bool isOK = SetFilePointerEx(hdl_, largePos, NULL, posMode) != 0; +#else + int whence; + switch (dir) { + case std::ios::beg: + whence = SEEK_SET; + break; + case std::ios::cur: + whence = SEEK_CUR; + break; + case std::ios::end: + default: + whence = SEEK_END; + break; + } + bool isOK = lseek(hdl_, pos, whence) >= 0; +#endif + if (isOK) return; + throw cybozu::Exception("File:seek") << name_ << cybozu::ErrorNo() << pos << static_cast(dir); + } + uint64_t getSize() const + { + uint64_t fileSize; +#ifdef _WIN32 + LARGE_INTEGER size; + bool isOK = GetFileSizeEx(hdl_, &size) != 0; + fileSize = size.QuadPart; +#else + struct stat stat; + bool isOK = fstat(hdl_, &stat) == 0; + fileSize = stat.st_size; +#endif + if (isOK) return fileSize; + throw cybozu::Exception("File:getSize") << name_ << cybozu::ErrorNo(); + } +}; + +/* + name has extension +*/ +inline bool HasExtension(const std::string& name, const std::string& extension) +{ + const size_t extensionSize = extension.size(); + if (extensionSize == 0) return true; + const size_t nameSize = name.size(); + if (nameSize < extensionSize + 1) return false; + const char *p = &name[nameSize - extensionSize - 1]; + return *p == '.' && memcmp(p + 1, &extension[0], extensionSize) == 0; +} +/* + split name as basename.suffix +*/ +inline std::string GetBaseName(const std::string& name, std::string *suffix = 0) +{ + size_t pos = name.find_last_of('.'); + if (pos == std::string::npos) { + if (suffix) suffix->clear(); + return name; + } + if (suffix) { + *suffix = name.substr(pos + 1); + } + return name.substr(0, pos); +} + +/** + replace \ with / +*/ +inline void ReplaceBackSlash(std::string& str) +{ + for (size_t i = 0, n = str.size(); i < n; i++) { + if (str[i] == '\\') str[i] = '/'; + } +} + +/** + get exe path and baseNamme + @note file name is the form "xxx.exe" then baseName = xxx +*/ +inline std::string GetExePath(std::string *baseName = 0) +{ + std::string path; + path.resize(4096); +#ifdef _WIN32 + if (!GetModuleFileNameA(NULL, &path[0], static_cast(path.size()) - 2)) { + return ""; + } + PathRemoveExtensionA(&path[0]); + if (baseName) { + *baseName = PathFindFileNameA(&path[0]); + } + if (::PathRemoveFileSpecA(&path[0])) { + ::PathAddBackslashA(&path[0]); + path[0] = static_cast(tolower(path[0])); + path.resize(strlen(&path[0])); + ReplaceBackSlash(path); + } +#else +#if defined(__APPLE__) + uint32_t size = (uint32_t)path.size(); + if (_NSGetExecutablePath(&path[0], &size) != 0) { + return ""; + } + path.resize(strlen(&path[0])); +#else + int ret = readlink("/proc/self/exe", &path[0], path.size() - 2); + if (ret < 0) return ""; + path.resize(ret); +#endif + size_t pos = path.find_last_of('/'); + if (pos != std::string::npos) { + if (baseName) { + const std::string name = path.substr(pos + 1); + std::string suffix; + std::string base = GetBaseName(name, &suffix); + if (suffix == "exe") { + *baseName = base; + } else { + *baseName = name; + } + } + path.resize(pos + 1); + } +#endif + return path; +} + +/** + get file size +*/ +inline uint64_t GetFileSize(const std::string& name) +{ +#ifdef _WIN32 + struct __stat64 buf; + bool isOK = _stat64(name.c_str(), &buf) == 0; +#else + struct stat buf; + bool isOK = stat(name.c_str(), &buf) == 0; +#endif + if (isOK) return buf.st_size; + throw cybozu::Exception("GetFileSize") << name << cybozu::ErrorNo(); +} + +/** + verify whether path exists or not +*/ +inline bool DoesFileExist(const std::string& path) +{ + if (path.empty()) return false; + std::string p = path; + char c = p[p.size() - 1]; + if (c == '/' || c == '\\') { + p.resize(p.size() - 1); + } +#ifdef _WIN32 + struct _stat buf; + return _stat(p.c_str(), &buf) == 0; +#else + struct stat buf; + return stat(p.c_str(), &buf) == 0; +#endif +} + +inline void RenameFile(const std::string& from, const std::string& to) +{ + if (DoesFileExist(to)) { + throw cybozu::Exception("RenameFile:file already exist") << from << to; + } +#ifdef _WIN32 + bool isOK = ::MoveFileExA(from.c_str(), to.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH) != 0; +#else + bool isOK = ::rename(from.c_str(), to.c_str()) == 0; +#endif + if (!isOK) { + throw cybozu::Exception("RenameFile") << from << to << cybozu::ErrorNo(); + } +} + +/** + remove file +*/ +inline void RemoveFile(const std::string& name) +{ +#ifdef _WIN32 + bool isOK = DeleteFileA(name.c_str()) != 0; +#else + bool isOK = unlink(name.c_str()) == 0; +#endif + if (!isOK) { + throw cybozu::Exception("RemoveFile") << name << cybozu::ErrorNo(); + } +} + +/* + remark of isFile() + not directory on Windows + not contain symbolic link on Linux +*/ +struct FileInfo { + std::string name; + uint32_t attr; // dwFileAttributes for Windows, d_type for Linux +#ifdef _WIN32 + bool isUnknown() const { return attr == 0; } + bool isDirectory() const { verify(); return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; } + bool isFile() const { verify(); return !isDirectory(); } +#else + bool isUnknown() const { return attr == DT_UNKNOWN; } + bool isDirectory() const { verify(); return attr == DT_DIR; } + bool isFile() const { verify(); return attr == DT_REG; } +#endif + FileInfo() : attr(0) {} + FileInfo(const std::string& name, uint32_t attr) : name(name), attr(attr) {} + void verify() const + { + if (isUnknown()) throw cybozu::Exception("FileInfo:unknown attr") << name; + } +}; + +typedef std::vector FileList; + + +namespace file_local { + +inline void filterAndPush(FileList& list, const FileInfo& fi, const std::string& extension, bool cond(const std::string&, const std::string&)) +{ + if (fi.name == "." || fi.name == "..") { + return; + } + if (cond(fi.name, extension)) { + list.push_back(fi); + } +} + +} // cybozu::file_local + +/** + get file name in dir + @param list [out] FileList + @param dir [in] directory + @param extension [in] select files(including directory) having extension such as "cpp" ; select all if suffix is empty + @param cond [in] filter function (select if cond(targetFile, suffix) is true) + @note "." and ".." are excluded +*/ +inline bool GetFileList(FileList &list, const std::string& dir, const std::string& extension = "", bool (*cond)(const std::string&, const std::string&) = cybozu::HasExtension) +{ +#ifdef _WIN32 + std::string path = dir + "/*"; + WIN32_FIND_DATAA fd; + struct Handle { + Handle(HANDLE hdl) + : hdl_(hdl) + { + } + ~Handle() + { + if (hdl_ != INVALID_HANDLE_VALUE) { + FindClose(hdl_); + } + } + HANDLE hdl_; + }; + Handle hdl(FindFirstFileA(path.c_str(), &fd)); + if (hdl.hdl_ == INVALID_HANDLE_VALUE) { + return false; + } + do { + FileInfo fi(fd.cFileName, fd.dwFileAttributes); + file_local::filterAndPush(list, fi, extension, cond); + } while (FindNextFileA(hdl.hdl_, &fd) != 0); + return true; +#else + struct Handle { + DIR *dir_; + Handle(DIR *dir) + : dir_(dir) + { + if (dir_ == 0) { + perror("opendir"); + } + } + ~Handle() + { + if (dir_) { + if (::closedir(dir_)) { + perror("closedir"); + } + } + } + bool isValid() const { return dir_ != 0; } + }; + Handle hdl(::opendir(dir.c_str())); + if (!hdl.isValid()) return false; + for (;;) { + struct dirent *dp = ::readdir(hdl.dir_); + if (dp == 0) return true; + FileInfo fi(dp->d_name, (uint8_t)dp->d_type); + file_local::filterAndPush(list, fi, extension, cond); + } +#endif +} + +inline FileList GetFileList(const std::string& dir, const std::string& extension = "", bool (*cond)(const std::string&, const std::string&) = cybozu::HasExtension) +{ + FileList fl; + if (GetFileList(fl, dir, extension, cond)) return fl; + throw cybozu::Exception("cybozu:GetFileList") << dir << cybozu::ErrorNo(); +} + +} // cybozu