Summary of Changes(1.65 -> 1.66)

==========================
List of Changes
==========================
1) Fixes bugs
    Fixes Issue 321: "no write permission for non-root user".
    (http://code.google.com/p/s3fs/issues/detail?id=321)
    Fixes a bug which s3fs does not set uid/gid headers when making symlink.

2) Cleanup  code.
    Adds a common function which  converts the Last-Modified header to utime.
    Deletes the useless cord and arranged it.

3) xmlns
    Changes that s3fs can decide using the xmlns url automatically.
    Then the noxmlns option is not needed anymore, but it is left.

4) Changes cache for performance
    Changes stat cache, it accumulates stat information and some headers.
    By adding some headers into cache, s3fs does not need to call curl_get_headers function.
    After changing, one cache entry increases in about 500 bytes from about 144 byte.
    
    Adds one condition to get out of the cache, that condition is by looking object's ETag.
    It works good for noticing changes about obojects.




git-svn-id: http://s3fs.googlecode.com/svn/trunk@400 df820570-a93a-0410-bd06-b72b767a4274
This commit is contained in:
ggtakec@gmail.com 2013-04-06 17:39:22 +00:00
parent a35cdc73b7
commit 8bd1483374
9 changed files with 751 additions and 412 deletions

View File

@ -1,7 +1,7 @@
dnl Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT(s3fs, 1.65)
AC_INIT(s3fs, 1.66)
AC_CANONICAL_SYSTEM

View File

@ -87,6 +87,7 @@ sets the url to use to access Amazon S3. If you want to use HTTPS, then you can
.TP
\fB\-o\fR noxmlns - disable registing xml name space.
disable registing xml name space for response of ListBucketResult and ListVersionsResult etc. Default name space is looked up from "http://s3.amazonaws.com/doc/2006-03-01".
This option should not be specified now, because s3fs looks up xmlns automatically after v1.66.
.TP
\fB\-o\fR nocopyapi - for other incomplete compatibility object storage.
For a distributed object storage which is compatibility S3 API without PUT(copy api).

View File

@ -24,141 +24,324 @@
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <iostream>
#include <assert.h>
#include <string>
#include <map>
#include <algorithm>
#include "common.h"
#include "cache.h"
#include "s3fs_util.h"
using namespace std;
//-------------------------------------------------------------------
// Typedef
// Static
//-------------------------------------------------------------------
typedef std::map<std::string, struct stat_cache_entry> stat_cache_t; // key=path
StatCache StatCache::singleton;
pthread_mutex_t StatCache::stat_cache_lock;
//-------------------------------------------------------------------
// Static valiables
// Constructor/Destructor
//-------------------------------------------------------------------
static stat_cache_t stat_cache;
static pthread_mutex_t stat_cache_lock;
//-------------------------------------------------------------------
// Functions
//-------------------------------------------------------------------
int init_stat_cache_mutex(void)
StatCache::StatCache()
{
return pthread_mutex_init(&stat_cache_lock, NULL);
if(this == StatCache::getStatCacheData()){
pthread_mutex_init(&(StatCache::stat_cache_lock), NULL);
}else{
assert(false);
}
CacheSize = 1000;
ExpireTime = 0;
IsExpireTime = false;
}
int destroy_stat_cache_mutex(void)
StatCache::~StatCache()
{
return pthread_mutex_destroy(&stat_cache_lock);
if(this == StatCache::getStatCacheData()){
pthread_mutex_destroy(&(StatCache::stat_cache_lock));
}else{
assert(false);
}
}
int get_stat_cache_entry(const char *path, struct stat *buf)
//-------------------------------------------------------------------
// Methods
//-------------------------------------------------------------------
unsigned long StatCache::GetCacheSize(void) const
{
int is_delete_cache = 0;
string strpath = path;
return CacheSize;
}
pthread_mutex_lock(&stat_cache_lock);
unsigned long StatCache::SetCacheSize(unsigned long size)
{
unsigned long old = CacheSize;
CacheSize = size;
return old;
}
time_t StatCache::GetExpireTime(void) const
{
return (IsExpireTime ? ExpireTime : (-1));
}
time_t StatCache::SetExpireTime(time_t expire)
{
time_t old = ExpireTime;
ExpireTime = expire;
IsExpireTime = true;
return old;
}
time_t StatCache::UnsetExpireTime(void)
{
time_t old = IsExpireTime ? ExpireTime : (-1);
ExpireTime = 0;
IsExpireTime = false;
return old;
}
bool StatCache::GetStat(string& key, struct stat* pst, headers_t* meta, bool overcheck, const char* petag)
{
bool is_delete_cache = false;
string strpath = key;
pthread_mutex_lock(&StatCache::stat_cache_lock);
stat_cache_t::iterator iter = stat_cache.end();
if('/' != strpath[strpath.length() - 1]){
if(overcheck && '/' != strpath[strpath.length() - 1]){
strpath += "/";
iter = stat_cache.find(strpath.c_str());
}
if(iter == stat_cache.end()){
strpath = path;
strpath = key;
iter = stat_cache.find(strpath.c_str());
}
if(iter != stat_cache.end()) {
if(!is_stat_cache_expire_time || ((*iter).second.cache_date + stat_cache_expire_time) >= time(NULL)){
// hit
FGPRINT(" stat cache hit [path=%s] [time=%ld] [hit count=%lu]\n",
strpath.c_str(), (*iter).second.cache_date, (*iter).second.hit_count);
if(buf != NULL){
*buf = (*iter).second.stbuf;
if(!IsExpireTime|| ((*iter).second.cache_date + ExpireTime) >= time(NULL)){
// hit without checking etag
if(petag){
string stretag = (*iter).second.meta["ETag"];
if('\0' != petag[0] && 0 != strcmp(petag, stretag.c_str())){
is_delete_cache = true;
}
}
(*iter).second.hit_count++;
pthread_mutex_unlock(&stat_cache_lock);
return 0;
if(is_delete_cache){
// not hit by different ETag
FGPRINT(" stat cache not hit by ETag[path=%s][time=%ld][hit count=%lu][ETag(%s)!=(%s)]\n",
strpath.c_str(), (*iter).second.cache_date, (*iter).second.hit_count,
petag ? petag : "null", (*iter).second.meta["ETag"].c_str());
}else{
// hit
FGPRINT(" stat cache hit [path=%s] [time=%ld] [hit count=%lu]\n",
strpath.c_str(), (*iter).second.cache_date, (*iter).second.hit_count);
if(pst!= NULL){
*pst= (*iter).second.stbuf;
}
if(meta != NULL){
meta->clear();
(*meta) = (*iter).second.meta;
}
(*iter).second.hit_count++;
pthread_mutex_unlock(&StatCache::stat_cache_lock);
return true;
}
}else{
// timeout
is_delete_cache = 1;
is_delete_cache = true;
}
}
pthread_mutex_unlock(&stat_cache_lock);
pthread_mutex_unlock(&StatCache::stat_cache_lock);
if(is_delete_cache){
delete_stat_cache_entry(strpath.c_str());
DelStat(strpath);
}
return -1;
return false;
}
void add_stat_cache_entry(const char *path, struct stat *st)
bool StatCache::AddStat(std::string& key, headers_t& meta)
{
FGPRINT(" add_stat_cache_entry[path=%s]\n", path);
if(CacheSize< 1){
return true;
}
FGPRINT(" add_stat_cache_entry[path=%s]\n", key.c_str());
if(max_stat_cache_size < 1){
return;
if(stat_cache.size() > CacheSize){
if(!TruncateCache()){
return false;
}
}
if(stat_cache.size() > max_stat_cache_size){
truncate_stat_cache();
struct stat st;
if(!convert_header_to_stat(key.c_str(), meta, &st)){
return false;
}
pthread_mutex_lock(&stat_cache_lock);
stat_cache[path].stbuf = *st;
stat_cache[path].cache_date = time(NULL); // Set time.
pthread_mutex_unlock(&stat_cache_lock);
pthread_mutex_lock(&StatCache::stat_cache_lock);
stat_cache[key].stbuf = st;
stat_cache[key].hit_count = 0;
stat_cache[key].cache_date = time(NULL); // Set time.
//copy only some keys
for (headers_t::iterator iter = meta.begin(); iter != meta.end(); ++iter) {
string tag = (*iter).first;
string value = (*iter).second;
if(tag == "Content-Type"){
stat_cache[key].meta[tag] = value;
}else if(tag == "Content-Length"){
stat_cache[key].meta[tag] = value;
}else if(tag == "ETag"){
stat_cache[key].meta[tag] = value;
}else if(tag == "Last-Modified"){
stat_cache[key].meta[tag] = value;
}else if(tag.substr(0, 5) == "x-amz"){
stat_cache[key].meta[tag] = value;
}else{
// Check for upper case
transform(tag.begin(), tag.end(), tag.begin(), static_cast<int (*)(int)>(std::tolower));
if(tag.substr(0, 5) == "x-amz"){
stat_cache[key].meta[tag] = value;
}
}
}
pthread_mutex_unlock(&StatCache::stat_cache_lock);
return true;
}
void delete_stat_cache_entry(const char *path)
bool StatCache::TruncateCache(void)
{
FGPRINT(" delete_stat_cache_entry[path=%s]\n", path);
string path_to_delete;
unsigned int lowest_hit_count = 0;
pthread_mutex_lock(&stat_cache_lock);
stat_cache_t::iterator iter = stat_cache.find(path);
pthread_mutex_lock(&StatCache::stat_cache_lock);
stat_cache_t::iterator iter;
for(iter = stat_cache.begin(); iter != stat_cache.end(); iter++) {
if(!lowest_hit_count) {
lowest_hit_count = (*iter).second.hit_count;
path_to_delete = (*iter).first;
}
if(lowest_hit_count > (*iter).second.hit_count){
lowest_hit_count = (*iter).second.hit_count;
path_to_delete = (*iter).first;
}
}
stat_cache.erase(path_to_delete);
pthread_mutex_unlock(&StatCache::stat_cache_lock);
FGPRINT(" truncate_stat_cache_entry[path=%s]\n", path_to_delete.c_str());
return true;
}
bool StatCache::DelStat(const char* key)
{
if(!key){
return false;
}
FGPRINT(" delete_stat_cache_entry[path=%s]\n", key);
pthread_mutex_lock(&StatCache::stat_cache_lock);
stat_cache_t::iterator iter = stat_cache.find(key);
if(iter != stat_cache.end()){
stat_cache.erase(iter);
}
if(0 < strlen(path) && '/' != path[strlen(path) - 1]){
// If there is "path/" cache, delete it.
string strpath = path;
strpath += "/";
if(0 < strlen(key) && 0 != strcmp(key, "/")){
string strpath = key;
if('/' == strpath[strpath.length() - 1]){
// If there is "path" cache, delete it.
strpath = strpath.substr(0, strpath.length() - 1);
}else{
// If there is "path/" cache, delete it.
strpath += "/";
}
iter = stat_cache.find(strpath.c_str());
if(iter != stat_cache.end()){
stat_cache.erase(iter);
}
}
pthread_mutex_unlock(&stat_cache_lock);
pthread_mutex_unlock(&StatCache::stat_cache_lock);
return true;
}
void truncate_stat_cache() {
string path_to_delete;
unsigned int hit_count = 0;
unsigned int lowest_hit_count = 0;
//-------------------------------------------------------------------
// Functions
//-------------------------------------------------------------------
bool convert_header_to_stat(const char* path, headers_t& meta, struct stat* pst)
{
headers_t::const_iterator iter;
pthread_mutex_lock(&stat_cache_lock);
stat_cache_t::iterator iter;
for(iter = stat_cache.begin(); iter != stat_cache.end(); iter++) {
hit_count = (* iter).second.hit_count;
if(!path || !pst){
return false;
}
memset(pst, 0, sizeof(struct stat));
if(!lowest_hit_count) {
lowest_hit_count = hit_count;
path_to_delete = (* iter).first;
}
if(lowest_hit_count > hit_count){
path_to_delete = (* iter).first;
}
pst->st_nlink = 1; // see fuse FAQ
// mode
iter = meta.find("x-amz-meta-mode");
if(iter != meta.end()){
pst->st_mode = get_mode((*iter).second.c_str());
}
stat_cache.erase(path_to_delete);
pthread_mutex_unlock(&stat_cache_lock);
// content-type
string strConType;
iter = meta.find("Content-Type");
if(iter != meta.end()){
strConType = (*iter).second;
}
if(strConType == "application/x-directory"){
pst->st_mode |= S_IFDIR;
}else if(0 < strlen(path) && '/' == path[strlen(path) - 1]){
if(strConType == "binary/octet-stream" || strConType == "application/octet-stream"){
pst->st_mode |= S_IFDIR;
}else{
pst->st_mode |= S_IFREG;
}
}else{
pst->st_mode |= S_IFREG;
}
FGPRINT(" purged %s from the stat cache\n", path_to_delete.c_str());
// blocks
if(S_ISREG(pst->st_mode)){
pst->st_blocks = get_blocks(pst->st_size);
}
// mtime
iter = meta.find("x-amz-meta-mtime");
if(iter != meta.end()){
pst->st_mtime = get_mtime((*iter).second.c_str());
}
if(pst->st_mtime == 0) {
iter = meta.find("Last-Modified");
if(iter != meta.end()){
pst->st_mtime = get_lastmodified((*iter).second.c_str());
}
}
if(-1 == pst->st_mtime){
pst->st_mtime = 0;
}
// size
iter = meta.find("Content-Length");
if(iter != meta.end()){
pst->st_size = get_size((*iter).second.c_str());
}
// uid/gid
iter = meta.find("x-amz-meta-uid");
if(iter != meta.end()){
pst->st_uid = get_uid((*iter).second.c_str());
}
iter = meta.find("x-amz-meta-gid");
if(iter != meta.end()){
pst->st_gid = get_gid((*iter).second.c_str());
}
return true;
}

View File

@ -1,29 +1,89 @@
#ifndef S3FS_CACHE_H_
#define S3FS_CACHE_H_
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "common.h"
//
// Struct
//
struct stat_cache_entry {
struct stat stbuf;
struct stat stbuf;
unsigned long hit_count;
time_t cache_date;
time_t cache_date;
headers_t meta;
stat_cache_entry() : hit_count(0), cache_date(0) {}
stat_cache_entry() : hit_count(0), cache_date(0) {
memset(&stbuf, 0, sizeof(struct stat));
meta.clear();
}
};
typedef std::map<std::string, struct stat_cache_entry> stat_cache_t; // key=path
//
// Class
//
class StatCache
{
private:
static StatCache singleton;
static pthread_mutex_t stat_cache_lock;
stat_cache_t stat_cache;
bool IsExpireTime;
time_t ExpireTime;
unsigned long CacheSize;
private:
bool GetStat(std::string& key, struct stat* pst, headers_t* meta, bool overcheck, const char* petag);
// Truncate stat cache
bool TruncateCache(void);
public:
StatCache();
~StatCache();
// Reference singleton
static StatCache* getStatCacheData(void) {
return &singleton;
}
// Attribute
unsigned long GetCacheSize(void) const;
unsigned long SetCacheSize(unsigned long size);
time_t GetExpireTime(void) const;
time_t SetExpireTime(time_t expire);
time_t UnsetExpireTime(void);
// Get stat cache
bool GetStat(std::string& key, struct stat* pst, headers_t* meta, bool overcheck = true) {
return GetStat(key, pst, meta, overcheck, NULL);
}
bool GetStat(std::string& key, struct stat* pst, bool overcheck = true) {
return GetStat(key, pst, NULL, overcheck, NULL);
}
bool GetStat(std::string& key, headers_t* meta, bool overcheck = true) {
return GetStat(key, NULL, meta, overcheck, NULL);
}
bool HasStat(std::string& key, bool overcheck = true) {
return GetStat(key, NULL, NULL, overcheck, NULL);
}
bool HasStat(std::string& key, const char* etag, bool overcheck = true) {
return GetStat(key, NULL, NULL, overcheck, etag);
}
// Add stat cache
bool AddStat(std::string& key, headers_t& meta);
// Delete stat cache
bool DelStat(const char* key);
bool DelStat(std::string& key) {
return DelStat(key.c_str());
}
};
//
// Functions
//
int init_stat_cache_mutex(void);
int destroy_stat_cache_mutex(void);
int get_stat_cache_entry(const char *path, struct stat *buf);
void add_stat_cache_entry(const char *path, struct stat *st);
void delete_stat_cache_entry(const char *path);
void truncate_stat_cache();
bool convert_header_to_stat(const char* path, headers_t& meta, struct stat* pst);
#endif // S3FS_CACHE_H_

View File

@ -33,9 +33,6 @@ typedef std::map<std::string, std::string> headers_t;
//
extern bool debug;
extern bool foreground;
extern unsigned long max_stat_cache_size;
extern time_t stat_cache_expire_time;
extern int is_stat_cache_expire_time;
extern int retries;
extern long connect_timeout;
extern time_t readwrite_timeout;

View File

@ -212,6 +212,7 @@ int curl_get_headers(const char *path, headers_t &meta)
// file exists in s3
// fixme: clean this up.
meta.clear();
for (headers_t::iterator iter = responseHeaders.begin(); iter != responseHeaders.end(); ++iter) {
string key = (*iter).first;
string value = (*iter).second;

File diff suppressed because it is too large Load Diff

View File

@ -65,7 +65,7 @@ string get_realpath(const char *path) {
// If there are "dir" and "dir/" object on S3, s3fs only recognizes "dir/".
// On this case, user can not know the "dir" object.
//
int insert_object(const char *name, struct s3_object **head)
int insert_object(const char* name, const char* etag, struct s3_object** head)
{
struct s3_object *cur_object;
struct s3_object *new_object;
@ -88,21 +88,31 @@ int insert_object(const char *name, struct s3_object **head)
is_have_cdelimiter = 1;
}
if(cLen == nLen){
// same object
if(is_have_cdelimiter == is_have_ndelimiter){
// perfect same object
// perfect same object, replace only etag.
}else if(is_have_cdelimiter){
// already set "dir/", so not need to add this.
return 0;
}else{
// new object is "dir/", replace name and etag
free(cur_object->name);
if(NULL == (cur_object->name = strdup(name))){
printf("insert_object: could not allocate memory\n");
S3FS_FUSE_EXIT();
return -1;
}
}
if(is_have_cdelimiter){
// already set "dir/"
return 0;
// replace etag.
if(cur_object->etag){
free(cur_object->etag);
cur_object->etag = NULL;
}
// new object is "dir/", replace name.
free(cur_object->name);
if(NULL == (cur_object->name = strdup(name))){
printf("insert_object: could not allocate memory\n");
S3FS_FUSE_EXIT();
return -1;
if(etag){
if(NULL == (cur_object->etag = strdup(etag))){
printf("insert_object: could not allocate memory\n");
S3FS_FUSE_EXIT();
return -1;
}
}
return 0;
}
@ -110,7 +120,7 @@ int insert_object(const char *name, struct s3_object **head)
}
// Not found same object.
new_object = (struct s3_object *) malloc(sizeof(struct s3_object));
new_object = (struct s3_object*)malloc(sizeof(struct s3_object));
if(new_object == NULL) {
printf("insert_object: could not allocate memory\n");
S3FS_FUSE_EXIT();
@ -123,12 +133,23 @@ int insert_object(const char *name, struct s3_object **head)
S3FS_FUSE_EXIT();
return -1;
}
if(etag){
if(NULL == (new_object->etag = strdup(etag))){
free(new_object->name);
free(new_object);
printf("insert_object: could not allocate memory\n");
S3FS_FUSE_EXIT();
return -1;
}
}else{
new_object->etag = NULL;
}
if((*head) == NULL)
if((*head) == NULL){
new_object->next = NULL;
else
}else{
new_object->next = (*head);
}
*head = new_object;
return 0;
@ -137,6 +158,9 @@ int insert_object(const char *name, struct s3_object **head)
int free_object(struct s3_object *object)
{
free(object->name);
if(object->etag){
free(object->etag);
}
free(object);
object = NULL;
@ -400,6 +424,16 @@ blkcnt_t get_blocks(off_t size)
return size / 512 + 1;
}
time_t get_lastmodified(const char* s)
{
struct tm tm;
if(!s){
return 0L;
}
strptime(s, "%a, %d %b %Y %H:%M:%S %Z", &tm);
return mktime(&tm); // GMT
}
//-------------------------------------------------------------------
// Help
//-------------------------------------------------------------------
@ -467,6 +501,8 @@ void show_help (void)
" disable registing xml name space for response of \n"
" ListBucketResult and ListVersionsResult etc. Default name \n"
" space is looked up from \"http://s3.amazonaws.com/doc/2006-03-01\".\n"
" This option should not be specified now, because s3fs looks up\n"
" xmlns automatically after v1.66.\n"
"\n"
" nocopyapi - for other incomplete compatibility object storage.\n"
" For a distributed object storage which is compatibility S3\n"

View File

@ -5,7 +5,8 @@
// Typedef
//-------------------------------------------------------------------
struct s3_object {
char *name;
char* name;
char* etag;
struct s3_object *next;
};
@ -23,7 +24,7 @@ typedef struct mvnode {
//-------------------------------------------------------------------
std::string get_realpath(const char *path);
int insert_object(const char *name, struct s3_object **head);
int insert_object(const char* name, const char* etag, struct s3_object** head);
int free_object(struct s3_object *object);
int free_object_list(struct s3_object *head);
@ -44,6 +45,7 @@ mode_t get_mode(const char *s);
uid_t get_uid(const char *s);
gid_t get_gid(const char *s);
blkcnt_t get_blocks(off_t size);
time_t get_lastmodified(const char* s);
void show_usage(void);
void show_help(void);