/* * s3fs - FUSE-based file system backed by Amazon S3 * * Copyright 2007-2013 Takeshi Nakatani * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "fdcache.h" #include "s3fs.h" #include "s3fs_util.h" #include "string_util.h" #include "curl.h" using namespace std; //------------------------------------------------ // Symbols //------------------------------------------------ #define MAX_MULTIPART_CNT 10000 // S3 multipart max count #define FDPAGE_SIZE (50 * 1024 * 1024) // 50MB(parallel uploading is 5 parallel(default) * 10 MB) //------------------------------------------------ // CacheFileStat class methods //------------------------------------------------ bool CacheFileStat::MakeCacheFileStatPath(const char* path, string& sfile_path, bool is_create_dir) { // make stat dir top path( "//..stat" ) string top_path = FdManager::GetCacheDir(); top_path += "/."; top_path += bucket; top_path += ".stat"; if(is_create_dir){ mkdirp(top_path + mydirname(path), 0777); } if(!path || '\0' == path[0]){ sfile_path = top_path; }else{ sfile_path = top_path + SAFESTRPTR(path); } return true; } bool CacheFileStat::DeleteCacheFileStat(const char* path) { if(!path || '\0' == path[0]){ return false; } // stat path string sfile_path; if(!CacheFileStat::MakeCacheFileStatPath(path, sfile_path, false)){ DPRNINFO("failed to create cache stat file path(%s)", path); return false; } if(0 != unlink(sfile_path.c_str())){ DPRNINFO("failed to delete file(%s): errno=%d", path, errno); return false; } return true; } //------------------------------------------------ // CacheFileStat methods //------------------------------------------------ CacheFileStat::CacheFileStat(const char* tpath) : path(""), fd(-1) { if(tpath && '\0' != tpath[0]){ SetPath(tpath, true); } } CacheFileStat::~CacheFileStat() { Release(); } bool CacheFileStat::SetPath(const char* tpath, bool is_open) { if(!tpath || '\0' == tpath[0]){ return false; } if(!Release()){ // could not close old stat file. return false; } if(tpath){ path = tpath; } if(!is_open){ return true; } return Open(); } bool CacheFileStat::Open(void) { if(0 == path.size()){ return false; } if(-1 != fd){ // already opened return true; } // stat path string sfile_path; if(!CacheFileStat::MakeCacheFileStatPath(path.c_str(), sfile_path, true)){ DPRN("failed to create cache stat file path(%s)", path.c_str()); return false; } // open if(-1 == (fd = open(sfile_path.c_str(), O_CREAT|O_RDWR, 0600))){ DPRNINFO("failed to open cache stat file path(%s) - errno(%d)", path.c_str(), errno); return false; } // lock if(-1 == flock(fd, LOCK_EX)){ DPRN("failed to lock cache stat file(%s) - errno(%d)", path.c_str(), errno); close(fd); fd = -1; return false; } // seek top if(0 != lseek(fd, 0, SEEK_SET)){ DPRN("failed to lseek cache stat file(%s) - errno(%d)", path.c_str(), errno); flock(fd, LOCK_UN); close(fd); fd = -1; return false; } DPRNINFO("file locked(%s - %s)", path.c_str(), sfile_path.c_str()); return true; } bool CacheFileStat::Release(void) { if(-1 == fd){ // already release return true; } // unlock if(-1 == flock(fd, LOCK_UN)){ DPRN("failed to unlock cache stat file(%s) - errno(%d)", path.c_str(), errno); return false; } DPRNINFO("file unlocked(%s)", path.c_str()); if(-1 == close(fd)){ DPRN("failed to close cache stat file(%s) - errno(%d)", path.c_str(), errno); return false; } fd = -1; return true; } //------------------------------------------------ // PageList methods //------------------------------------------------ void PageList::FreeList(fdpage_list_t& list) { for(fdpage_list_t::iterator iter = list.begin(); iter != list.end(); iter = list.erase(iter)){ delete (*iter); } list.clear(); } PageList::PageList(off_t size, bool is_init) { Init(size, is_init); } PageList::~PageList() { Clear(); } off_t PageList::Size(void) const { if(0 == pages.size()){ return 0; } fdpage_list_t::const_reverse_iterator riter = pages.rbegin(); return ((*riter)->offset + (*riter)->bytes); } int PageList::Resize(off_t size, bool is_init) { off_t total = Size(); if(0 == total){ Init(size, is_init); }else if(total < size){ off_t remain = size - total; // remaining bytes fdpage_list_t::reverse_iterator riter = pages.rbegin(); if((*riter)->bytes < FdManager::GetPageSize()){ // resize last area remain += (*riter)->bytes; // remaining bytes(without last page) (*riter)->bytes = remain > static_cast(FdManager::GetPageSize()) ? FdManager::GetPageSize() : static_cast(remain); // reset page size remain -= (*riter)->bytes; // remaining bytes(after last page) (*riter)->init = is_init; } // add new area for(off_t next = (*riter)->next(); 0 < remain; remain -= size, next += size){ size = remain > static_cast(FdManager::GetPageSize()) ? static_cast(FdManager::GetPageSize()) : remain; fdpage* page = new fdpage(next, size, is_init); pages.push_back(page); } }else if(total > size){ for(fdpage_list_t::reverse_iterator riter = pages.rbegin(); riter != pages.rend(); riter++){ if((*riter)->offset < size){ (*riter)->bytes = static_cast(size - (*riter)->offset); break; } } } return true; } void PageList::Clear(void) { PageList::FreeList(pages); } int PageList::Init(off_t size, bool is_init) { Clear(); for(off_t total = 0; total < size; total += FdManager::GetPageSize()){ size_t areasize = (total + static_cast(FdManager::GetPageSize())) < size ? FdManager::GetPageSize() : static_cast(size - total); fdpage* page = new fdpage(total, areasize, is_init); pages.push_back(page); } return pages.size(); } bool PageList::IsInit(off_t start, off_t size) { off_t next = start + size; if(0 == pages.size()){ return false; } // check end fdpage_list_t::reverse_iterator riter = pages.rbegin(); if((*riter)->next() < next){ // size is over end of page list. return false; } for(fdpage_list_t::iterator iter = pages.begin(); iter != pages.end(); iter++){ if(next <= (*iter)->offset){ break; } if((start <= (*iter)->offset && (*iter)->offset < next) || // start < iter-start < end (start <= (*iter)->end() && (*iter)->end() < next) || // start < iter-end < end ((*iter)->offset <= start && next <= (*iter)->end()) ) // iter-start < start < end < iter-end { if(!(*iter)->init){ return false; } } } return true; } bool PageList::SetInit(off_t start, off_t size, bool is_init) { // check size & resize if(Size() < (start + size)){ Resize(start + size, false); } off_t next = start + size; for(fdpage_list_t::iterator iter = pages.begin(); iter != pages.end(); iter++){ if((*iter)->end() < start){ // out of area // iter:start < iter:end < start < end continue; }else if(next <= (*iter)->offset){ // out of area // start < end < iter:start < iter:end break; } // area of target overlaps with iter area // iter:start < start < iter:end < end // iter:start < start < end < iter:end // start < iter:start < iter:end < end // start < iter:start < end < iter:end if((*iter)->init != is_init){ (*iter)->init = is_init; } } return true; } bool PageList::FindUninitPage(off_t start, off_t& resstart, size_t& ressize) { for(fdpage_list_t::iterator iter = pages.begin(); iter != pages.end(); iter++){ if(start <= (*iter)->end()){ if(!(*iter)->init){ resstart = (*iter)->offset; ressize = (*iter)->bytes; return true; } } } return false; } int PageList::GetUninitPages(fdpage_list_t& uninit_list, off_t start, off_t size) { for(fdpage_list_t::iterator iter = pages.begin(); iter != pages.end(); iter++){ if(start <= (*iter)->end()){ if((start + size) <= (*iter)->offset){ // reach to end break; } // after start pos if(!(*iter)->init){ // found uninitialized area fdpage_list_t::reverse_iterator riter = uninit_list.rbegin(); if(riter != uninit_list.rend() && (*riter)->next() == (*iter)->offset){ // merge to before page (*riter)->bytes += (*iter)->bytes; }else{ fdpage* page = new fdpage((*iter)->offset, (*iter)->bytes, false); uninit_list.push_back(page); } } } } return uninit_list.size(); } bool PageList::Serialize(CacheFileStat& file, bool is_output) { if(!file.Open()){ return false; } if(is_output){ // // put to file // stringstream ssall; ssall << Size(); for(fdpage_list_t::iterator iter = pages.begin(); iter != pages.end(); iter++){ ssall << "\n" << (*iter)->offset << ":" << (*iter)->bytes << ":" << ((*iter)->init ? "1" : "0"); } string strall = ssall.str(); if(0 >= pwrite(file.GetFd(), strall.c_str(), strall.length(), 0)){ DPRN("failed to write stats(%d)", errno); return false; } }else{ // // loading from file // struct stat st; memset(&st, 0, sizeof(struct stat)); if(-1 == fstat(file.GetFd(), &st)){ DPRN("fstat is failed. errno(%d)", errno); return false; } if(0 >= st.st_size){ // nothing Init(0, false); return true; } char* ptmp; if(NULL == (ptmp = (char*)calloc(st.st_size + 1, sizeof(char)))){ DPRNCRIT("could not allocate memory."); S3FS_FUSE_EXIT(); return false; } // read from file if(0 >= pread(file.GetFd(), ptmp, st.st_size, 0)){ DPRN("failed to read stats(%d)", errno); free(ptmp); return false; } string oneline; stringstream ssall(ptmp); // init Clear(); // load(size) if(!getline(ssall, oneline, '\n')){ DPRN("failed to parse stats."); free(ptmp); return false; } off_t total = s3fs_strtoofft(oneline.c_str()); // load each part bool is_err = false; while(getline(ssall, oneline, '\n')){ string part; stringstream ssparts(oneline); // offset if(!getline(ssparts, part, ':')){ is_err = true; break; } off_t offset = s3fs_strtoofft(part.c_str()); // size if(!getline(ssparts, part, ':')){ is_err = true; break; } off_t size = s3fs_strtoofft(part.c_str()); // init if(!getline(ssparts, part, ':')){ is_err = true; break; } bool is_init = (1 == s3fs_strtoofft(part.c_str()) ? true : false); // add new area SetInit(offset, size, is_init); } free(ptmp); if(is_err){ DPRN("failed to parse stats."); Clear(); return false; } // check size if(total != Size()){ DPRN("different size(%jd - %jd).", (intmax_t)total, (intmax_t)Size()); Clear(); return false; } } return true; } void PageList::Dump(void) { int cnt = 0; DPRNINFO("pages = {"); for(fdpage_list_t::iterator iter = pages.begin(); iter != pages.end(); iter++, cnt++){ DPRNINFO(" [%08d] -> {%014jd - %014zu : %s}", cnt, (intmax_t)((*iter)->offset), (*iter)->bytes, (*iter)->init ? "true" : "false"); } DPRNINFO("}"); } //------------------------------------------------ // FdEntity methods //------------------------------------------------ FdEntity::FdEntity(const char* tpath, const char* cpath) : is_lock_init(false), path(SAFESTRPTR(tpath)), cachepath(SAFESTRPTR(cpath)), fd(-1), file(NULL), is_modify(false) { try{ pthread_mutex_init(&fdent_lock, NULL); is_lock_init = true; }catch(exception& e){ DPRNCRIT("failed to init mutex"); } } FdEntity::~FdEntity() { Clear(); if(is_lock_init){ try{ pthread_mutex_destroy(&fdent_lock); }catch(exception& e){ DPRNCRIT("failed to destroy mutex"); } is_lock_init = false; } } void FdEntity::Clear(void) { AutoLock auto_lock(&fdent_lock); if(file){ if(0 != cachepath.size()){ CacheFileStat cfstat(path.c_str()); if(!pagelist.Serialize(cfstat, true)){ DPRN("failed to save cache stat file(%s).", path.c_str()); } } fclose(file); file = NULL; fd = -1; } pagelist.Init(0, false); refcnt = 0; path = ""; cachepath = ""; is_modify = false; } void FdEntity::Close(void) { FPRNINFO("[path=%s][fd=%d][refcnt=%d]", path.c_str(), fd, (-1 != fd ? refcnt - 1 : refcnt)); if(-1 != fd){ AutoLock auto_lock(&fdent_lock); if(0 < refcnt){ refcnt--; } if(0 == refcnt){ if(0 != cachepath.size()){ CacheFileStat cfstat(path.c_str()); if(!pagelist.Serialize(cfstat, true)){ DPRN("failed to save cache stat file(%s).", path.c_str()); } } fclose(file); file = NULL; fd = -1; } } } int FdEntity::Dup(void) { FPRNINFO("[path=%s][fd=%d][refcnt=%d]", path.c_str(), fd, (-1 != fd ? refcnt + 1 : refcnt)); if(-1 != fd){ AutoLock auto_lock(&fdent_lock); refcnt++; } return fd; } int FdEntity::Open(off_t size, time_t time) { bool already_opened = false; // already opened fd bool is_csf_loaded = false; // loaded by cache stat file bool is_truncate = false; // need to truncate bool init_value = false; // value for pagelist FPRNINFO("[path=%s][fd=%d][size=%jd][time=%jd]", path.c_str(), fd, (intmax_t)size, (intmax_t)time); if(-1 != fd){ // already opened, needs to increment refcnt. already_opened = true; }else{ // open if(0 != cachepath.size()){ // At first, open & flock stat file. { CacheFileStat cfstat(path.c_str()); is_csf_loaded = pagelist.Serialize(cfstat, false); } // open cache file if(is_csf_loaded && -1 != (fd = open(cachepath.c_str(), O_RDWR))){ // file exists struct stat st; memset(&st, 0, sizeof(struct stat)); if(-1 == fstat(fd, &st)){ DPRN("fstat is failed. errno(%d)", errno); fclose(file); file = NULL; fd = -1; return (0 == errno ? -EIO : -errno); } if((-1 != size && size != pagelist.Size()) || st.st_size != pagelist.Size()){ is_csf_loaded = false; // reinitializing if(-1 == size){ size = st.st_size; }else{ is_truncate = true; } }else{ // size OK! --> no initialize after this line. } }else{ // file does not exist -> create & open if(-1 == (fd = open(cachepath.c_str(), O_CREAT|O_RDWR|O_TRUNC, 0600))){ DPRN("failed to open file(%s). errno(%d)", cachepath.c_str(), errno); return (0 == errno ? -EIO : -errno); } if(-1 == size){ size = 0; }else{ is_truncate = true; } is_csf_loaded = false; } // make file pointer(for being same tmpfile) if(NULL == (file = fdopen(fd, "wb"))){ DPRN("failed to get fileno(%s). errno(%d)", cachepath.c_str(), errno); close(fd); fd = -1; return (0 == errno ? -EIO : -errno); } }else{ // open temporary file if(NULL == (file = tmpfile()) || -1 ==(fd = fileno(file))){ DPRN("failed to open tmp file. err(%d)", errno); if(file){ fclose(file); file = NULL; } return (0 == errno ? -EIO : -errno); } if(-1 == size){ size = 0; }else{ is_truncate = true; } } } // truncate if(is_truncate){ if(0 != ftruncate(fd, size) || 0 != fsync(fd)){ DPRN("ftruncate(%s) or fsync returned err(%d)", cachepath.c_str(), errno); fclose(file); file = NULL; fd = -1; return (0 == errno ? -EIO : -errno); } } // set mtime if(-1 != time){ if(0 != SetMtime(time)){ DPRN("failed to set mtime. errno(%d)", errno); fclose(file); file = NULL; fd = -1; return (0 == errno ? -EIO : -errno); } } // set internal data if(already_opened){ Dup(); }else{ if(!is_csf_loaded){ pagelist.Init(size, init_value); } refcnt = 1; is_modify = false; } return 0; } int FdEntity::SetMtime(time_t time) { FPRNINFO("[path=%s][fd=%d][time=%jd]", path.c_str(), fd, (intmax_t)time); if(-1 == time){ return 0; } if(-1 != fd){ AutoLock auto_lock(&fdent_lock); struct timeval tv[2]; tv[0].tv_sec = time; tv[0].tv_usec= 0L; tv[1].tv_sec = tv[0].tv_sec; tv[1].tv_usec= 0L; if(-1 == futimes(fd, tv)){ DPRN("futimes failed. errno(%d)", errno); return -errno; } }else if(0 < cachepath.size()){ // not opened file yet. struct utimbuf n_mtime; n_mtime.modtime = time; n_mtime.actime = time; if(-1 == utime(cachepath.c_str(), &n_mtime)){ DPRNINFO("utime failed. errno(%d)", errno); return -errno; } } return 0; } bool FdEntity::GetSize(off_t& size) { if(-1 == fd){ return false; } AutoLock auto_lock(&fdent_lock); size = pagelist.Size(); return true; } bool FdEntity::GetMtime(time_t& time) { struct stat st; if(!GetStats(st)){ return false; } time = st.st_mtime; return true; } bool FdEntity::GetStats(struct stat& st) { if(-1 == fd){ return false; } AutoLock auto_lock(&fdent_lock); memset(&st, 0, sizeof(struct stat)); if(-1 == fstat(fd, &st)){ DPRN("fstat failed. errno(%d)", errno); return false; } return true; } bool FdEntity::SetAllStatus(bool is_enable) { FPRNINFO("[path=%s][fd=%d][%s]", path.c_str(), fd, is_enable ? "enable" : "disable"); if(-1 == fd){ return false; } AutoLock auto_lock(&fdent_lock); // get file size struct stat st; memset(&st, 0, sizeof(struct stat)); if(-1 == fstat(fd, &st)){ DPRN("fstat is failed. errno(%d)", errno); return false; } // Reinit pagelist.Init(st.st_size, is_enable); return true; } int FdEntity::Load(off_t start, off_t size) { int result = 0; FPRNINFO("[path=%s][fd=%d][offset=%jd][size=%jd]", path.c_str(), fd, (intmax_t)start, (intmax_t)size); if(-1 == fd){ return -EBADF; } AutoLock auto_lock(&fdent_lock); // check loaded area & load fdpage_list_t uninit_list; if(0 < pagelist.GetUninitPages(uninit_list, start, size)){ for(fdpage_list_t::iterator iter = uninit_list.begin(); iter != uninit_list.end(); iter++){ if(-1 != size && (start + size) <= (*iter)->offset){ break; } // download if((*iter)->bytes >= static_cast(2 * S3fsCurl::GetMultipartSize()) && !nomultipart){ // default 20MB // parallel request // Additional time is needed for large files time_t backup = 0; if(120 > S3fsCurl::GetReadwriteTimeout()){ backup = S3fsCurl::SetReadwriteTimeout(120); } result = S3fsCurl::ParallelGetObjectRequest(path.c_str(), fd, (*iter)->offset, (*iter)->bytes); if(0 != backup){ S3fsCurl::SetReadwriteTimeout(backup); } }else{ // single request S3fsCurl s3fscurl; result = s3fscurl.GetObjectRequest(path.c_str(), fd, (*iter)->offset, (*iter)->bytes); } if(0 != result){ break; } // Set init flag pagelist.SetInit((*iter)->offset, static_cast((*iter)->bytes), true); } PageList::FreeList(uninit_list); } return result; } bool FdEntity::LoadFull(off_t* size, bool force_load) { int result; FPRNINFO("[path=%s][fd=%d]", path.c_str(), fd); if(-1 == fd){ if(0 != Open()){ return false; } } if(force_load){ SetAllDisable(); } // // TODO: possibly do background for delay loading // if(0 != (result = Load(0, pagelist.Size()))){ DPRN("could not download, result(%d)", result); return false; } if(is_modify){ AutoLock auto_lock(&fdent_lock); is_modify = false; } if(size){ *size = pagelist.Size(); } return true; } int FdEntity::RowFlush(const char* tpath, headers_t& meta, bool force_sync) { int result; FPRNINFO("[tpath=%s][path=%s][fd=%d]", SAFESTRPTR(tpath), path.c_str(), fd); if(-1 == fd){ return -EBADF; } AutoLock auto_lock(&fdent_lock); if(!force_sync && !is_modify){ // nothing to update. return 0; } /* * Make decision to do multi upload (or not) based upon file size * * According to the AWS spec: * - 1 to 10,000 parts are allowed * - minimum size of parts is 5MB (expect for the last part) * * For our application, we will define minimum part size to be 10MB (10 * 2^20 Bytes) * minimum file size will be 64 GB - 2 ** 36 * * Initially uploads will be done serially * * If file is > 20MB, then multipart will kick in */ if(pagelist.Size() > (MAX_MULTIPART_CNT * S3fsCurl::GetMultipartSize())){ // close f ? return -ENOTSUP; } // seek to head of file. if(0 != lseek(fd, 0, SEEK_SET)){ DPRN("lseek error(%d)", errno); return -errno; } if(pagelist.Size() >= (2 * S3fsCurl::GetMultipartSize()) && !nomultipart){ // default 20MB // Additional time is needed for large files time_t backup = 0; if(120 > S3fsCurl::GetReadwriteTimeout()){ backup = S3fsCurl::SetReadwriteTimeout(120); } result = S3fsCurl::ParallelMultipartUploadRequest(tpath ? tpath : path.c_str(), meta, fd); if(0 != backup){ S3fsCurl::SetReadwriteTimeout(backup); } }else{ S3fsCurl s3fscurl(true); result = s3fscurl.PutRequest(tpath ? tpath : path.c_str(), meta, fd); } // seek to head of file. if(0 == result && 0 != lseek(fd, 0, SEEK_SET)){ DPRN("lseek error(%d)", errno); return -errno; } if(0 == result){ is_modify = false; } return result; } ssize_t FdEntity::Read(char* bytes, off_t start, size_t size, bool force_load) { int result; ssize_t rsize; FPRNINFO("[path=%s][fd=%d][offset=%jd][size=%zu]", path.c_str(), fd, (intmax_t)start, size); if(-1 == fd){ return -EBADF; } if(force_load){ AutoLock auto_lock(&fdent_lock); pagelist.SetInit(start, static_cast(size), false); } // Loading if(0 != (result = Load(start, size))){ DPRN("could not download. start(%jd), size(%zu), errno(%d)", (intmax_t)start, size, result); return -EIO; } // Reading { AutoLock auto_lock(&fdent_lock); if(-1 == (rsize = pread(fd, bytes, size, start))){ DPRN("pread failed. errno(%d)", errno); return -errno; } } return rsize; } ssize_t FdEntity::Write(const char* bytes, off_t start, size_t size) { int result; ssize_t wsize; FPRNINFO("[path=%s][fd=%d][offset=%jd][size=%zu]", path.c_str(), fd, (intmax_t)start, size); if(-1 == fd){ return -EBADF; } // Load unitialized area which starts from 0 to (start + size) before writing. if(0 != (result = Load(0, start))){ DPRN("failed to load uninitialized area before writing(errno=%d)", result); return static_cast(result); } // Writing { AutoLock auto_lock(&fdent_lock); if(-1 == (wsize = pwrite(fd, bytes, size, start))){ DPRN("pwrite failed. errno(%d)", errno); return -errno; } if(!is_modify){ is_modify = true; } if(0 < wsize){ pagelist.SetInit(start, static_cast(wsize), true); } } return wsize; } //------------------------------------------------ // FdManager symbol //------------------------------------------------ // [NOTE] // NOCACHE_PATH_PREFIX symbol needs for not using cache mode. // Now s3fs I/F functions in s3fs.cpp has left the processing // to FdManager and FdEntity class. FdManager class manages // the list of local file stat and file descriptor in conjunction // with the FdEntity class. // When s3fs is not using local cache, it means FdManager must // return new temporary file descriptor at each opening it. // Then FdManager caches fd by key which is dummy file path // instead of real file path. // This process may not be complete, but it is easy way can // be realized. // #define NOCACHE_PATH_PREFIX_FORM " __S3FS_UNEXISTED_PATH_%lx__ / " // important space words for simply //------------------------------------------------ // FdManager class valiable //------------------------------------------------ FdManager FdManager::singleton; pthread_mutex_t FdManager::fd_manager_lock; bool FdManager::is_lock_init(false); string FdManager::cache_dir(""); size_t FdManager::page_size(FDPAGE_SIZE); //------------------------------------------------ // FdManager class methods //------------------------------------------------ bool FdManager::SetCacheDir(const char* dir) { if(!dir || '\0' == dir[0]){ cache_dir = ""; }else{ cache_dir = dir; } return true; } size_t FdManager::SetPageSize(size_t size) { // If already has entries, this function is failed. if(0 < FdManager::get()->fent.size()){ return -1; } size_t old = FdManager::page_size; FdManager::page_size = size; return old; } bool FdManager::DeleteCacheDirectory(void) { if(0 == FdManager::cache_dir.size()){ return true; } string cache_dir; if(!FdManager::MakeCachePath(NULL, cache_dir, false)){ return false; } return delete_files_in_dir(cache_dir.c_str(), true); } int FdManager::DeleteCacheFile(const char* path) { FPRNINFO("[path=%s]", SAFESTRPTR(path)); if(!path){ return -EIO; } if(0 == FdManager::cache_dir.size()){ return 0; } string cache_path = ""; if(!FdManager::MakeCachePath(path, cache_path, false)){ return 0; } int result = 0; if(0 != unlink(cache_path.c_str())){ DPRNINFO("failed to delete file(%s): errno=%d", path, errno); result = -errno; } if(!CacheFileStat::DeleteCacheFileStat(path)){ DPRNINFO("failed to delete stat file(%s): errno=%d", path, errno); if(0 != errno){ result = -errno; }else{ result = -EIO; } } return result; } bool FdManager::MakeCachePath(const char* path, string& cache_path, bool is_create_dir) { if(0 == FdManager::cache_dir.size()){ cache_path = ""; return true; } string resolved_path(FdManager::cache_dir + "/" + bucket); if(is_create_dir){ mkdirp(resolved_path + mydirname(path), 0777); } if(!path || '\0' == path[0]){ cache_path = resolved_path; }else{ cache_path = resolved_path + SAFESTRPTR(path); } return true; } bool FdManager::MakeRandomTempPath(const char* path, string& tmppath) { char szBuff[64]; sprintf(szBuff, NOCACHE_PATH_PREFIX_FORM, random()); // warry for performance, but maybe don't warry. tmppath = szBuff; tmppath += path ? path : ""; return true; } //------------------------------------------------ // FdManager methods //------------------------------------------------ FdManager::FdManager() { if(this == FdManager::get()){ try{ pthread_mutex_init(&FdManager::fd_manager_lock, NULL); FdManager::is_lock_init = true; }catch(exception& e){ FdManager::is_lock_init = false; DPRNCRIT("failed to init mutex"); } }else{ assert(false); } } FdManager::~FdManager() { if(this == FdManager::get()){ for(fdent_map_t::iterator iter = fent.begin(); fent.end() != iter; iter++){ FdEntity* ent = (*iter).second; delete ent; } fent.clear(); if(FdManager::is_lock_init){ try{ pthread_mutex_destroy(&FdManager::fd_manager_lock); }catch(exception& e){ DPRNCRIT("failed to init mutex"); } FdManager::is_lock_init = false; } }else{ assert(false); } } FdEntity* FdManager::GetFdEntity(const char* path, int existfd) { FPRNINFO("[path=%s][fd=%d]", SAFESTRPTR(path), existfd); if(!path || '\0' == path[0]){ return NULL; } AutoLock auto_lock(&FdManager::fd_manager_lock); fdent_map_t::iterator iter = fent.find(string(path)); if(fent.end() != iter && (-1 == existfd || (*iter).second->GetFd() == existfd)){ return (*iter).second; } if(-1 != existfd){ for(iter = fent.begin(); iter != fent.end(); iter++){ if((*iter).second && (*iter).second->GetFd() == existfd){ // found opend fd in map if(0 == strcmp((*iter).second->GetPath(), path)){ return (*iter).second; } // found fd, but it is used another file(file descriptor is recycled) // so returns NULL. break; } } } return NULL; } FdEntity* FdManager::Open(const char* path, off_t size, time_t time, bool force_tmpfile, bool is_create) { FdEntity* ent; FPRNINFO("[path=%s][size=%jd][time=%jd]", SAFESTRPTR(path), (intmax_t)size, (intmax_t)time); if(!path || '\0' == path[0]){ return NULL; } AutoLock auto_lock(&FdManager::fd_manager_lock); fdent_map_t::iterator iter = fent.find(string(path)); if(fent.end() != iter){ // found ent = (*iter).second; }else if(is_create){ // not found string cache_path = ""; if(!force_tmpfile && !FdManager::MakeCachePath(path, cache_path, true)){ DPRN("failed to make cache path for object(%s).", path); return NULL; } // make new obj ent = new FdEntity(path, cache_path.c_str()); if(0 < cache_path.size()){ // using cache fent[string(path)] = ent; }else{ // not using cache, so the key of fdentity is set not really existsing path. // (but not strictly unexisting path.) // // [NOTE] // The reason why this process here, please look at the definition of the // comments of NOCACHE_PATH_PREFIX_FORM symbol. // string tmppath(""); FdManager::MakeRandomTempPath(path, tmppath); fent[tmppath] = ent; } }else{ return NULL; } // open if(-1 == ent->Open(size, time)){ return NULL; } return ent; } FdEntity* FdManager::ExistOpen(const char* path, int existfd) { FPRNINFO("[path=%s][fd=%d]", SAFESTRPTR(path), existfd); // search by real path FdEntity* ent = Open(path, -1, -1, false, false); if(!ent && -1 != existfd){ // search from all fdentity because of not using cache. AutoLock auto_lock(&FdManager::fd_manager_lock); for(fdent_map_t::iterator iter = fent.begin(); iter != fent.end(); iter++){ if((*iter).second && (*iter).second->GetFd() == existfd && (*iter).second->IsOpen()){ // found opend fd in map if(0 == strcmp((*iter).second->GetPath(), path)){ ent = (*iter).second; // open if(-1 == ent->Open(-1, -1)){ return NULL; } }else{ // found fd, but it is used another file(file descriptor is recycled) // so returns NULL. } break; } } } return ent; } void FdManager::Rename(const std::string &from, const std::string &to) { fdent_map_t::iterator iter = fent.find(from); if(fent.end() != iter){ // found FPRNINFO("[from=%s][to=%s]", from.c_str(), to.c_str()); FdEntity* ent = (*iter).second; fent.erase(iter); ent->SetPath(to); fent[to] = ent; } } bool FdManager::Close(FdEntity* ent) { FPRNINFO("[ent->file=%s][ent->fd=%d]", ent ? ent->GetPath() : "", ent ? ent->GetFd() : -1); AutoLock auto_lock(&FdManager::fd_manager_lock); for(fdent_map_t::iterator iter = fent.begin(); iter != fent.end(); iter++){ if((*iter).second == ent){ ent->Close(); if(!ent->IsOpen()){ delete (*iter).second; fent.erase(iter); return true; } } } return false; } /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */