/*
Copyright (c) 2006-2009 Z RESEARCH, Inc. <http://www.zresearch.com>
This file is part of GlusterFS.
GlusterFS 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 3 of the License,
or (at your option) any later version.
GlusterFS 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, see
<http://www.gnu.org/licenses/>.
*/
/*
* TODO:
* Need to free_state() when fuse_reply_err() + return.
* Check loc->path for "" after fuse_loc_fill in all fops
* (now being done in getattr, lookup) or better - make
* fuse_loc_fill() and inode_path() return success/failure.
*/
#include <stdint.h>
#include <signal.h>
#include <pthread.h>
#ifndef _CONFIG_H
#define _CONFIG_H
#include "config.h"
#endif /* _CONFIG_H */
#include "glusterfs.h"
#include "logging.h"
#include "xlator.h"
#include "glusterfs.h"
#include "defaults.h"
#include "common-utils.h"
#include <fuse/fuse_lowlevel.h>
#include "fuse-extra.h"
#include "list.h"
#include "dict.h"
#include "compat.h"
#include "compat-errno.h"
/* TODO: when supporting posix acl, remove this definition */
#define DISABLE_POSIX_ACL
#define ZR_MOUNTPOINT_OPT "mountpoint"
#define ZR_DIRECT_IO_OPT "direct-io-mode"
#define BIG_FUSE_CHANNEL_SIZE 1048576
struct fuse_private {
int fd;
struct fuse *fuse;
struct fuse_session *se;
struct fuse_chan *ch;
char *volfile;
size_t volfile_size;
char *mount_point;
data_t *buf;
pthread_t fuse_thread;
char fuse_thread_started;
uint32_t direct_io_mode;
double entry_timeout;
double attribute_timeout;
};
typedef struct fuse_private fuse_private_t;
#define _FI_TO_FD(fi) ((fd_t *)((long)fi->fh))
#define FI_TO_FD(fi) ((_FI_TO_FD (fi))?(fd_ref (_FI_TO_FD(fi))):((fd_t *) 0))
#define FUSE_FOP(state, ret, op_num, fop, args ...) \
do { \
call_frame_t *frame = get_call_frame_for_req (state, 1); \
xlator_t *xl = frame->this->children ? \
frame->this->children->xlator : NULL; \
dict_t *refs = frame->root->req_refs; \
frame->root->state = state; \
frame->root->op = op_num; \
STACK_WIND (frame, ret, xl, xl->fops->fop, args); \
dict_unref (refs); \
} while (0)
typedef struct {
void *pool;
xlator_t *this;
inode_table_t *itable;
loc_t loc;
loc_t loc2;
fuse_req_t req;
int32_t flags;
off_t off;
size_t size;
unsigned long nlookup;
fd_t *fd;
dict_t *dict;
char *name;
char is_revalidate;
} fuse_state_t;
int fuse_chan_receive (struct fuse_chan *ch,
char *buf,
int32_t size);
static void
free_state (fuse_state_t *state)
{
loc_wipe (&state->loc);
loc_wipe (&state->loc2);
if (state->dict) {
dict_unref (state->dict);
state->dict = (void *)0xaaaaeeee;
}
if (state->name) {
FREE (state->name);
state->name = NULL;
}
if (state->fd) {
fd_unref (state->fd);
state->fd = (void *)0xfdfdfdfd;
}
#ifdef DEBUG
memset (state, 0x90, sizeof (*state));
#endif
FREE (state);
state = NULL;
}
fuse_state_t *
state_from_req (fuse_req_t req)
{
fuse_state_t *state;
xlator_t *this = NULL;
this = fuse_req_userdata (req);
state = (void *)calloc (1, sizeof (*state));
ERR_ABORT (state);
state->pool = this->ctx->pool;
state->itable = this->itable;
state->req = req;
state->this = this;
return state;
}
static pid_t
get_pid_from_req (fuse_req_t req)
{
const struct fuse_ctx *ctx = NULL;
ctx = fuse_req_ctx(req);
return ctx->pid;
}
static call_frame_t *
get_call_frame_for_req (fuse_state_t *state, char d)
{
call_pool_t *pool = state->pool;
fuse_req_t req = state->req;
const struct fuse_ctx *ctx = NULL;
call_frame_t *frame = NULL;
xlator_t *this = NULL;
fuse_private_t *priv = NULL;
if (req) {
this = fuse_req_userdata (req);
} else {
this = state->this;
}
priv = this->private;
frame = create_frame (this, pool);
if (req) {
ctx = fuse_req_ctx(req);
frame->root->uid = ctx->uid;
frame->root->gid = ctx->gid;
frame->root->pid = ctx->pid;
frame->root->unique = req_callid (req);
}
if (d) {
frame->root->req_refs = dict_ref (get_new_dict ());
dict_set (frame->root->req_refs, NULL, priv->buf);
}
frame->root->type = GF_OP_TYPE_FOP_REQUEST;
return frame;
}
GF_MUST_CHECK static int32_t
fuse_loc_fill (loc_t *loc,
fuse_state_t *state,
ino_t ino,
ino_t par,
const char *name)
{
inode_t *inode = NULL, *parent = NULL;
int32_t ret = -1;
char *path = NULL;
/* resistance against multiple invocation of loc_fill not to get
reference leaks via inode_search() */
inode = loc->inode;
if (!inode) {
if (ino)
inode = inode_search (state->itable, ino, NULL);
if (par && name)
inode = inode_search (state->itable, par, name);
loc->inode = inode;
if (inode)
loc->ino = inode->ino;
}
parent = loc->parent;
if (!parent) {
if (inode)
parent = inode_parent (inode, par, name);
else
parent = inode_search (state->itable, par, NULL);
loc->parent = parent;
}
if (name && parent) {
ret = inode_path (parent, name, &path);
if (ret <= 0) {
gf_log ("glusterfs-fuse", GF_LOG_ERROR,
"inode_path failed for %"PRId64"/%s",
parent->ino, name);
goto fail;
} else {
loc->path = path;
}
} else if (inode) {
ret = inode_path (inode, NULL, &path);
if (ret <= 0) {
gf_log ("glusterfs-fuse", GF_LOG_ERROR,
"inode_path failed for %"PRId64,
inode->ino);
goto fail;
} else {
loc->path = path;
}
}
if (loc->path) {
loc->name = strrchr (loc->path, '/');
if (loc->name)
loc->name++;
else loc->name = "";
}
if ((ino != 1) &&
(parent == NULL)) {
gf_log ("fuse-bridge", GF_LOG_ERROR,
"failed to search parent for %"PRId64"/%s (%"PRId64")",
(ino_t)par, name, (ino_t)ino);
ret = -1;
goto fail;
}
ret = 0;
fail:
return ret;
}
static int
need_fresh_lookup (int32_t op_ret, int32_t op_errno,
loc_t *loc, struct stat *buf)
{
if (op_ret == -1) {
gf_log ("fuse-bridge",
(op_errno == ENOENT)? GF_LOG_DEBUG: GF_LOG_WARNING,
"revalidate of %s failed (%s)",
loc->path, strerror (op_errno));
return 1;
}
if (loc->inode->ino != buf->st_ino) {
gf_log ("fuse-bridge", GF_LOG_WARNING,
"inode num of %s changed %"PRId64" -> %"PRId64,
loc->path, loc->inode->ino, buf->st_ino);
return 1;
}
if ((loc->inode->st_mode & S_IFMT) ^ (buf->st_mode & S_IFMT)) {
gf_log ("fuse-bridge", GF_LOG_WARNING,
"inode mode of %s changed 0%o -> 0%o",
loc->path, loc->inode->st_mode, buf->st_mode);
return 1;
}
return 0;
}
static int
fuse_lookup_cbk (call_frame_t *frame,
void *cookie,
xlator_t *this,
int32_t op_ret,
int32_t op_errno,
inode_t *inode,
struct stat *stat,
dict_t *dict);
static int
fuse_entry_cbk (call_frame_t *frame,
void *cookie,
xlator_t *this,
int32_t op_ret,
int32_t op_errno,
inode_t *inode,
struct stat *buf)
{
fuse_state_t *state;
fuse_req_t req;
struct fuse_entry_param e = {0, };
fuse_private_t *priv = this->private;
state = frame->root->state;
req = state->req;
if (!op_ret && state->loc.ino == 1) {
buf->st_ino = 1;
}
if (state->is_revalidate == 1
&& need_fresh_lookup (op_ret, op_errno, &state->loc, buf)) {
inode_unref (state->loc.inode);
state->loc.inode = inode_new (state->itable);
state->is_revalidate = 2;
STACK_WIND (frame, fuse_lookup_cbk,
FIRST_CHILD (this),
FIRST_CHILD (this)->fops->lookup,
&state->loc, state->dict);
return 0;
}
if (op_ret == 0) {
gf_log ("glusterfs-fuse", GF_LOG_DEBUG,
"%"PRId64": %s() %s => %"PRId64" (%"PRId64")",
frame->root->unique, gf_fop_list[frame->root->op],
state->loc.path, buf->st_ino, state->loc.ino);
inode_link (inode, state->loc.parent, state->loc.name, buf);
inode_lookup (inode);
/* TODO: make these timeouts configurable (via meta?) */
e.ino = inode->ino;
#ifdef GF_DARWIN_HOST_OS
e.generation = 0;
#else
e.generation = buf->st_ctime;
#endif
e.entry_timeout = priv->entry_timeout;
e.attr_timeout = priv->attribute_timeout;
e.attr = *buf;
e.attr.st_blksize = BIG_FUSE_CHANNEL_SIZE;
if (!e.ino || !buf->st_ino) {
gf_log ("glusterfs-fuse", GF_LOG_ERROR,
"%"PRId64": %s() %s returning inode 0",
frame->root->unique,
gf_fop_list[frame->root->op], state->loc.path);
}
if (state->loc.parent)
fuse_reply_entry (req, &e);
else
fuse_reply_attr (req, buf, priv->attribute_timeout);
} else {
gf_log ("glusterfs-fuse",
(op_errno == ENOENT ? GF_LOG_DEBUG : GF_LOG_ERROR),
"%"PRId64": %s() %s => -1 (%s)", frame->root->unique,
gf_fop_list[frame->root->op], state->loc.path,
strerror (op_errno));
fuse_reply_err (req, op_errno);
}
free_state (state);
STACK_DESTROY (frame->root);
return 0;
}
static int
fuse_lookup_cbk (call_frame_t *frame,
void *cookie,
xlator_t *this,
int32_t op_ret,
int32_t op_errno,
inode_t *inode,
struct stat *stat,
dict_t *dict)
{
fuse_entry_cbk (frame, cookie, this, op_ret, op_errno, inode, stat);
return 0;
}
static void
fuse_lookup (fuse_req_t req,
fuse_ino_t par,
const char *name)
{
fuse_state_t *state;
int32_t ret = -1;
state = state_from_req (req);
ret = fuse_loc_fill (&state->loc, state, 0, par, name);
if (ret < 0) {
gf_log ("glusterfs-fuse", GF_LOG_ERROR,
"%"PRId64": LOOKUP %"PRId64"/%s (fuse_loc_fill() failed)",
req_callid (req), (ino_t)par, name);
free_state (state);
fuse_reply_err (req, EINVAL);
return;
}
if (!state->loc.inode) {
gf_log ("glusterfs-fuse", GF_LOG_DEBUG,
"%"PRId64": LOOKUP %s", req_callid (req),
state->loc.path);
state->loc.inode = inode_new (state->itable);
/* to differntiate in entry_cbk what kind of call it is */
state->is_revalidate = -1;
} else {
gf_log ("glusterfs-fuse", GF_LOG_DEBUG,
"%"PRId64": LOOKUP %s(%"PRId64")", req_callid (req),
state->loc.path, state->loc.inode->ino);
state->is_revalidate = 1;
}
state->dict = dict_new();
FUSE_FOP (state, fuse_lookup_cbk, GF_FOP_LOOKUP,
lookup, &state->loc, state->dict);
}
static void
fuse_forget (fuse_req_t req,
fuse_ino_t ino,
unsigned long nlookup)
{
inode_t *fuse_inode;
fuse_state_t *state;
if (ino == 1) {
fuse_reply_none (req);
return;
}
state = state_from_req (req);
fuse_inode = inode_search (state->itable, ino, NULL);
if (fuse_inode) {
gf_log ("glusterfs-fuse", GF_LOG_DEBUG,
"got forget on inode (%lu)", ino);
inode_forget (fuse_inode, nlookup);
inode_unref (fuse_inode);
} else {
gf_log ("glusterfs-fuse", GF_LOG_ERROR,
"got forget, but inode (%lu) not found", ino);
}
free_state (state);
fuse_reply_none (req);
}
static int
fuse_attr_cbk (call_frame_t *frame,
void *cookie,
xlator_t *this,
int32_t op_ret,
int32_t op_errno,
struct stat *buf)
{
fuse_state_t *state;
fuse_req_t req;
fuse_private_t *priv = this->private;
state = frame->root->state;
req = state->req;
if (op_ret == 0) {
gf_log ("glusterfs-fuse",
(buf->st_ino ? GF_LOG_DEBUG : GF_LOG_ERROR),
"%"PRId64": %s() %s => %"PRId64, frame->root->unique,
gf_fop_list[frame->root->op],
state->loc.path ? state->loc.path : "ERR",
buf->st_ino);
/* TODO: make these timeouts configurable via meta */
/* TODO: what if the inode number has changed by now */
buf->st_blksize = BIG_FUSE_CHANNEL_SIZE;
fuse_reply_attr (req, buf, priv->attribute_timeout);
} else {
gf_log ("glusterfs-fuse", GF_LOG_ERROR,
"%"PRId64": %s() %s => -1 (%s)", frame->root->unique,
gf_fop_list[frame->root->op],
state->loc.path ? state->loc.path : "ERR",
strerror (op_errno));
fuse_reply_err (req, op_errno);
}
free_state (state);
STACK_DESTROY (frame->root);
return 0;
}
static void
fuse_getattr (fuse_req_t req,
fuse_ino_t ino,
struct fuse_file_info *fi)
{
fuse_state_t *state;
fd_t *fd = NULL;
int32_t ret = -1;
state = state_from_req (req);
if (ino == 1) {
ret = fuse_loc_fill (&state->loc, state, ino, 0, NULL);
if (ret < 0) {
gf_log ("glusterfs-fuse", GF_LOG_ERROR,
"%"PRId64": GETATTR %"PRId64" (fuse_loc_fill() failed)",
req_callid(req), (ino_t)ino);
fuse_reply_err (req, EINVAL);
free_state (state);
return;
}
if (state->loc.inode)
state->is_revalidate = 1;
else
state->is_revalidate = -1;
state->dict = dict_new();
FUSE_FOP (state, fuse_lookup_cbk, GF_FOP_LOOKUP,
lookup, &state->loc, state->dict);
return;
}
ret = fuse_loc_fill (&state->loc, state, ino, 0, NULL);
if (!state->loc.inode) {
gf_log ("glusterfs-fuse", GF_LOG_ERROR,
"%"PRId64": GETATTR %"PRId64" (%s) (fuse_loc_fill() returned NULL inode)",
req_callid (req), (int64_t)ino, state->loc.path);
fuse_reply_err (req, EINVAL);
return;
}
fd = fd_lookup (state->loc.inode, get_pid_from_req (req));
state->fd = fd;
if (!fd || S_ISDIR (state->loc.inode->st_mode)) {
/* this is the @ret of fuse_loc_fill, checked here
to permit fstat() to happen even when fuse_loc_fill fails
*/
if (ret < 0) {
gf_log ("glusterfs-fuse", GF_LOG_ERROR,
"%"PRId64": GETATTR %"PRId64" (fuse_loc_fill() failed)",
req_callid(req), (ino_t)ino);
fuse_reply_err (req, EINVAL);
free_state (state);
return;
}
gf_log ("glusterfs-fuse", GF_LOG_DEBUG,
"%"PRId64": GETATTR %"PRId64" (%s)",
req_callid (req), (int64_t)ino, state->loc.path);
FUSE_FOP (state, fuse_attr_cbk, GF_FOP_STAT,
stat, &state->loc);
} else {
gf_log ("glusterfs-fuse", GF_LOG_DEBUG,
"%"PRId64": FGETATTR %"PRId64" (%s/%p)",
req_callid (req), (int64_t)ino, state->loc.path, fd);
FUSE_FOP (state,fuse_attr_cbk, GF_FOP_FSTAT,
fstat, fd);
}
}
static int
fuse_fd_cbk (call_frame_t *frame,
void *cookie,
xlator_t *this,
int32_t op_ret,
int32_t op_errno,
fd_t *fd)
{
fuse_state_t *state;
fuse_req_t req;
fuse_private_t *priv = this->private;
state = frame->root->state;
req = state->req;
if (op_ret >= 0) {
struct fuse_file_info fi = {0, };
fi.fh = (unsigned long) fd;
fi.flags = state->flags;
if (!S_ISDIR (fd->inode->st_mode)) {
if ((fi.flags & 3) && priv->direct_io_mode)
fi.direct_io = 1;
}
gf_log ("glusterfs-fuse", GF_LOG_DEBUG,
"%"PRId64": %s() %s => %p", frame->root->unique,
gf_fop_list[frame->root->op], state->loc.path, fd);
fd_ref (fd);
if (fuse_reply_open (req, &fi) == -ENOENT) {
gf_log ("glusterfs-fuse", GF_LOG_WARNING,
"open() got EINTR");
fd_unref (fd);
goto out;
}
fd_bind (fd);
} else {
gf_log ("glusterfs-fuse", GF_LOG_ERROR,
"%"PRId64": %s() %s => -1 (%s)", frame->root->unique,
|