/** * @file file.cpp * @author myusgun@gmail.com * @brief file */ #include "cf/file.h" #include #include #include #include #include /* for file system */ #ifdef _ON_WINDOWS # include # include # include "dirent.win32.hpp" # define DELIMITER "\\" # define mkdir(a,b) _mkdir(a) # define access(a,b) _access(a,b) # define F_OK 0 # define W_OK 2 # define R_OK 4 # define S_IWUSR _S_IWRITE # define S_IRUSR _S_IREAD # define S_IXUSR _S_IEXEC # define S_IRGRP 0x00000000 # define S_IWGRP 0x00000000 # define S_IXGRP 0x00000000 # define S_IROTH 0x00000000 # define S_IWOTH 0x00000000 # define S_IXOTH 0x00000000 # define S_IRWXU 0x00000000 #else # include # include # define DELIMITER "/" # define O_BINARY 0x00000000 #endif #define NO_FD -1 /*--------------------------------------------------------------*/ static inline cf::int32_t getFlag(const cf::int32_t flag) { cf::int32_t result = 0; if (flag & cf::file::READ) result |= O_RDONLY; else if (flag & cf::file::WRITE) result |= O_WRONLY; if (flag & cf::file::RW) result |= O_RDWR; if (flag & cf::file::CREATE) result |= O_CREAT; if (flag & cf::file::TRUNC) result |= O_TRUNC; if (flag & cf::file::APPEND) result |= O_APPEND; if (flag & cf::file::EXCL) result |= O_EXCL; return result; } static inline cf::int32_t getWhence(const cf::int32_t whence) throw (cf::exception) { switch(whence) { case cf::file::BEGIN: return SEEK_SET; case cf::file::CURRENT: return SEEK_CUR; case cf::file::END: return SEEK_END; default: THROW_EXCEPTION("unknown whence"); } } static inline cf::void_t splitPath(const std::string & in, std::string & path, std::string & file) { size_t found = in.find_last_of(cf::file::getDelimiter()); if (found == std::string::npos) { path = "."; file = in; } else { path = in.substr(0, found); file = in.substr(found + 1); } } static inline std::string getLockfilePath(const std::string & path) { std::string parent; std::string file; std::string lockPath; splitPath(path, parent, file); lockPath = parent + cf::file::getDelimiter() + "." + file + ".lock"; return lockPath; } static inline cf::bool_t compareByName(const cf::file::ENTRY & lhs, const cf::file::ENTRY & rhs) { std::size_t len = lhs.mName.length() < rhs.mName.length() ? lhs.mName.length() : rhs.mName.length(); cf::int32_t smaller = static_cast(len); if (strncmp(lhs.mName.c_str(), rhs.mName.c_str(), smaller) < 0) return true; else return false; } /*--------------------------------------------------------------*/ cf::file::file(const cf::char_t * path) throw (cf::exception) : mFD(NO_FD), mIsLocked(false) { if (!path) THROW_EXCEPTION("file path is null"); mPath = path; } cf::file::~file() { close(); } const cf::char_t * cf::file::getDelimiter() { return DELIMITER; } cf::void_t cf::file::open(const cf::int32_t flag) throw (cf::exception) { #define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH|S_IREAD|S_IWRITE) if (flag & file::LOCK) mIsLocked = true; /* lock */ std::string lockfile = getLockfilePath(mPath); cf::int32_t lockfd = ::open(lockfile.c_str(), O_CREAT | O_EXCL, FILE_MODE); if (lockfd < 0) { if (errno == EEXIST) THROW_EXCEPTION("{" << mPath << "} is already locked"); else THROW_EXCEPTION("cannot create locking file {" << lockfile << "} (" << errno << ":" << strerror(errno) << ")"); } ::close(lockfd); if (!mIsLocked) file(lockfile.c_str()).remove(); /* open */ cf::int32_t nativeFlag = getFlag(flag) | O_BINARY; mFD = ::open(mPath.c_str(), nativeFlag, FILE_MODE); if (mFD < 0) THROW_EXCEPTION("cannot open file(" << errno << ":" << strerror(errno) << ")"); } cf::void_t cf::file::close() throw (cf::exception) { if (isOpened()) { ::close(mFD); mFD = NO_FD; } if (mIsLocked) { file lockfile(getLockfilePath(mPath).c_str()); if (lockfile.exists()) lockfile.remove(); mIsLocked = false; } } cf::bool_t cf::file::isOpened() { return mFD > NO_FD ? true : false; } cf::int32_t cf::file::seek(const cf::int32_t offset, const cf::int32_t whence) throw (cf::exception) { off_t delta = ::lseek(mFD, static_cast(offset), getWhence(whence)); if (delta < 0) THROW_EXCEPTION("cannot seek in file(" << errno << ":" << strerror(errno) << ")"); return static_cast(delta); } cf::int32_t cf::file::tell() throw (cf::exception) { return seek(0, file::CURRENT); } cf::int32_t cf::file::size() throw (cf::exception) { struct stat statbuf; if (stat(mPath.c_str(), &statbuf) < 0) THROW_EXCEPTION("cannot get stat {" << mPath << "} (" << errno << ":" << strerror(errno) << ")"); return static_cast(statbuf.st_size); } cf::bool_t cf::file::exists() const { return access(mPath.c_str(), F_OK) ? false : true; } cf::void_t cf::file::read(cf::bin & out) throw (cf::exception) { if (out.size() <= 0) THROW_EXCEPTION("read-buffer size is {" << out.size() << "}"); cf::uint8_t * buf = out.buffer(); cf::int32_t bufSize = static_cast(out.size()); cf::int32_t readLen = static_cast(::read(mFD, buf, bufSize)); if (readLen < 0) THROW_EXCEPTION("cannot read from file(" << errno << ":" << strerror(errno) << ")"); out.set(buf, readLen); } cf::bin cf::file::read(const cf::int32_t len) throw (cf::exception) { cf::bin out; out.resize(len > 0 ? len : size()); read(out); return out; } cf::void_t cf::file::write(const cf::bin & in) throw (cf::exception) { cf::void_t * buf = reinterpret_cast(in.buffer()); size_t len = static_cast(in.size()); size_t writtenLen = 0; if (!buf) THROW_EXCEPTION("writing buffer is null"); writtenLen = ::write(mFD, buf, len); if (writtenLen != len) THROW_EXCEPTION("cannot write to file(" << errno << ":" << strerror(errno) << ")"); } cf::void_t cf::file::remove() throw (cf::exception) { close(); if (::remove(mPath.c_str()) < 0) THROW_EXCEPTION("cannot remove file {" << mPath << "} (" << errno << ":" << strerror(errno) << ")"); } cf::void_t cf::file::rename(const cf::char_t * path) throw (cf::exception) { if (!path) THROW_EXCEPTION("new file path is null"); close(); if (::rename(mPath.c_str(), path) < 0) THROW_EXCEPTION("cannot rename file from {" << mPath << "} to {" << path << "} (" << errno << ":" << strerror(errno) << ")"); } const cf::char_t * cf::file::getPath() const { return mPath.c_str(); } cf::bool_t cf::file::isLocked() const { return mIsLocked; } cf::void_t cf::file::makedir(const cf::char_t * path) throw (cf::exception) { if (!path) THROW_EXCEPTION("directory path is null"); std::string fullpath = path; std::string step; std::string::iterator iter; fullpath += getDelimiter(); for (iter = fullpath.begin() ; iter != fullpath.end() ; iter++) { step += *iter; if (*iter != *getDelimiter()) continue; if (file(step.c_str()).exists()) continue; #define DIRECTORY_MODE (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) cf::int32_t result = mkdir(step.c_str(), DIRECTORY_MODE); if (result && errno != EEXIST) THROW_EXCEPTION("cannot make directory {" << step << "} (" << errno << ":" << strerror(errno) << ")"); } } cf::file::EntryList cf::file::getEntryList(const cf::char_t * path, const cf::bool_t exceptDots) throw (cf::exception) { if (!path) THROW_EXCEPTION("directory path is null"); if (!file(path).exists()) THROW_EXCEPTION("{" << path << "} does not exist"); DIR * dirp = NULL; struct dirent * entp = NULL; cf::int32_t error = 0; file::EntryList entryList; dirp = opendir(path); if (!dirp) THROW_EXCEPTION("cannot open directory {" << path << "} (" << errno << ":" << strerror(errno) << ")"); error = errno; /** * readdir manapge *-------------------------------------------------------------- * command : man readdir *-------------------------------------------------------------- * On success, readdir() returns a pointer to a dirent structure. * (This structure may be statically allocated; do not attempt to free(3) it.) * If the end of the directory stream is reached, NULL is returned and errno is not changed. * If an error occurs, NULL is returned and errno is set appropriately. *-------------------------------------------------------------- * * from. gnu-libc-manual *-------------------------------------------------------------- * url : http://www.gnu.org/software/libc/manual/html_node/Reading_002fClosing-Directory.html *-------------------------------------------------------------- * In POSIX.1-2008, readdir is not thread-safe. * In the GNU C Library implementation, * it is safe to call readdir concurrently on different dirstreams, * but multiple threads accessing the same dirstream result in undefined behavior. * readdir_r is a fully thread-safe alternative, but suffers from poor portability(see below). * It is recommended that you use readdir, with external locking if multiple threads access the same dirstream. *-------------------------------------------------------------- */ while ((entp = readdir(dirp))) { /* success */ ENTRY ent; ent.mName = entp->d_name; if (exceptDots && (ent.mName == "." || ent.mName == "..")) continue; /** * FIXME : set entry type * @see http://stackoverflow.com/questions/3828192/checking-if-a-directory-exists-in-unix-system-call * @remarks * do not use 'd_type' field in 'struct dirent'. * 'd_type' field is not supported in unix operating system(s) such as SunOS/Solaris. * to support linux and unix, S_ISDIR() macro should be used. */ #if 0 switch (entp->d_type) { case DT_DIR: ent.mType = DIRECTORY; break; case DT_REG: ent.mType = REGULAR_FILE; break; default: ent.mType = UNDEFINED; break; } #endif entryList.push_back(ent); } closedir(dirp); if (error != errno) THROW_EXCEPTION("cannot read directory {" << path << "} (" << errno << ":" << strerror(errno) << ")"); entryList.sort(compareByName); return entryList; }