///////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2008-2012 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>
//
// See accompanying file COPYING.TXT file for licensing details.
//
///////////////////////////////////////////////////////////////////////////////
#define CPPCMS_SOURCE
#if defined(__sun)
#define _POSIX_PTHREAD_SEMANTICS
#endif
#include "session_posix_file_storage.h"
#include <cppcms/cppcms_error.h>
#include <cppcms/config.h>
#include "crc32.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stddef.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <cppcms/cstdint.h>
#include "posix_util.h"
namespace cppcms {
namespace sessions {
struct session_file_storage::_data {};
using namespace cppcms::impl;
session_file_storage::session_file_storage(std::string path,int concurrency_hint,int procs_no,bool force_flock) :
memory_(MAP_FAILED)
{
if(path.empty()){
if(::getenv("TEMP"))
path_=std::string(::getenv("TEMP")) + "/cppcms_sessions";
else if(::getenv("TMP"))
path_=std::string(::getenv("TMP")) + "/cppcms_sessions";
else
path_ = "/tmp/cppcms_sessions";
}
else
path_=path;
if(::mkdir(path_.c_str(),0777) < 0) {
if(errno!=EEXIST) {
int err=errno;
throw cppcms_error(err,"Failed to create a directory for session storage " + path_);
}
}
lock_size_ = concurrency_hint;
if(force_flock || (procs_no > 1 && !test_pthread_mutex_pshared()))
file_lock_=true;
else
file_lock_=false;
if(!file_lock_ && procs_no > 1) {
#ifdef MAP_ANONYMOUS
int flags = MAP_ANONYMOUS | MAP_SHARED;
#else // defined(MAP_ANON)
int flags = MAP_ANON | MAP_SHARED;
#endif
memory_ = ::mmap(0,sizeof(pthread_mutex_t) * lock_size_,PROT_READ | PROT_WRITE,flags,-1,0);
if(memory_ == MAP_FAILED)
throw cppcms_error(errno,"Memory map failed:");
locks_ = reinterpret_cast<pthread_mutex_t *>(memory_);
for(unsigned i=0;i<lock_size_;i++)
create_mutex(locks_+i,true);
}
else {
mutexes_.resize(lock_size_);
locks_ = &mutexes_.front();
for(unsigned i=0;i<lock_size_;i++)
create_mutex(locks_+i,false);
}
}
session_file_storage::~session_file_storage()
{
if(memory_ !=MAP_FAILED) {
for(unsigned i=0;i<lock_size_;i++)
destroy_mutex(reinterpret_cast<pthread_mutex_t *>(memory_) + i);
munmap((char*)memory_,sizeof(pthread_mutex_t) * lock_size_);
}
else {
for(unsigned i=0;i<lock_size_;i++)
destroy_mutex(&mutexes_[i]);
}
}
pthread_mutex_t *session_file_storage::sid_to_pos(std::string const &sid)
{
char buf[5] = { sid[0],sid[1],sid[2],sid[3],0};
unsigned pos;
sscanf(buf,"%x",&pos);
return locks_ + (pos % lock_size_);
}
void session_file_storage::lock(std::string const &sid)
{
pthread_mutex_lock(sid_to_pos(sid));
}
void session_file_storage::unlock(std::string const &sid)
{
pthread_mutex_unlock(sid_to_pos(sid));
}
std::string session_file_storage::file_name(std::string const &sid)
{
return path_ + "/" + sid;
}
class session_file_storage::locked_file {
public:
locked_file(session_file_storage *object,std::string sid,bool create) :
object_(object),
sid_(sid),
fd_(-1)
{
name_=object_->file_name(sid);
object_->lock(sid_);
for(;;) {
if(create)
fd_=::open(name_.c_str(),O_CREAT | O_RDWR,0666);
else
fd_=::open(name_.c_str(),O_RDWR);
if(fd_ < 0) return;
if(!object_->file_lock_)
return;
struct flock lock;
memset(&lock,0,sizeof(lock));
lock.l_type=F_WRLCK;
lock.l_whence=SEEK_SET;
int res;
while((res=fcntl(fd_,F_SETLKW,&lock))!=0 && errno==EINTR)
;
if(res < 0) {
::close(fd_);
fd_=-1;
}
struct stat s_id,s_name;
if(::stat(name_.c_str(),&s_name) < 0) {
// looks like file was deleted
::close(fd_);
fd_=-1;
continue;
}
if(::fstat(fd_,&s_id) < 0) {
::close(fd_);
fd_=-1;
return;
}
if(s_id.st_ino!=s_name.st_ino || s_id.st_dev!=s_name.st_dev) {
::close(fd_);
fd_=-1;
continue;
}
return;
}
}
~locked_file()
{
if(fd_>=0) {
if(object_->file_lock_) {
struct flock lock;
memset(&lock,0,sizeof(lock));
lock.l_type=F_UNLCK;
lock.l_whence=SEEK_SET;
int res;
while((res=fcntl(fd_,F_SETLKW,&lock))!=0 && errno==EINTR)
;
}
::close(fd_);
}
object_->unlock(sid_);
}
int fd() { return fd_; }
std::string name() { return name_; }
private:
session_file_storage *object_;
std::string sid_;
int fd_;
std::string name_;
};
void session_file_storage::save(std::string const &sid,time_t timeout,std::string const &in)
{
locked_file file(this,sid,true);
int fd=file.fd();
if(fd<0)
throw cppcms_error(errno,"failed to create session file");
save_to_file(fd,timeout,in);
}
bool session_file_storage::load(std::string const &sid,time_t &timeout,std::string &out)
{
locked_file file(this,sid,false);
int fd=file.fd();
if(fd<0) return false;
if(!read_from_file(fd,timeout,out)) {
::unlink(file.name().c_str());
return false;
}
return true;
}
void session_file_storage::remove(std::string const &sid)
{
locked_file file(this,sid,false);
if(file.fd() >= 0)
::unlink(file.name().c_str());
}
bool session_file_storage::read_timestamp(int fd)
{
::lseek(fd,0,SEEK_SET);
int64_t stamp;
if(!read_all(fd,&stamp,sizeof(stamp)) || stamp < ::time(0))
return false;
return true;
}
bool session_file_storage::is_blocking()
{
return file_lock_;
}
bool session_file_storage::read_from_file(int fd,time_t &timeout,std::string &data)
{
int64_t f_timeout;
uint32_t crc;
uint32_t size;
::lseek(fd,0,SEEK_SET);
if(!read_all(fd,&f_timeout,sizeof(f_timeout)))
return false;
if(f_timeout < time(0))
return false;
if(!read_all(fd,&crc,sizeof(crc)) || !read_all(fd,&size,sizeof(size)))
return false;
std::vector<char> buffer(size,0);
impl::crc32_calc crc_calc;
if(size > 0) {
if(!read_all(fd,&buffer.front(),size))
return false;
crc_calc.process_bytes(&buffer.front(),size);
}
uint32_t real_crc=crc_calc.checksum();
if(crc != real_crc)
return false;
timeout=f_timeout;
if(size > 0)
data.assign(&buffer.front(),size);
else
data.clear();
return true;
}
void session_file_storage::save_to_file(int fd,time_t timeout,std::string const &in)
{
struct {
int64_t timeout;
uint32_t crc;
uint32_t size;
} tmp = { timeout, 0, static_cast<uint32_t>(in.size()) };
impl::crc32_calc crc_calc;
crc_calc.process_bytes(in.data(),in.size());
tmp.crc=crc_calc.checksum();
if(!write_all(fd,&tmp,sizeof(tmp)) || !write_all(fd,in.data(),in.size()))
throw cppcms_error(errno,"Failed to write to file");
}
bool session_file_storage::write_all(int fd,void const *vbuf,int n)
{
char const *buf=reinterpret_cast<char const *>(vbuf);
while(n > 0) {
int res = ::write(fd,buf,n);
if(res < 0 && errno==EINTR)
continue;
if(res <= 0)
return false;
n-=res;
}
return true;
}
bool session_file_storage::read_all(int fd,void *vbuf,int n)
{
char *buf=reinterpret_cast<char *>(vbuf);
while(n > 0) {
int res = ::read(fd,buf,n);
if(res < 0 && errno==EINTR)
continue;
if(res <= 0)
return false;
n-=res;
}
return true;
}
void session_file_storage::gc()
{
DIR *d=0;
struct dirent *entry_st=0,*entry_p;
int path_len=pathconf(path_.c_str(),_PC_NAME_MAX);
if(path_len < 0 ) {
// Only "sessions" should be in this directory
// also this directory has high level of trust
// thus... Don't care about symlink exploits
#ifdef NAME_MAX
path_len=NAME_MAX;
#elif defined(PATH_MAX)
path_len=PATH_MAX;
#else
path_len=4096; // guess
#endif
}
// this is for Solaris...
entry_st=(struct dirent *)new char[sizeof(struct dirent)+path_len+1];
try{
if((d=::opendir(path_.c_str()))==NULL) {
int err=errno;
throw cppcms_error(err,"Failed to open directory :"+path_);
}
while(::readdir_r(d,entry_st,&entry_p)==0 && entry_p!=NULL) {
int i;
for(i=0;i<32;i++) {
if(!isxdigit(entry_st->d_name[i]))
break;
}
if(i!=32 || entry_st->d_name[i]!=0)
continue;
std::string sid=entry_st->d_name;
{
locked_file file(this,sid,false);
if(file.fd() >=0 && !read_timestamp(file.fd()))
::unlink(file.name().c_str());
}
}
::closedir(d);
}
catch(...) {
if(d) ::closedir(d);
delete [] entry_st;
throw;
}
delete [] entry_st;
}
struct session_file_storage_factory::_data {};
session_file_storage_factory::session_file_storage_factory(std::string path,int conc,int proc_no,bool force_lock) :
storage_(new session_file_storage(path,conc,proc_no,force_lock))
{
}
session_file_storage_factory::~session_file_storage_factory()
{
}
booster::shared_ptr<session_storage> session_file_storage_factory::get()
{
return storage_;
}
bool session_file_storage_factory::requires_gc()
{
return true;
}
void session_file_storage_factory::gc_job()
{
storage_->gc();
}
} // sessions
} // cppcms