Files
s3fs-fuse/src/fdcache.cpp
Ka-Hing Cheung 03d84a07d1 fix rename before close
nautilus does this when you drag and drop to overwrite a file:

1) create .goutputstream-XXXXXX to write to
2) fsync the fd for .goutputstream-XXXXXX
3) rename .goutputstream-XXXXXX to target file
4) close the fd for .goutputstream-XXXXXX

previously, doing this on s3fs would result in an empty target file
because after the rename, s3fs would not flush the content of
.goutputstream-XXXXXX to target file.

this change moves the FdEntity from the old path to the new path
whenever rename happens. On flush s3fs would now flush the correct
content to the rename target.
2015-01-12 15:05:54 -08:00

1220 lines
30 KiB
C++

/*
* s3fs - FUSE-based file system backed by Amazon S3
*
* Copyright 2007-2013 Takeshi Nakatani <ggtakec.com>
*
* 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 <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/file.h>
#include <stdint.h>
#include <unistd.h>
#include <pthread.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <curl/curl.h>
#include <string>
#include <iostream>
#include <sstream>
#include <map>
#include <list>
#include <vector>
#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( "/<cache_dir>/.<bucket_name>.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<off_t>(FdManager::GetPageSize()) ? FdManager::GetPageSize() : static_cast<size_t>(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<off_t>(FdManager::GetPageSize()) ? static_cast<off_t>(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_t>(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<off_t>(FdManager::GetPageSize())) < size ? FdManager::GetPageSize() : static_cast<size_t>(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)
{
for(fdpage_list_t::iterator iter = pages.begin(); iter != pages.end(); iter++){
if(start <= (*iter)->end()){
// 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)){
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<size_t>(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<off_t>((*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<off_t>(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<ssize_t>(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<off_t>(wsize), true);
}
}
return wsize;
}
//------------------------------------------------
// 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;
}
//------------------------------------------------
// 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)
{
FPRNINFO("[path=%s]", SAFESTRPTR(path));
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){
return NULL;
}
return (*iter).second;
}
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());
fent[string(path)] = ent;
}else{
return NULL;
}
// open
if(-1 == ent->Open(size, time)){
return NULL;
}
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
*/