diff options
Diffstat (limited to 'xlators/features/quota/src/quota.c')
| -rw-r--r-- | xlators/features/quota/src/quota.c | 5806 |
1 files changed, 5024 insertions, 782 deletions
diff --git a/xlators/features/quota/src/quota.c b/xlators/features/quota/src/quota.c index f7b02af6442..18df9ae6d19 100644 --- a/xlators/features/quota/src/quota.c +++ b/xlators/features/quota/src/quota.c @@ -1,1094 +1,5336 @@ /* - Copyright (c) 2008-2009 Gluster, Inc. <http://www.gluster.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/>. + Copyright (c) 2008-2012 Red Hat, Inc. <http://www.redhat.com> + This file is part of GlusterFS. + + This file is licensed to you under your choice of the GNU Lesser + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. */ -#ifndef _CONFIG_H -#define _CONFIG_H -#include "config.h" -#endif - -#include <sys/time.h> - -#include "xlator.h" -#include "defaults.h" -#include "common-utils.h" - -struct quota_local { - struct stat stbuf; - inode_t *inode; - char *path; - fd_t *fd; - off_t offset; - int32_t count; - struct iovec *vector; - struct iobref *iobref; - loc_t loc; -}; +#include "quota.h" +#include <glusterfs/statedump.h> +#include "quota-messages.h" +#include <glusterfs/events.h> + +struct volume_options options[]; +static int32_t +__quota_init_inode_ctx(inode_t *inode, xlator_t *this, + quota_inode_ctx_t **context) +{ + int32_t ret = -1; + quota_inode_ctx_t *ctx = NULL; -struct quota_priv { - char only_first_time; /* Used to make sure a call is done only one time */ - gf_lock_t lock; /* Used while updating variables */ + if (inode == NULL) { + goto out; + } - uint64_t disk_usage_limit; /* Used for Disk usage quota */ - uint64_t current_disk_usage; /* Keep the current usage value */ + QUOTA_ALLOC_OR_GOTO(ctx, quota_inode_ctx_t, out); - uint32_t min_free_disk_limit; /* user specified limit, in %*/ - uint32_t current_free_disk; /* current free disk space available, in % */ - uint32_t refresh_interval; /* interval in seconds */ - uint32_t min_disk_last_updated_time; /* used for interval calculation */ -}; + LOCK_INIT(&ctx->lock); + + if (context != NULL) { + *context = ctx; + } + + INIT_LIST_HEAD(&ctx->parents); + + ret = __inode_ctx_put(inode, this, (uint64_t)(long)ctx); + if (ret) { + gf_msg(this->name, GF_LOG_WARNING, 0, Q_MSG_INODE_CTX_SET_FAILED, + "cannot set quota context " + "in inode (gfid:%s)", + uuid_utoa(inode->gfid)); + GF_FREE(ctx); + } +out: + return ret; +} + +static int32_t +quota_inode_ctx_get(inode_t *inode, xlator_t *this, quota_inode_ctx_t **ctx, + char create_if_absent) +{ + int32_t ret = 0; + uint64_t ctx_int; + LOCK(&inode->lock); + { + ret = __inode_ctx_get(inode, this, &ctx_int); + + if ((ret == 0) && (ctx != NULL)) { + *ctx = (quota_inode_ctx_t *)(unsigned long)ctx_int; + } else if (create_if_absent) { + ret = __quota_init_inode_ctx(inode, this, ctx); + } + } + UNLOCK(&inode->lock); + + return ret; +} int -quota_statvfs_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, struct statvfs *stbuf) +quota_loc_fill(loc_t *loc, inode_t *inode, inode_t *parent, char *path) { - struct quota_priv *priv = this->private; - - if (op_ret >= 0) { - priv->current_free_disk = - (stbuf->f_bavail * 100) / stbuf->f_blocks; - } + int ret = -1; + + if (!loc || (inode == NULL)) + return ret; + + if (inode) { + loc->inode = inode_ref(inode); + gf_uuid_copy(loc->gfid, inode->gfid); + } + + if (parent) { + loc->parent = inode_ref(parent); + } + + if (path != NULL) { + loc->path = gf_strdup(path); + + loc->name = strrchr(loc->path, '/'); + if (loc->name) { + loc->name++; + } + } + + ret = 0; - STACK_DESTROY (frame->root); - return 0; + return ret; } +int +quota_inode_loc_fill(inode_t *inode, loc_t *loc) +{ + char *resolvedpath = NULL; + inode_t *parent = NULL; + int ret = -1; + xlator_t *this = NULL; + + if ((!inode) || (!loc)) { + return ret; + } + + this = THIS; + + if ((inode) && __is_root_gfid(inode->gfid)) { + loc->parent = NULL; + goto ignore_parent; + } + + parent = inode_parent(inode, 0, NULL); + if (!parent) { + gf_msg_debug(this->name, 0, + "cannot find parent for " + "inode (gfid:%s)", + uuid_utoa(inode->gfid)); + } + +ignore_parent: + ret = inode_path(inode, NULL, &resolvedpath); + if (ret < 0) { + gf_msg_debug(this->name, 0, + "cannot construct path for " + "inode (gfid:%s)", + uuid_utoa(inode->gfid)); + } + + ret = quota_loc_fill(loc, inode, parent, resolvedpath); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "cannot fill loc"); + goto err; + } + +err: + if (parent) { + inode_unref(parent); + } + + GF_FREE(resolvedpath); + + return ret; +} -static void -build_root_loc (xlator_t *this, loc_t *loc) +int32_t +quota_local_cleanup(quota_local_t *local) { - memset (loc, 0, sizeof (*loc)); - loc->path = "/"; + if (local == NULL) { + goto out; + } + + loc_wipe(&local->loc); + loc_wipe(&local->newloc); + loc_wipe(&local->oldloc); + loc_wipe(&local->validate_loc); + + inode_unref(local->inode); + + if (local->xdata) + dict_unref(local->xdata); + + if (local->validate_xdata) + dict_unref(local->validate_xdata); + + if (local->stub) + call_stub_destroy(local->stub); + + LOCK_DESTROY(&local->lock); + + mem_put(local); +out: + return 0; +} + +static quota_local_t * +quota_local_new() +{ + quota_local_t *local = NULL; + local = mem_get0(THIS->local_pool); + if (local == NULL) + goto out; + + LOCK_INIT(&local->lock); + local->space_available = -1; + +out: + return local; } +quota_dentry_t * +__quota_dentry_new(quota_inode_ctx_t *ctx, char *name, uuid_t par) +{ + quota_dentry_t *dentry = NULL; + GF_UNUSED int32_t ret = 0; + + QUOTA_ALLOC_OR_GOTO(dentry, quota_dentry_t, err); + + INIT_LIST_HEAD(&dentry->next); + + dentry->name = gf_strdup(name); + if (dentry->name == NULL) { + GF_FREE(dentry); + dentry = NULL; + goto err; + } + + gf_uuid_copy(dentry->par, par); + + if (ctx != NULL) + list_add_tail(&dentry->next, &ctx->parents); + +err: + return dentry; +} void -gf_quota_usage_subtract (xlator_t *this, size_t size) +__quota_dentry_free(quota_dentry_t *dentry) { - struct quota_priv *priv = NULL; + if (dentry == NULL) { + goto out; + } - priv = this->private; + list_del_init(&dentry->next); - LOCK (&priv->lock); - { - if (priv->current_disk_usage < size) - priv->current_disk_usage = 0; - else - priv->current_disk_usage -= size; - } - UNLOCK (&priv->lock); + GF_FREE(dentry->name); + GF_FREE(dentry); +out: + return; } +void +__quota_dentry_del(quota_inode_ctx_t *ctx, const char *name, uuid_t par) +{ + quota_dentry_t *dentry = NULL; + quota_dentry_t *tmp = NULL; + + list_for_each_entry_safe(dentry, tmp, &ctx->parents, next) + { + if ((strcmp(dentry->name, name) == 0) && + (gf_uuid_compare(dentry->par, par) == 0)) { + __quota_dentry_free(dentry); + break; + } + } +} void -gf_quota_usage_add (xlator_t *this, size_t size) +quota_dentry_del(quota_inode_ctx_t *ctx, const char *name, uuid_t par) +{ + LOCK(&ctx->lock); + { + __quota_dentry_del(ctx, name, par); + } + UNLOCK(&ctx->lock); +} + +static inode_t * +__quota_inode_parent(inode_t *inode, uuid_t pargfid, const char *name) { - struct quota_priv *priv = this->private; + inode_t *parent = NULL; - LOCK (&priv->lock); - { - priv->current_disk_usage += size; - } - UNLOCK (&priv->lock); + parent = inode_parent(inode, pargfid, name); + inode_unref(inode); + return parent; } +static inode_t * +quota_inode_parent(inode_t *inode, uuid_t pargfid, const char *name) +{ + inode_t *parent = NULL; + + parent = __quota_inode_parent(inode, pargfid, name); + if (!parent) + gf_msg_callingfn(THIS->name, GF_LOG_ERROR, 0, Q_MSG_PARENT_NULL, + "Failed to find " + "ancestor for inode (%s)", + uuid_utoa(inode->gfid)); -void -gf_quota_update_current_free_disk (xlator_t *this) + return parent; +} + +int32_t +quota_inode_depth(inode_t *inode) { - call_frame_t *frame = NULL; - call_pool_t *pool = NULL; - loc_t loc; + int depth = 0; + inode_t *cur_inode = NULL; - pool = this->ctx->pool; - frame = create_frame (this, pool); - - build_root_loc (this, &loc); + cur_inode = inode_ref(inode); + while (cur_inode && !__is_root_gfid(cur_inode->gfid)) { + depth++; + cur_inode = quota_inode_parent(cur_inode, 0, NULL); + if (!cur_inode) + depth = -1; + } - STACK_WIND (frame, quota_statvfs_cbk, - this->children->xlator, - this->children->xlator->fops->statfs, &loc); + if (cur_inode) + inode_unref(cur_inode); - return ; + return depth; } - -int -gf_quota_check_free_disk (xlator_t *this) +int32_t +quota_find_common_ancestor(inode_t *inode1, inode_t *inode2, + uuid_t *common_ancestor) { - struct quota_priv * priv = NULL; - struct timeval tv = {0, 0}; + int32_t depth1 = 0; + int32_t depth2 = 0; + int32_t ret = -1; + inode_t *cur_inode1 = NULL; + inode_t *cur_inode2 = NULL; + + depth1 = quota_inode_depth(inode1); + if (depth1 < 0) + goto out; + + depth2 = quota_inode_depth(inode2); + if (depth2 < 0) + goto out; + + cur_inode1 = inode_ref(inode1); + cur_inode2 = inode_ref(inode2); + + while (cur_inode1 && depth1 > depth2) { + cur_inode1 = quota_inode_parent(cur_inode1, 0, NULL); + depth1--; + } + + while (cur_inode2 && depth2 > depth1) { + cur_inode2 = quota_inode_parent(cur_inode2, 0, NULL); + depth2--; + } + + while (depth1 && cur_inode1 && cur_inode2 && cur_inode1 != cur_inode2) { + cur_inode1 = quota_inode_parent(cur_inode1, 0, NULL); + cur_inode2 = quota_inode_parent(cur_inode2, 0, NULL); + depth1--; + } + + if (cur_inode1 && cur_inode2) { + gf_uuid_copy(*common_ancestor, cur_inode1->gfid); + ret = 0; + } +out: + if (cur_inode1) + inode_unref(cur_inode1); + + if (cur_inode2) + inode_unref(cur_inode2); + + return ret; +} - priv = this->private; - if (priv->min_free_disk_limit) { - gettimeofday (&tv, NULL); - if (tv.tv_sec > (priv->refresh_interval + - priv->min_disk_last_updated_time)) { - priv->min_disk_last_updated_time = tv.tv_sec; - gf_quota_update_current_free_disk (this); - } - if (priv->current_free_disk <= priv->min_free_disk_limit) - return -1; - } +void +check_ancestory_continue(struct list_head *parents, inode_t *inode, + int32_t op_ret, int32_t op_errno, void *data) +{ + call_frame_t *frame = NULL; + quota_local_t *local = NULL; + uint32_t link_count = 0; + + frame = data; + local = frame->local; + + if (parents && list_empty(parents)) { + gf_msg(THIS->name, GF_LOG_WARNING, EIO, Q_MSG_ANCESTRY_BUILD_FAILED, + "Couldn't build ancestry for inode (gfid:%s). " + "Without knowing ancestors till root, quota " + "cannot be enforced. " + "Hence, failing fop with EIO", + uuid_utoa(inode->gfid)); + op_errno = EIO; + op_ret = -1; + } + + LOCK(&local->lock); + { + link_count = --local->link_count; + if (op_ret < 0) { + local->op_ret = op_ret; + local->op_errno = op_errno; + } + } + UNLOCK(&local->lock); - return 0; + if (link_count == 0) + local->fop_continue_cbk(frame); } +void +check_ancestory(call_frame_t *frame, inode_t *inode) +{ + inode_t *cur_inode = NULL; + inode_t *parent = NULL; + + cur_inode = inode_ref(inode); + while (cur_inode && !__is_root_gfid(cur_inode->gfid)) { + parent = inode_parent(cur_inode, 0, NULL); + if (!parent) { + quota_build_ancestry(cur_inode, check_ancestory_continue, frame); + inode_unref(cur_inode); + return; + } + inode_unref(cur_inode); + cur_inode = parent; + } + + if (cur_inode) { + inode_unref(cur_inode); + check_ancestory_continue(NULL, NULL, 0, 0, frame); + } else { + check_ancestory_continue(NULL, NULL, -1, ESTALE, frame); + } +} -int -quota_truncate_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, struct stat *prebuf, - struct stat *postbuf) +void +check_ancestory_2_cbk(struct list_head *parents, inode_t *inode, int32_t op_ret, + int32_t op_errno, void *data) { - struct quota_priv *priv = this->private; - struct quota_local *local = NULL; + inode_t *this_inode = NULL; + quota_inode_ctx_t *ctx = NULL; + + this_inode = data; - local = frame->local; + if (op_ret < 0) + goto out; - if ((op_ret >= 0) && priv->disk_usage_limit) { - gf_quota_usage_subtract (this, (local->stbuf.st_blocks - - postbuf->st_blocks) * 512); - loc_wipe (&local->loc); - } + if (parents == NULL || list_empty(parents)) { + gf_msg(THIS->name, GF_LOG_WARNING, 0, Q_MSG_ENFORCEMENT_FAILED, + "Couldn't build ancestry for inode (gfid:%s). " + "Without knowing ancestors till root, quota " + "cannot be enforced.", + uuid_utoa(this_inode->gfid)); + goto out; + } - STACK_UNWIND_STRICT (truncate, frame, op_ret, op_errno, - prebuf, postbuf); - return 0; + quota_inode_ctx_get(this_inode, THIS, &ctx, 0); + if (ctx) + ctx->ancestry_built = _gf_true; + +out: + inode_unref(this_inode); } +void +check_ancestory_2(xlator_t *this, quota_local_t *local, inode_t *inode) +{ + inode_t *cur_inode = NULL; + inode_t *parent = NULL; + quota_inode_ctx_t *ctx = NULL; + char *name = NULL; + uuid_t pgfid = {0}; + + name = (char *)local->loc.name; + if (local->loc.parent) { + gf_uuid_copy(pgfid, local->loc.parent->gfid); + } + + cur_inode = inode_ref(inode); + while (cur_inode && !__is_root_gfid(cur_inode->gfid)) { + quota_inode_ctx_get(cur_inode, this, &ctx, 0); + /* build ancestry is required only on the first lookup, + * so stop crawling when the inode_ctx is set for an inode + */ + if (ctx && ctx->ancestry_built) + goto setctx; + + parent = inode_parent(cur_inode, pgfid, name); + if (!parent) { + quota_build_ancestry(cur_inode, check_ancestory_2_cbk, + inode_ref(inode)); + goto out; + } -int -quota_truncate_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, struct stat *buf) + if (name != NULL) { + name = NULL; + gf_uuid_clear(pgfid); + } + + inode_unref(cur_inode); + cur_inode = parent; + } + +setctx: + if (cur_inode && cur_inode != inode) { + quota_inode_ctx_get(inode, this, &ctx, 0); + if (ctx) + ctx->ancestry_built = _gf_true; + } +out: + if (cur_inode) + inode_unref(cur_inode); +} + +static void +quota_link_count_decrement(call_frame_t *frame) { - struct quota_local *local = NULL; - struct quota_priv *priv = NULL; + call_frame_t *tmpframe = NULL; + quota_local_t *local = NULL; + call_stub_t *stub = NULL; + int link_count = -1; + + local = frame->local; + if (local && local->par_frame) { + local = local->par_frame->local; + tmpframe = frame; + } + + if (local == NULL) + goto out; + + LOCK(&local->lock); + { + link_count = --local->link_count; + if (link_count == 0) { + stub = local->stub; + local->stub = NULL; + } + } + UNLOCK(&local->lock); + + if (stub != NULL) { + call_resume(stub); + } - priv = this->private; - local = frame->local; +out: + if (tmpframe) { + local = tmpframe->local; + tmpframe->local = NULL; - if (op_ret >= 0) { - local->stbuf = *buf; - } + STACK_DESTROY(frame->root); + if (local) + quota_local_cleanup(local); + } - STACK_WIND (frame, quota_truncate_cbk, - FIRST_CHILD (this), FIRST_CHILD (this)->fops->truncate, - &local->loc, local->offset); - return 0; + return; } +static void +quota_handle_validate_error(call_frame_t *frame, int32_t op_ret, + int32_t op_errno) +{ + quota_local_t *local; -int -quota_truncate (call_frame_t *frame, xlator_t *this, - loc_t *loc, off_t offset) + local = frame->local; + if (local && local->par_frame) + local = local->par_frame->local; + + if (local == NULL) + goto out; + + if (op_ret < 0) { + LOCK(&local->lock); + { + local->op_ret = op_ret; + local->op_errno = op_errno; + } + UNLOCK(&local->lock); + } + /* we abort checking limits on this path to root */ + quota_link_count_decrement(frame); +out: + return; +} + +int32_t +quota_validate_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, dict_t *xdata, struct iatt *postparent) { - struct quota_local *local = NULL; - struct quota_priv *priv = NULL; + quota_local_t *local = NULL; + int32_t ret = 0; + quota_inode_ctx_t *ctx = NULL; + uint64_t value = 0; + quota_meta_t size = { + 0, + }; + + local = frame->local; + + if (op_ret < 0) { + goto unwind; + } + + GF_ASSERT(local); + GF_ASSERT(frame); + GF_VALIDATE_OR_GOTO_WITH_ERROR("quota", this, unwind, op_errno, EINVAL); + GF_VALIDATE_OR_GOTO_WITH_ERROR(this->name, xdata, unwind, op_errno, EINVAL); + + ret = inode_ctx_get(local->validate_loc.inode, this, &value); + + ctx = (quota_inode_ctx_t *)(unsigned long)value; + if ((ret == -1) || (ctx == NULL)) { + gf_msg(this->name, GF_LOG_WARNING, EINVAL, Q_MSG_INODE_CTX_GET_FAILED, + "quota context is" + " not present in inode (gfid:%s)", + uuid_utoa(local->validate_loc.inode->gfid)); + op_errno = EINVAL; + goto unwind; + } + + ret = quota_dict_get_meta(xdata, QUOTA_SIZE_KEY, SLEN(QUOTA_SIZE_KEY), + &size); + if (ret == -1) { + gf_msg(this->name, GF_LOG_WARNING, EINVAL, Q_MSG_SIZE_KEY_MISSING, + "quota size key not present " + "in dict"); + op_errno = EINVAL; + } + + local->just_validated = 1; /* so that we don't go into infinite + * loop of validation and checking + * limit when timeout is zero. + */ + LOCK(&ctx->lock); + { + ctx->size = size.size; + ctx->validate_time = gf_time(); + ctx->file_count = size.file_count; + ctx->dir_count = size.dir_count; + } + UNLOCK(&ctx->lock); + + quota_check_limit(frame, local->validate_loc.inode, this); + return 0; - priv = this->private; +unwind: + quota_handle_validate_error(frame, op_ret, op_errno); + return 0; +} - if (priv->disk_usage_limit) { - local = CALLOC (1, sizeof (struct quota_local)); - frame->local = local; +static inline gf_boolean_t +quota_timeout(time_t t, uint32_t timeout) +{ + return (gf_time() - t) >= timeout; +} - loc_copy (&local->loc, loc); - local->offset = offset; +/* Return: 1 if new entry added + * 0 no entry added + * -1 on errors + */ +static int32_t +quota_add_parent(struct list_head *list, char *name, uuid_t pgfid) +{ + quota_dentry_t *entry = NULL; + gf_boolean_t found = _gf_false; + int ret = 0; + + if (!list_empty(list)) { + list_for_each_entry(entry, list, next) + { + if (gf_uuid_compare(pgfid, entry->par) == 0) { + found = _gf_true; + goto out; + } + } + } + + entry = __quota_dentry_new(NULL, name, pgfid); + if (entry) + list_add_tail(&entry->next, list); + else + ret = -1; + +out: + if (found) + return 0; + else if (ret == 0) + return 1; + else + return -1; +} - STACK_WIND (frame, quota_truncate_stat_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->stat, loc); - return 0; - } +/* This function iterates the parent list in inode + * context and add unique parent to the list + * Returns number of dentry added to the list, or -1 on errors + */ +static int32_t +quota_add_parents_from_ctx(quota_inode_ctx_t *ctx, struct list_head *list) +{ + int ret = 0; + quota_dentry_t *dentry = NULL; + int32_t count = 0; + + if (ctx == NULL || list == NULL) + goto out; + + LOCK(&ctx->lock); + { + list_for_each_entry(dentry, &ctx->parents, next) + { + ret = quota_add_parent(list, dentry->name, dentry->par); + if (ret == 1) + count++; + else if (ret == -1) + break; + } + } + UNLOCK(&ctx->lock); - STACK_WIND (frame, quota_truncate_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->truncate, - loc, offset); - return 0; +out: + return (ret == -1) ? -1 : count; } +int32_t +quota_build_ancestry_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, gf_dirent_t *entries, + dict_t *xdata) +{ + inode_t *parent = NULL; + inode_t *tmp_parent = NULL; + inode_t *linked_inode = NULL; + inode_t *tmp_inode = NULL; + gf_dirent_t *entry = NULL; + loc_t loc = { + 0, + }; + quota_dentry_t *dentry = NULL; + quota_dentry_t *tmp = NULL; + quota_inode_ctx_t *ctx = NULL; + struct list_head parents; + quota_local_t *local = NULL; + int ret; + + INIT_LIST_HEAD(&parents); + + local = frame->local; + frame->local = NULL; + + if (op_ret < 0) + goto err; + + if ((op_ret > 0) && (entries != NULL)) { + list_for_each_entry(entry, &entries->list, list) + { + if (__is_root_gfid(entry->inode->gfid)) { + /* The list contains a sub-list for each + * possible path to the target inode. Each + * sub-list starts with the root entry of the + * tree and is followed by the child entries + * for a particular path to the target entry. + * The root entry is an implied sub-list + * delimiter, as it denotes we have started + * processing a new path. Reset the parent + * pointer and continue + */ + + tmp_parent = NULL; + } else { + /* For a non-root entry, link this inode */ + linked_inode = inode_link(entry->inode, tmp_parent, + entry->d_name, &entry->d_stat); + if (linked_inode) { + tmp_inode = entry->inode; + entry->inode = linked_inode; + inode_unref(tmp_inode); + } else { + gf_msg(this->name, GF_LOG_WARNING, EINVAL, + Q_MSG_PARENT_NULL, "inode link failed"); + op_errno = EINVAL; + goto err; + } + } + + gf_uuid_copy(loc.gfid, entry->d_stat.ia_gfid); + + loc.inode = inode_ref(entry->inode); + loc.parent = inode_ref(tmp_parent); + loc.name = entry->d_name; + + quota_fill_inodectx(this, entry->inode, entry->dict, &loc, + &entry->d_stat, &op_errno); + + /* For non-directory, posix_get_ancestry_non_directory + * returns all hard-links that are represented by nodes + * adjacent to each other in the dentry-list. + * (Unlike the directory case where adjacent nodes + * either have a parent/child relationship or belong to + * different paths). + */ + if (entry->inode->ia_type == IA_IFDIR) + tmp_parent = entry->inode; + + loc_wipe(&loc); + } + } + + parent = inode_parent(local->loc.inode, 0, NULL); + if (parent == NULL) { + gf_msg(this->name, GF_LOG_WARNING, EINVAL, Q_MSG_PARENT_NULL, + "parent is NULL"); + op_errno = EINVAL; + goto err; + } + + quota_inode_ctx_get(local->loc.inode, this, &ctx, 0); + + ret = quota_add_parents_from_ctx(ctx, &parents); + if (ret == -1) { + op_errno = errno; + goto err; + } + + if (list_empty(&parents)) { + /* we built ancestry for a directory */ + list_for_each_entry(entry, &entries->list, list) + { + if (entry->inode == local->loc.inode) + break; + } + + /* Getting assertion here, need to investigate + comment for now + GF_ASSERT (&entry->list != &entries->list); + */ + + ret = quota_add_parent(&parents, entry->d_name, parent->gfid); + if (ret == -1) { + op_errno = errno; + goto err; + } + } + + local->ancestry_cbk(&parents, local->loc.inode, 0, 0, local->ancestry_data); + goto cleanup; + +err: + local->ancestry_cbk(NULL, NULL, -1, op_errno, local->ancestry_data); + +cleanup: + STACK_DESTROY(frame->root); + quota_local_cleanup(local); + + if (parent != NULL) { + inode_unref(parent); + parent = NULL; + } + + if (!list_empty(&parents)) { + list_for_each_entry_safe(dentry, tmp, &parents, next) + { + __quota_dentry_free(dentry); + } + } + + return 0; +} int -quota_ftruncate_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, struct stat *prebuf, - struct stat *postbuf) +quota_build_ancestry(inode_t *inode, quota_ancestry_built_t ancestry_cbk, + void *data) { - struct quota_priv *priv = NULL; - struct quota_local *local = NULL; + fd_t *fd = NULL; + quota_local_t *local = NULL; + call_frame_t *new_frame = NULL; + int op_errno = ENOMEM; + int op_ret = -1; + xlator_t *this = NULL; + dict_t *xdata_req = NULL; + + this = THIS; + + xdata_req = dict_new(); + if (xdata_req == NULL) + goto err; + + fd = fd_anonymous(inode); + if (fd == NULL) + goto err; + + new_frame = create_frame(this, this->ctx->pool); + if (new_frame == NULL) + goto err; + + local = quota_local_new(); + if (local == NULL) + goto err; + + new_frame->root->uid = new_frame->root->gid = 0; + new_frame->local = local; + local->ancestry_cbk = ancestry_cbk; + local->ancestry_data = data; + local->loc.inode = inode_ref(inode); + + op_ret = dict_set_int8(xdata_req, QUOTA_LIMIT_KEY, 1); + if (op_ret < 0) { + op_errno = -op_ret; + goto err; + } + + op_ret = dict_set_int8(xdata_req, QUOTA_LIMIT_OBJECTS_KEY, 1); + if (op_ret < 0) { + op_errno = -op_ret; + goto err; + } + + op_ret = dict_set_int8(xdata_req, GET_ANCESTRY_DENTRY_KEY, 1); + if (op_ret < 0) { + op_errno = -op_ret; + goto err; + } + + /* This would ask posix layer to construct dentry chain till root + * We don't need to do a opendir, we can use the anonymous fd + * here for the readidrp. + * avoiding opendir also reduces the window size where another FOP + * can be executed before completion of build ancestry + */ + STACK_WIND(new_frame, quota_build_ancestry_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->readdirp, fd, 0, 0, xdata_req); + + op_ret = 0; + +err: + if (fd) + fd_unref(fd); + + if (xdata_req) + dict_unref(xdata_req); + + if (op_ret < 0) { + ancestry_cbk(NULL, NULL, -1, op_errno, data); + + if (new_frame) { + local = new_frame->local; + new_frame->local = NULL; + STACK_DESTROY(new_frame->root); + } - local = frame->local; - priv = this->private; + if (local) + quota_local_cleanup(local); + } - if ((op_ret >= 0) && priv->disk_usage_limit) { - gf_quota_usage_subtract (this, (local->stbuf.st_blocks - - postbuf->st_blocks) * 512); - fd_unref (local->fd); - } + return 0; +} - STACK_UNWIND_STRICT (ftruncate, frame, op_ret, op_errno, - prebuf, postbuf); - return 0; +int +quota_validate(call_frame_t *frame, inode_t *inode, xlator_t *this, + fop_lookup_cbk_t cbk_fn) +{ + quota_local_t *local = NULL; + int ret = 0; + dict_t *xdata = NULL; + quota_priv_t *priv = NULL; + + local = frame->local; + priv = this->private; + + LOCK(&local->lock); + { + loc_wipe(&local->validate_loc); + + ret = quota_inode_loc_fill(inode, &local->validate_loc); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENFORCEMENT_FAILED, + "cannot fill loc for inode (gfid:%s), hence " + "aborting quota-checks and continuing with fop", + uuid_utoa(inode->gfid)); + } + } + UNLOCK(&local->lock); + + if (ret < 0) { + ret = -ENOMEM; + goto err; + } + + xdata = dict_new(); + if (xdata == NULL) { + ret = -ENOMEM; + goto err; + } + + ret = dict_set_int8(xdata, QUOTA_SIZE_KEY, 1); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "dict set failed"); + ret = -ENOMEM; + goto err; + } + + ret = dict_set_str(xdata, "volume-uuid", priv->volume_uuid); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "dict set failed"); + ret = -ENOMEM; + goto err; + } + + ret = quota_enforcer_lookup(frame, this, xdata, cbk_fn); + if (ret < 0) { + ret = -ENOTCONN; + goto err; + } + + ret = 0; +err: + if (xdata) + dict_unref(xdata); + + return ret; } +void +quota_check_limit_continuation(struct list_head *parents, inode_t *inode, + int32_t op_ret, int32_t op_errno, void *data) +{ + call_frame_t *frame = NULL; + xlator_t *this = NULL; + quota_local_t *local = NULL; + quota_local_t *par_local = NULL; + quota_dentry_t *entry = NULL; + inode_t *parent = NULL; + int parent_count = 0; + + frame = data; + local = frame->local; + this = THIS; + + if (local->par_frame) + par_local = local->par_frame->local; + else + par_local = local; + + if ((op_ret < 0) || list_empty(parents)) { + if (op_ret >= 0) { + gf_msg(this->name, GF_LOG_WARNING, EIO, Q_MSG_ANCESTRY_BUILD_FAILED, + "Couldn't build ancestry for inode (gfid:%s). " + "Without knowing ancestors till root, quota" + "cannot be enforced. " + "Hence, failing fop with EIO", + uuid_utoa(inode->gfid)); + op_errno = EIO; + } + + quota_handle_validate_error(frame, -1, op_errno); + goto out; + } -int -quota_ftruncate_fstat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, struct stat *buf) + list_for_each_entry(entry, parents, next) { parent_count++; } + + LOCK(&par_local->lock); + { + par_local->link_count += (parent_count - 1); + } + UNLOCK(&par_local->lock); + + if (local->par_frame) { + list_for_each_entry(entry, parents, next) + { + parent = inode_find(inode->table, entry->par); + quota_check_limit(frame, parent, this); + inode_unref(parent); + } + } else { + list_for_each_entry(entry, parents, next) + { + parent = do_quota_check_limit(frame, inode, this, entry, _gf_true); + if (parent) + inode_unref(parent); + else + quota_link_count_decrement(frame); + } + } + +out: + return; +} + +int32_t +quota_check_object_limit(call_frame_t *frame, quota_inode_ctx_t *ctx, + quota_priv_t *priv, inode_t *_inode, xlator_t *this, + int32_t *op_errno, int just_validated, + quota_local_t *local, gf_boolean_t *skip_check) { - struct quota_local *local = NULL; - struct quota_priv *priv = NULL; + int32_t ret = -1; + uint32_t timeout = 0; + char need_validate = 0; + gf_boolean_t hard_limit_exceeded = 0; + int64_t object_aggr_count = 0; + + GF_ASSERT(frame); + GF_ASSERT(priv); + GF_ASSERT(_inode); + GF_ASSERT(this); + GF_ASSERT(local); + + if (ctx != NULL && (ctx->object_hard_lim > 0 || ctx->object_soft_lim)) { + LOCK(&ctx->lock); + { + timeout = priv->soft_timeout; + + object_aggr_count = ctx->file_count + ctx->dir_count + 1; + if (((ctx->object_soft_lim >= 0) && + (object_aggr_count) > ctx->object_soft_lim)) { + timeout = priv->hard_timeout; + } + + if (!just_validated && quota_timeout(ctx->validate_time, timeout)) { + need_validate = 1; + } else if ((object_aggr_count) > ctx->object_hard_lim) { + hard_limit_exceeded = 1; + } + } + UNLOCK(&ctx->lock); + + if (need_validate && *skip_check != _gf_true) { + *skip_check = _gf_true; + ret = quota_validate(frame, _inode, this, quota_validate_cbk); + if (ret < 0) { + *op_errno = -ret; + *skip_check = _gf_false; + } + goto out; + } - priv = this->private; - local = frame->local; + if (hard_limit_exceeded) { + local->op_ret = -1; + local->op_errno = EDQUOT; + *op_errno = EDQUOT; + goto out; + } + + /*We log usage only if quota limit is configured on + that inode + */ + quota_log_usage(this, ctx, _inode, 0); + } - if (op_ret >= 0) { - local->stbuf = *buf; - } + ret = 0; - STACK_WIND (frame, quota_ftruncate_cbk, - FIRST_CHILD (this), FIRST_CHILD (this)->fops->ftruncate, - local->fd, local->offset); - return 0; +out: + return ret; } +int32_t +quota_check_size_limit(call_frame_t *frame, quota_inode_ctx_t *ctx, + quota_priv_t *priv, inode_t *_inode, xlator_t *this, + int32_t *op_errno, int just_validated, int64_t delta, + quota_local_t *local, gf_boolean_t *skip_check) +{ + int32_t ret = -1; + uint32_t timeout = 0; + char need_validate = 0; + gf_boolean_t hard_limit_exceeded = 0; + int64_t space_available = 0; + int64_t wouldbe_size = 0; + + GF_ASSERT(frame); + GF_ASSERT(priv); + GF_ASSERT(_inode); + GF_ASSERT(this); + GF_ASSERT(local); + + if (ctx != NULL && (ctx->hard_lim > 0 || ctx->soft_lim > 0)) { + wouldbe_size = ctx->size + delta; + + LOCK(&ctx->lock); + { + timeout = priv->soft_timeout; + + if ((ctx->soft_lim >= 0) && (wouldbe_size > ctx->soft_lim)) { + timeout = priv->hard_timeout; + } + + if (!just_validated && quota_timeout(ctx->validate_time, timeout)) { + need_validate = 1; + } else if (wouldbe_size >= ctx->hard_lim) { + hard_limit_exceeded = 1; + } + } + UNLOCK(&ctx->lock); + + if (need_validate && *skip_check != _gf_true) { + *skip_check = _gf_true; + ret = quota_validate(frame, _inode, this, quota_validate_cbk); + if (ret < 0) { + *op_errno = -ret; + *skip_check = _gf_false; + } + goto out; + } + + if (hard_limit_exceeded) { + local->op_ret = -1; + local->op_errno = EDQUOT; -int -quota_ftruncate (call_frame_t *frame, xlator_t *this, - fd_t *fd, off_t offset) + space_available = ctx->hard_lim - ctx->size; + + if (space_available < 0) + space_available = 0; + + if ((local->space_available < 0) || + (local->space_available > space_available)) { + local->space_available = space_available; + } + + if (space_available == 0) { + *op_errno = EDQUOT; + goto out; + } + } + + /* We log usage only if quota limit is configured on + that inode. */ + quota_log_usage(this, ctx, _inode, delta); + } + + ret = 0; +out: + return ret; +} + +int32_t +quota_check_limit(call_frame_t *frame, inode_t *inode, xlator_t *this) { - struct quota_local *local = NULL; - struct quota_priv *priv = NULL; + int32_t ret = -1, op_errno = EINVAL; + inode_t *_inode = NULL, *parent = NULL; + quota_inode_ctx_t *ctx = NULL; + quota_priv_t *priv = NULL; + quota_local_t *local = NULL; + quota_local_t *par_local = NULL; + char just_validated = 0; + int64_t delta = 0; + int8_t object_delta = 0; + uint64_t value = 0; + gf_boolean_t skip_check = _gf_false; + + GF_VALIDATE_OR_GOTO("quota", this, err); + GF_VALIDATE_OR_GOTO(this->name, frame, err); + GF_VALIDATE_OR_GOTO(this->name, inode, err); + + local = frame->local; + GF_VALIDATE_OR_GOTO(this->name, local, err); + + if (local->par_frame) { + par_local = local->par_frame->local; + GF_VALIDATE_OR_GOTO(this->name, par_local, err); + } else { + par_local = local; + } + + delta = par_local->delta; + object_delta = par_local->object_delta; + + GF_VALIDATE_OR_GOTO(this->name, par_local->stub, err); + /* Allow all the trusted clients + * Don't block the gluster internal processes like rebalance, gsyncd, + * self heal etc from the disk quotas. + * + * Method: Allow all the clients with PID negative. This is by the + * assumption that any kernel assigned pid doesn't have the negative + * number. + */ + if (0 > frame->root->pid) { + ret = 0; + quota_link_count_decrement(frame); + goto done; + } + + priv = this->private; + + inode_ctx_get(inode, this, &value); + ctx = (quota_inode_ctx_t *)(unsigned long)value; + + _inode = inode_ref(inode); + + LOCK(&local->lock); + { + just_validated = local->just_validated; + local->just_validated = 0; + } + UNLOCK(&local->lock); + + do { + /* In a rename operation, enforce should be stopped at common + ancestor */ + if (!gf_uuid_is_null(par_local->common_ancestor) && + !gf_uuid_compare(_inode->gfid, par_local->common_ancestor)) { + quota_link_count_decrement(frame); + break; + } + + if (object_delta <= 0) + goto skip_check_object_limit; + + ret = quota_check_object_limit(frame, ctx, priv, _inode, this, + &op_errno, just_validated, par_local, + &skip_check); + if (skip_check == _gf_true) + goto done; + + if (ret) { + if (op_errno != EDQUOT) + gf_msg(this->name, GF_LOG_ERROR, 0, Q_MSG_ENFORCEMENT_FAILED, + "Failed to " + "check quota object limit"); + goto err; + } + + skip_check_object_limit: + ret = quota_check_size_limit(frame, ctx, priv, _inode, this, &op_errno, + just_validated, delta, par_local, + &skip_check); + if (skip_check == _gf_true) + goto done; + + if (ret) { + if (op_errno != EDQUOT) + gf_msg(this->name, GF_LOG_ERROR, 0, Q_MSG_ENFORCEMENT_FAILED, + "Failed to " + "check quota size limit"); + goto err; + } + + if (__is_root_gfid(_inode->gfid)) { + quota_link_count_decrement(frame); + break; + } + parent = inode_parent(_inode, 0, NULL); + if (parent == NULL) { + ret = quota_build_ancestry(_inode, quota_check_limit_continuation, + frame); + if (ret < 0) { + op_errno = -ret; + goto err; + } + + break; + } - priv = this->private; + inode_unref(_inode); + _inode = parent; + just_validated = 0; - if (priv->disk_usage_limit) { - local = CALLOC (1, sizeof (struct quota_local)); - frame->local = local; + value = 0; + inode_ctx_get(_inode, this, &value); + ctx = (quota_inode_ctx_t *)(unsigned long)value; + } while (1); - local->fd = fd_ref (fd); - local->offset = offset; +done: + if (_inode != NULL) { + inode_unref(_inode); + _inode = NULL; + } + return 0; - STACK_WIND (frame, quota_ftruncate_fstat_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->fstat, fd); - return 0; - } +err: + quota_handle_validate_error(frame, -1, op_errno); - STACK_WIND (frame, quota_ftruncate_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->ftruncate, - fd, offset); - return 0; + inode_unref(_inode); + return 0; } +inode_t * +do_quota_check_limit(call_frame_t *frame, inode_t *inode, xlator_t *this, + quota_dentry_t *dentry, gf_boolean_t force) +{ + int32_t ret = -1; + inode_t *parent = NULL; + call_frame_t *new_frame = NULL; + quota_local_t *new_local = NULL; + + parent = inode_parent(inode, dentry->par, dentry->name); + if (parent == NULL) { + if (force) + parent = inode_find(inode->table, dentry->par); + else + goto out; + } + if (parent == NULL) + goto out; + + new_frame = copy_frame(frame); + if (new_frame == NULL) + goto out; + + new_local = quota_local_new(); + if (new_local == NULL) + goto out; + + new_frame->local = new_local; + new_local->par_frame = frame; + + quota_check_limit(new_frame, parent, this); + + ret = 0; +out: + if (ret < 0) { + if (parent) { + /* Caller should decrement link_count, in case parent is + * NULL + */ + quota_handle_validate_error(frame, -1, ENOMEM); + } + + if (new_frame) { + new_frame->local = NULL; + STACK_DESTROY(new_frame->root); + } + } + + return parent; +} + +static int +quota_get_limits(xlator_t *this, dict_t *dict, int64_t *hard_lim, + int64_t *soft_lim, int64_t *object_hard_limit, + int64_t *object_soft_limit) +{ + quota_limits_t *limit = NULL; + quota_limits_t *object_limit = NULL; + quota_priv_t *priv = NULL; + int64_t soft_lim_percent = 0; + int64_t *ptr = NULL; + int ret = 0; + + if ((this == NULL) || (dict == NULL) || (hard_lim == NULL) || + (soft_lim == NULL)) + goto out; + + priv = this->private; + + ret = dict_get_bin(dict, QUOTA_LIMIT_KEY, (void **)&ptr); + limit = (quota_limits_t *)ptr; + + if (limit) { + *hard_lim = ntoh64(limit->hl); + soft_lim_percent = ntoh64(limit->sl); + } + + if (soft_lim_percent < 0) { + soft_lim_percent = priv->default_soft_lim; + } + + if ((*hard_lim > 0) && (soft_lim_percent > 0)) { + *soft_lim = (soft_lim_percent * (*hard_lim)) / 100; + } + + ret = dict_get_bin(dict, QUOTA_LIMIT_OBJECTS_KEY, (void **)&ptr); + if (ret) + return 0; + object_limit = (quota_limits_t *)ptr; + + if (object_limit) { + *object_hard_limit = ntoh64(object_limit->hl); + soft_lim_percent = ntoh64(object_limit->sl); + } + + if (soft_lim_percent < 0) { + soft_lim_percent = priv->default_soft_lim; + } + + if ((*object_hard_limit > 0) && (soft_lim_percent > 0)) { + *object_soft_limit = (soft_lim_percent * (*object_hard_limit)) / 100; + } + +out: + return 0; +} int -quota_mknod_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, - inode_t *inode, struct stat *buf, struct stat *preparent, - struct stat *postparent) +quota_fill_inodectx(xlator_t *this, inode_t *inode, dict_t *dict, loc_t *loc, + struct iatt *buf, int32_t *op_errno) { - struct quota_priv *priv = NULL; + int32_t ret = -1; + char found = 0; + quota_inode_ctx_t *ctx = NULL; + quota_dentry_t *dentry = NULL; + uint64_t value = 0; + int64_t hard_lim = 0; + int64_t soft_lim = 0; + int64_t object_hard_limit = 0; + int64_t object_soft_limit = 0; + + quota_get_limits(this, dict, &hard_lim, &soft_lim, &object_hard_limit, + &object_soft_limit); + + inode_ctx_get(inode, this, &value); + ctx = (quota_inode_ctx_t *)(unsigned long)value; + + if ((((ctx == NULL) || (ctx->hard_lim == hard_lim)) && (hard_lim < 0) && + !QUOTA_REG_OR_LNK_FILE(buf->ia_type))) { + ret = 0; + goto out; + } + + ret = quota_inode_ctx_get(inode, this, &ctx, 1); + if ((ret == -1) || (ctx == NULL)) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_INODE_CTX_GET_FAILED, + "cannot create quota " + "context in inode(gfid:%s)", + uuid_utoa(inode->gfid)); + ret = -1; + *op_errno = ENOMEM; + goto out; + } + + LOCK(&ctx->lock); + { + ctx->hard_lim = hard_lim; + ctx->soft_lim = soft_lim; + ctx->object_hard_lim = object_hard_limit; + ctx->object_soft_lim = object_soft_limit; + + ctx->buf = *buf; + + if (!QUOTA_REG_OR_LNK_FILE(buf->ia_type)) { + goto unlock; + } - priv = this->private; + /* do nothing if it is a nameless lookup */ + if (loc->name == NULL || !loc->parent) + goto unlock; + + list_for_each_entry(dentry, &ctx->parents, next) + { + if ((strcmp(dentry->name, loc->name) == 0) && + (gf_uuid_compare(loc->parent->gfid, dentry->par) == 0)) { + found = 1; + break; + } + } - if ((op_ret >= 0) && priv->disk_usage_limit) { - gf_quota_usage_add (this, buf->st_blocks * 512); - } + if (!found) { + dentry = __quota_dentry_new(ctx, (char *)loc->name, + loc->parent->gfid); + if (dentry == NULL) { + /* + gf_msg (this->name, GF_LOG_WARNING, ENOMEM, + Q_MSG_ENOMEM, + "cannot create a new dentry (par:%" +- PRId64", name:%s) for inode(ino:%" +- PRId64", gfid:%s)", +- uuid_utoa (local->loc.inode->gfid)); + */ + ret = -1; + *op_errno = ENOMEM; + goto unlock; + } + } + } +unlock: + UNLOCK(&ctx->lock); - STACK_UNWIND_STRICT (mknod, frame, op_ret, op_errno, inode, buf, - preparent, postparent); - return 0; +out: + return ret; } +/* + * return _gf_true if enforcement is needed and _gf_false otherwise + */ +gf_boolean_t +should_quota_enforce(xlator_t *this, dict_t *dict, glusterfs_fop_t fop) +{ + int ret = 0; + + ret = dict_check_flag(dict, GF_INTERNAL_CTX_KEY, GF_DHT_HEAL_DIR); + + if (fop == GF_FOP_MKDIR && ret == DICT_FLAG_SET) { + return _gf_false; + } else if (ret == -ENOENT) { + gf_msg(this->name, GF_LOG_DEBUG, EINVAL, Q_MSG_INTERNAL_FOP_KEY_MISSING, + "No internal fop context present"); + goto out; + } +out: + return _gf_true; +} -int -quota_mknod (call_frame_t *frame, xlator_t *this, - loc_t *loc, mode_t mode, dev_t rdev) +int32_t +quota_lookup_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, dict_t *dict, struct iatt *postparent) { - struct quota_priv *priv = NULL; + quota_local_t *local = NULL; + inode_t *this_inode = NULL; - priv = this->private; + local = frame->local; + frame->local = NULL; - if (gf_quota_check_free_disk (this) == -1) { - gf_log (this->name, GF_LOG_ERROR, - "min-free-disk limit (%u) crossed, current available is %u", - priv->min_free_disk_limit, priv->current_free_disk); - STACK_UNWIND_STRICT (mknod, frame, -1, ENOSPC, NULL, NULL, - NULL, NULL); - return 0; - } + if (op_ret >= 0 && inode) { + this_inode = inode_ref(inode); - if (priv->current_disk_usage > priv->disk_usage_limit) { - gf_log (this->name, GF_LOG_ERROR, - "Disk usage limit (%"PRIu64") crossed, current usage is %"PRIu64"", - priv->disk_usage_limit, priv->current_disk_usage); - STACK_UNWIND_STRICT (mknod, frame, -1, ENOSPC, NULL, NULL, - NULL, NULL); - return 0; - } + op_ret = quota_fill_inodectx(this, inode, dict, &local->loc, buf, + &op_errno); + if (op_ret < 0) + op_errno = ENOMEM; + } - STACK_WIND (frame, quota_mknod_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->mknod, - loc, mode, rdev); - return 0; -} + QUOTA_STACK_UNWIND(lookup, frame, op_ret, op_errno, inode, buf, dict, + postparent); + if (op_ret < 0 || this_inode == NULL || gf_uuid_is_null(this_inode->gfid)) + goto out; -int -quota_mkdir_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, inode_t *inode, - struct stat *buf, struct stat *preparent, - struct stat *postparent) + check_ancestory_2(this, local, this_inode); + +out: + if (this_inode) + inode_unref(this_inode); + + quota_local_cleanup(local); + + return 0; +} + +int32_t +quota_lookup(call_frame_t *frame, xlator_t *this, loc_t *loc, dict_t *xattr_req) { - struct quota_priv *priv = NULL; + quota_priv_t *priv = NULL; + int32_t ret = -1; + quota_local_t *local = NULL; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + xattr_req = xattr_req ? dict_ref(xattr_req) : dict_new(); + if (!xattr_req) + goto err; + + local = quota_local_new(); + if (local == NULL) { + goto err; + } + + frame->local = local; + loc_copy(&local->loc, loc); + + ret = dict_set_int8(xattr_req, QUOTA_LIMIT_KEY, 1); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "dict set of key for " + "hard-limit failed"); + goto err; + } + + ret = dict_set_int8(xattr_req, QUOTA_LIMIT_OBJECTS_KEY, 1); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "dict set of key for quota object limit failed"); + goto err; + } - priv = this->private; + STACK_WIND(frame, quota_lookup_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->lookup, loc, xattr_req); - if ((op_ret >= 0) && priv->disk_usage_limit) { - gf_quota_usage_subtract (this, buf->st_blocks * 512); - } + ret = 0; - STACK_UNWIND_STRICT (mkdir, frame, op_ret, op_errno, inode, buf, - preparent, postparent); - return 0; +err: + if (xattr_req) + dict_unref(xattr_req); + + if (ret < 0) { + QUOTA_STACK_UNWIND(lookup, frame, -1, ENOMEM, NULL, NULL, NULL, NULL); + } + + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->lookup, + loc, xattr_req); + return 0; +} + +int32_t +quota_writev_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) +{ + int32_t ret = 0; + uint64_t ctx_int = 0; + quota_inode_ctx_t *ctx = NULL; + quota_local_t *local = NULL; + + local = frame->local; + + if ((op_ret < 0) || (local == NULL) || (postbuf == NULL)) { + goto out; + } + + ret = inode_ctx_get(local->loc.inode, this, &ctx_int); + if (ret) { + gf_msg(this->name, GF_LOG_WARNING, 0, Q_MSG_INODE_CTX_GET_FAILED, + "%s: failed to get the " + "context", + local->loc.path); + goto out; + } + + ctx = (quota_inode_ctx_t *)(unsigned long)ctx_int; + + if (ctx == NULL) { + gf_msg(this->name, GF_LOG_WARNING, 0, Q_MSG_INODE_CTX_GET_FAILED, + "quota context not set in %s (gfid:%s)", local->loc.path, + uuid_utoa(local->loc.inode->gfid)); + goto out; + } + + LOCK(&ctx->lock); + { + ctx->buf = *postbuf; + } + UNLOCK(&ctx->lock); + +out: + QUOTA_STACK_UNWIND(writev, frame, op_ret, op_errno, prebuf, postbuf, xdata); + + return 0; } +static int gf_quota_enforcer_log; -int -quota_mkdir (call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode) +int32_t +quota_writev_helper(call_frame_t *frame, xlator_t *this, fd_t *fd, + struct iovec *vector, int32_t count, off_t off, + uint32_t flags, struct iobref *iobref, dict_t *xdata) { - struct quota_priv *priv = NULL; + quota_local_t *local = NULL; + int32_t op_errno = EINVAL; + struct iovec *new_vector = NULL; + int32_t new_count = 0; + + local = frame->local; + + GF_VALIDATE_OR_GOTO("quota", local, unwind); + + if (local->op_ret == -1) { + op_errno = local->op_errno; + + if ((op_errno == EDQUOT) && (local->space_available > 0)) { + new_count = iov_subset(vector, count, 0, local->space_available, + &new_vector, 0); + if (new_count < 0) { + local->op_ret = -1; + local->op_errno = ENOMEM; + goto unwind; + } + + vector = new_vector; + count = new_count; + } else if (op_errno == ENOENT || op_errno == ESTALE) { + /* We may get ENOENT/ESTALE in case of below scenario + * fd = open file.txt + * unlink file.txt + * write on fd + * Here build_ancestry can fail as the file is removed. + * For now ignore ENOENT/ESTALE with writes on active fd + * We need to re-visit this code once we understand + * how other file-system behave in this scenario + */ + gf_msg_debug(this->name, 0, + "quota enforcer failed " + "with ENOENT/ESTALE on %s, cannot check " + "quota limits and allowing writes", + uuid_utoa(fd->inode->gfid)); + } else if ((op_errno == EINVAL) && + !inode_parent(local->loc.inode, 0, NULL)) { + /* We may get INVAL with parent == NULL, + * in case of below scenario + * 1. enable quota + * 2. glusterfsd stop/start + * 3. nameless lookup + * 4. write on fd + * Here build_ancestry can fail as the file's pgfid + * is't exist. + * For now ignore EINVAL with writes on active fd + * untils the pgfid is created at name lookup + */ + GF_LOG_OCCASIONALLY(gf_quota_enforcer_log, this->name, + GF_LOG_CRITICAL, + "Quota cannot be enforced as " + "parent is not available and writes are being " + "allowed without checking whether they are " + "within quota limits. This can happen if Quota " + "crawl is not complete. If crawl has been " + "completed, please file a bug."); + } else { + goto unwind; + } + } - priv = this->private; + STACK_WIND(frame, quota_writev_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->writev, fd, vector, count, off, flags, + iobref, xdata); - if (gf_quota_check_free_disk (this) == -1) { - gf_log (this->name, GF_LOG_ERROR, - "min-free-disk limit (%u) crossed, current available is %u", - priv->min_free_disk_limit, priv->current_free_disk); - STACK_UNWIND_STRICT (mkdir, frame, -1, ENOSPC, NULL, NULL, - NULL, NULL); - return 0; - - } + if (new_vector != NULL) + GF_FREE(new_vector); - if (priv->current_disk_usage > priv->disk_usage_limit) { - gf_log (this->name, GF_LOG_ERROR, - "Disk usage limit (%"PRIu64") crossed, current usage is %"PRIu64"", - priv->disk_usage_limit, priv->current_disk_usage); - STACK_UNWIND_STRICT (mkdir, frame, -1, ENOSPC, NULL, NULL, - NULL, NULL); - return 0; + return 0; + +unwind: + QUOTA_STACK_UNWIND(writev, frame, -1, op_errno, NULL, NULL, NULL); + return 0; +} + +int32_t +quota_writev(call_frame_t *frame, xlator_t *this, fd_t *fd, + struct iovec *vector, int32_t count, off_t off, uint32_t flags, + struct iobref *iobref, dict_t *xdata) +{ + quota_priv_t *priv = NULL; + int32_t op_errno = EINVAL; + int32_t parents = 0; + int32_t fail_count = 0; + uint64_t size = 0; + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + quota_dentry_t *dentry = NULL, *tmp = NULL; + call_stub_t *stub = NULL; + struct list_head head; + inode_t *par_inode = NULL; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + INIT_LIST_HEAD(&head); + + GF_ASSERT(frame); + GF_VALIDATE_OR_GOTO("quota", this, unwind); + GF_VALIDATE_OR_GOTO(this->name, fd, unwind); + + local = quota_local_new(); + if (local == NULL) { + goto unwind; + } + + frame->local = local; + local->loc.inode = inode_ref(fd->inode); + + (void)quota_inode_ctx_get(fd->inode, this, &ctx, 0); + if (ctx == NULL) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(fd->inode->gfid)); + } + + stub = fop_writev_stub(frame, quota_writev_helper, fd, vector, count, off, + flags, iobref, xdata); + if (stub == NULL) { + op_errno = ENOMEM; + goto unwind; + } + + priv = this->private; + GF_VALIDATE_OR_GOTO(this->name, priv, unwind); + + parents = quota_add_parents_from_ctx(ctx, &head); + if (parents == -1) { + op_errno = errno; + goto unwind; + } + + size = iov_length(vector, count); + + LOCK(&local->lock); + { + local->delta = size; + local->object_delta = 0; + local->link_count = (parents != 0) ? parents : 1; + local->stub = stub; + } + UNLOCK(&local->lock); + + if (parents == 0) { + /* nameless lookup on this inode, allow quota to reconstruct + * ancestry as part of check_limit. + */ + quota_check_limit(frame, fd->inode, this); + } else { + list_for_each_entry_safe(dentry, tmp, &head, next) + { + par_inode = do_quota_check_limit(frame, fd->inode, this, dentry, + _gf_false); + if (par_inode == NULL) { + if (ctx) { + /* remove stale entry from inode ctx */ + quota_dentry_del(ctx, dentry->name, dentry->par); + parents--; + fail_count++; + } + } else { + inode_unref(par_inode); + } + __quota_dentry_free(dentry); + } + + if (parents == 0) { + LOCK(&local->lock); + { + local->link_count++; + } + UNLOCK(&local->lock); + quota_check_limit(frame, fd->inode, this); + } + + while (fail_count != 0) { + quota_link_count_decrement(frame); + fail_count--; } + } - STACK_WIND (frame, quota_mkdir_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->mkdir, - loc, mode); + return 0; - return 0; +unwind: + QUOTA_STACK_UNWIND(writev, frame, -1, op_errno, NULL, NULL, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->writev, + fd, vector, count, off, flags, iobref, xdata); + return 0; } +int32_t +quota_mkdir_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + QUOTA_STACK_UNWIND(mkdir, frame, op_ret, op_errno, inode, buf, preparent, + postparent, xdata); + return 0; +} -int -quota_unlink_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, struct stat *preparent, - struct stat *postparent) +int32_t +quota_mkdir_helper(call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode, + mode_t umask, dict_t *xdata) { - struct quota_local *local = NULL; + quota_local_t *local = NULL; + int32_t op_errno = EINVAL; + + local = frame->local; + + GF_VALIDATE_OR_GOTO("quota", local, unwind); + + op_errno = local->op_errno; - local = frame->local; + if (local->op_ret == -1) { + goto unwind; + } - if (local) { - if (op_ret >= 0) { - gf_quota_usage_subtract (this, - local->stbuf.st_blocks * 512); - } - loc_wipe (&local->loc); - } + STACK_WIND(frame, quota_mkdir_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->mkdir, loc, mode, umask, xdata); - STACK_UNWIND_STRICT (unlink, frame, op_ret, op_errno, preparent, postparent); - return 0; + return 0; + +unwind: + QUOTA_STACK_UNWIND(mkdir, frame, -1, op_errno, NULL, NULL, NULL, NULL, + NULL); + return 0; } +int32_t +quota_mkdir(call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode, + mode_t umask, dict_t *xdata) +{ + quota_priv_t *priv = NULL; + int32_t ret = 0, op_errno = 0; + quota_local_t *local = NULL; + call_stub_t *stub = NULL; + + priv = this->private; + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + if (!should_quota_enforce(this, xdata, GF_FOP_MKDIR)) { + gf_msg(this->name, GF_LOG_DEBUG, 0, Q_MSG_ENFORCEMENT_SKIPPED, + "Enforcement has been skipped(internal fop)."); + goto off; + } + + local = quota_local_new(); + if (local == NULL) { + op_errno = ENOMEM; + goto err; + } + + frame->local = local; + + ret = loc_copy(&local->loc, loc); + if (ret) { + op_errno = ENOMEM; + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + goto err; + } + + stub = fop_mkdir_stub(frame, quota_mkdir_helper, loc, mode, umask, xdata); + if (stub == NULL) { + op_errno = ENOMEM; + goto err; + } + + LOCK(&local->lock); + { + local->stub = stub; + local->delta = 0; + local->object_delta = 1; + local->link_count = 1; + } + UNLOCK(&local->lock); + + quota_check_limit(frame, loc->parent, this); + return 0; + +err: + QUOTA_STACK_UNWIND(mkdir, frame, -1, op_errno, NULL, NULL, NULL, NULL, + NULL); + + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->mkdir, + loc, mode, umask, xdata); + + return 0; +} -int -quota_unlink_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, struct stat *buf) +int32_t +quota_create_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, fd_t *fd, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) { - struct quota_local *local = NULL; + int32_t ret = -1; + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + quota_dentry_t *dentry = NULL; + + local = frame->local; + if (op_ret < 0) { + goto unwind; + } + + ret = quota_inode_ctx_get(inode, this, &ctx, 1); + if ((ret == -1) || (ctx == NULL)) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_INODE_CTX_GET_FAILED, + "cannot create quota " + "context in inode(gfid:%s)", + uuid_utoa(inode->gfid)); + op_ret = -1; + op_errno = ENOMEM; + goto unwind; + } + + LOCK(&ctx->lock); + { + ctx->buf = *buf; + + dentry = __quota_dentry_new(ctx, (char *)local->loc.name, + local->loc.parent->gfid); + if (dentry == NULL) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "cannot create a new dentry " + "(name:%s) for inode(gfid:%s)", + local->loc.name, uuid_utoa(local->loc.inode->gfid)); + op_ret = -1; + op_errno = ENOMEM; + goto unlock; + } + } +unlock: + UNLOCK(&ctx->lock); - local = frame->local; +unwind: + QUOTA_STACK_UNWIND(create, frame, op_ret, op_errno, fd, inode, buf, + preparent, postparent, xdata); + return 0; +} - if (op_ret >= 0) { - if (buf->st_nlink == 1) { - local->stbuf = *buf; - } - } +int32_t +quota_create_helper(call_frame_t *frame, xlator_t *this, loc_t *loc, + int32_t flags, mode_t mode, mode_t umask, fd_t *fd, + dict_t *xdata) +{ + quota_local_t *local = NULL; + int32_t op_errno = EINVAL; + + local = frame->local; + + GF_VALIDATE_OR_GOTO("quota", local, unwind); - STACK_WIND (frame, quota_unlink_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->unlink, - &local->loc); + if (local->op_ret == -1) { + op_errno = local->op_errno; + goto unwind; + } - return 0; + STACK_WIND(frame, quota_create_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->create, loc, flags, mode, umask, fd, + xdata); + return 0; + +unwind: + QUOTA_STACK_UNWIND(create, frame, -1, op_errno, NULL, NULL, NULL, NULL, + NULL, NULL); + return 0; } +int32_t +quota_create(call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t flags, + mode_t mode, mode_t umask, fd_t *fd, dict_t *xdata) +{ + quota_priv_t *priv = NULL; + int32_t ret = -1; + quota_local_t *local = NULL; + int32_t op_errno = 0; + call_stub_t *stub = NULL; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + QUOTA_WIND_FOR_INTERNAL_FOP(xdata, off); + + local = quota_local_new(); + if (local == NULL) { + op_errno = ENOMEM; + goto err; + } + + frame->local = local; + + ret = loc_copy(&local->loc, loc); + if (ret) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + op_errno = ENOMEM; + goto err; + } + + stub = fop_create_stub(frame, quota_create_helper, loc, flags, mode, umask, + fd, xdata); + if (stub == NULL) { + goto err; + } + + LOCK(&local->lock); + { + local->link_count = 1; + local->stub = stub; + local->delta = 0; + local->object_delta = 1; + } + UNLOCK(&local->lock); + + quota_check_limit(frame, loc->parent, this); + return 0; +err: + QUOTA_STACK_UNWIND(create, frame, -1, op_errno, NULL, NULL, NULL, NULL, + NULL, NULL); + + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->create, + loc, flags, mode, umask, fd, xdata); + return 0; +} -int -quota_unlink (call_frame_t *frame, xlator_t *this, loc_t *loc) +int32_t +quota_unlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) { - struct quota_local *local = NULL; - struct quota_priv *priv = NULL; + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + uint64_t value = 0; - priv = this->private; + if (op_ret < 0) { + goto out; + } - if (priv->disk_usage_limit) { - local = CALLOC (1, sizeof (struct quota_local)); - frame->local = local; + local = (quota_local_t *)frame->local; - loc_copy (&local->loc, loc); + inode_ctx_get(local->loc.inode, this, &value); + ctx = (quota_inode_ctx_t *)(unsigned long)value; - STACK_WIND (frame, - quota_unlink_stat_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->stat, - loc); - return 0; - } + if (ctx == NULL) { + gf_msg(this->name, GF_LOG_INFO, EINVAL, Q_MSG_INODE_CTX_GET_FAILED, + "quota context not set inode (gfid:%s)", + uuid_utoa(local->loc.gfid)); + goto out; + } - STACK_WIND (frame, quota_unlink_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->unlink, - loc); - return 0; + quota_dentry_del(ctx, local->loc.name, local->loc.parent->gfid); + +out: + QUOTA_STACK_UNWIND(unlink, frame, op_ret, op_errno, preparent, postparent, + xdata); + return 0; } +int32_t +quota_unlink(call_frame_t *frame, xlator_t *this, loc_t *loc, int xflag, + dict_t *xdata) +{ + quota_priv_t *priv = NULL; + int32_t ret = -1; + quota_local_t *local = NULL; + + priv = this->private; -int -quota_rmdir_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, struct stat *preparent, - struct stat *postparent) + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + local = quota_local_new(); + if (local == NULL) { + goto err; + } + + frame->local = local; + + ret = loc_copy(&local->loc, loc); + if (ret) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + goto err; + } + + STACK_WIND(frame, quota_unlink_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->unlink, loc, xflag, xdata); + + ret = 0; + +err: + if (ret == -1) { + QUOTA_STACK_UNWIND(unlink, frame, -1, 0, NULL, NULL, NULL); + } + + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->unlink, + loc, xflag, xdata); + return 0; +} + +int32_t +quota_link_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + int32_t ret = -1; + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + quota_dentry_t *dentry = NULL; + char found = 0; + + if (op_ret < 0) { + goto out; + } + + local = (quota_local_t *)frame->local; + + ret = quota_inode_ctx_get(inode, this, &ctx, 0); + if ((ret == -1) || (ctx == NULL)) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(inode->gfid)); + goto out; + } + + LOCK(&ctx->lock); + { + list_for_each_entry(dentry, &ctx->parents, next) + { + if ((strcmp(dentry->name, local->loc.name) == 0) && + (gf_uuid_compare(local->loc.parent->gfid, dentry->par) == 0)) { + found = 1; + + gf_msg_debug(this->name, 0, + "new entry being" + " linked (name:%s) for inode " + "(gfid:%s) is already present " + "in inode-dentry-list", + dentry->name, uuid_utoa(local->loc.inode->gfid)); + break; + } + } + + if (!found) { + dentry = __quota_dentry_new(ctx, (char *)local->loc.name, + local->loc.parent->gfid); + if (dentry == NULL) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "cannot create a new dentry (name:%s)" + "for inode(gfid:%s)", + local->loc.name, uuid_utoa(local->loc.inode->gfid)); + op_ret = -1; + op_errno = ENOMEM; + goto unlock; + } + } + + ctx->buf = *buf; + } +unlock: + UNLOCK(&ctx->lock); + +out: + QUOTA_STACK_UNWIND(link, frame, op_ret, op_errno, inode, buf, preparent, + postparent, xdata); + + return 0; +} + +int32_t +quota_link_helper(call_frame_t *frame, xlator_t *this, loc_t *oldloc, + loc_t *newloc, dict_t *xdata) { - struct quota_local *local = NULL; + quota_local_t *local = NULL; + int32_t op_errno = EINVAL; + + local = frame->local; - local = frame->local; + GF_VALIDATE_OR_GOTO("quota", local, unwind); - if (local) { - if (op_ret >= 0) { - gf_quota_usage_subtract (this, local->stbuf.st_blocks * 512); - } - loc_wipe (&local->loc); - } + op_errno = local->op_errno; - STACK_UNWIND_STRICT (rmdir, frame, op_ret, op_errno, preparent, postparent); - return 0; + if (local->op_ret == -1) { + goto unwind; + } + + STACK_WIND(frame, quota_link_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->link, oldloc, newloc, xdata); + return 0; + +unwind: + QUOTA_STACK_UNWIND(link, frame, -1, op_errno, NULL, NULL, NULL, NULL, NULL); + return 0; } +void +quota_link_continue(call_frame_t *frame) +{ + int32_t ret = -1; + int32_t op_errno = EIO; + quota_local_t *local = NULL; + uuid_t common_ancestor = {0}; + xlator_t *this = NULL; + quota_inode_ctx_t *ctx = NULL; + inode_t *src_parent = NULL; + inode_t *dst_parent = NULL; + + local = frame->local; + this = THIS; + + if (local->op_ret < 0) { + op_errno = local->op_errno; + goto err; + } + + if (local->xdata && dict_get(local->xdata, GLUSTERFS_INTERNAL_FOP_KEY)) { + /* Treat link as rename, crawl upwards only till common ancestor + */ + ret = quota_find_common_ancestor( + local->oldloc.inode, local->newloc.parent, &common_ancestor); + if (ret < 0 || gf_uuid_is_null(common_ancestor)) { + gf_msg(this->name, GF_LOG_ERROR, ESTALE, + Q_MSG_ANCESTRY_BUILD_FAILED, + "failed to get " + "common_ancestor for %s and %s", + local->oldloc.path, local->newloc.path); + op_errno = ESTALE; + goto err; + } + } else { + /* Treat link as a new file. + * TODO: Currently marker accounts twice for the links created + * across directories. + * This needs re-visit if marker accounts only once + * for the links created across directories + */ + if (local->oldloc.parent) + src_parent = inode_ref(local->oldloc.parent); + else + src_parent = inode_parent(local->oldloc.inode, 0, NULL); + dst_parent = local->newloc.parent; + + /* No need to check quota limit if src and dst parents are same + */ + if (src_parent == dst_parent || + gf_uuid_compare(src_parent->gfid, dst_parent->gfid) == 0) { + inode_unref(src_parent); + goto wind; + } -int -quota_rmdir_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, struct stat *buf) + inode_unref(src_parent); + } + + quota_inode_ctx_get(local->oldloc.inode, this, &ctx, 0); + if (ctx == NULL) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(local->oldloc.inode->gfid)); + } + + LOCK(&local->lock); + { + local->link_count = 1; + local->delta = (ctx != NULL) ? ctx->buf.ia_blocks * 512 : 0; + local->object_delta = 1; + gf_uuid_copy(local->common_ancestor, common_ancestor); + } + UNLOCK(&local->lock); + + quota_check_limit(frame, local->newloc.parent, this); + return; + +err: + QUOTA_STACK_UNWIND(link, frame, -1, op_errno, NULL, NULL, NULL, NULL, NULL); + return; + +wind: + STACK_WIND(frame, quota_link_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->link, &(local->oldloc), + &(local->newloc), local->xdata); + return; +} + +int32_t +quota_link(call_frame_t *frame, xlator_t *this, loc_t *oldloc, loc_t *newloc, + dict_t *xdata) +{ + quota_priv_t *priv = NULL; + int32_t ret = -1; + int32_t op_errno = ENOMEM; + quota_local_t *local = NULL; + call_stub_t *stub = NULL; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + local = quota_local_new(); + if (local == NULL) { + goto err; + } + + frame->local = (void *)local; + + if (xdata) + local->xdata = dict_ref(xdata); + + ret = loc_copy(&local->loc, newloc); + if (ret == -1) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + goto err; + } + + ret = loc_copy(&local->oldloc, oldloc); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + goto err; + } + + ret = loc_copy(&local->newloc, newloc); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + goto err; + } + + /* No need to check quota limit if src and dst parents are same */ + if (oldloc->parent && newloc->parent && + !gf_uuid_compare(oldloc->parent->gfid, newloc->parent->gfid)) { + gf_msg_debug(this->name, GF_LOG_DEBUG, + "link %s -> %s are " + "in the same directory, so skip check limit", + oldloc->path, newloc->path); + goto wind; + } + + stub = fop_link_stub(frame, quota_link_helper, oldloc, newloc, xdata); + if (stub == NULL) { + goto err; + } + + LOCK(&local->lock); + { + local->link_count = 2; + local->fop_continue_cbk = quota_link_continue; + local->stub = stub; + } + UNLOCK(&local->lock); + + check_ancestory(frame, newloc->parent); + + /* source parent can be NULL, so do check_ancestry on a file */ + if (oldloc->parent) + check_ancestory(frame, oldloc->parent); + else + check_ancestory(frame, oldloc->inode); + + return 0; + +err: + QUOTA_STACK_UNWIND(link, frame, -1, op_errno, NULL, NULL, NULL, NULL, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->link, + oldloc, newloc, xdata); + return 0; + +wind: + STACK_WIND(frame, quota_link_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->link, oldloc, newloc, xdata); + return 0; +} + +int32_t +quota_rename_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + struct iatt *preoldparent, struct iatt *postoldparent, + struct iatt *prenewparent, struct iatt *postnewparent, + dict_t *xdata) { - struct quota_local *local = NULL; + int32_t ret = -1; + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + quota_dentry_t *old_dentry = NULL, *dentry = NULL; + char new_dentry_found = 0; + + if (op_ret < 0) { + goto out; + } + + local = frame->local; + + GF_VALIDATE_OR_GOTO("quota", local, out); + + if (!QUOTA_REG_OR_LNK_FILE(local->oldloc.inode->ia_type)) + goto out; + + ret = quota_inode_ctx_get(local->oldloc.inode, this, &ctx, 0); + if ((ret == -1) || (ctx == NULL)) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(local->oldloc.inode->gfid)); + + goto out; + } + + LOCK(&ctx->lock); + { + list_for_each_entry(dentry, &ctx->parents, next) + { + if ((strcmp(dentry->name, local->oldloc.name) == 0) && + (gf_uuid_compare(local->oldloc.parent->gfid, dentry->par) == + 0)) { + old_dentry = dentry; + } else if ((strcmp(dentry->name, local->newloc.name) == 0) && + (gf_uuid_compare(local->newloc.parent->gfid, + dentry->par) == 0)) { + new_dentry_found = 1; + gf_msg_debug(this->name, 0, + "new entry being " + "linked (name:%s) for inode (gfid:%s) " + "is in inode-dentry-list", + dentry->name, + uuid_utoa(local->oldloc.inode->gfid)); + } + + if (old_dentry && new_dentry_found) + break; + } + + if (old_dentry != NULL) { + __quota_dentry_free(old_dentry); + } else { + gf_msg_debug(this->name, 0, + "dentry corresponding" + "the path just renamed (name:%s) is not" + " present", + local->oldloc.name); + } - local = frame->local; + if (!new_dentry_found) { + dentry = __quota_dentry_new(ctx, (char *)local->newloc.name, + local->newloc.parent->gfid); + if (dentry == NULL) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "cannot create a new dentry (name:%s) " + "for inode(gfid:%s)", + local->newloc.name, + uuid_utoa(local->newloc.inode->gfid)); + op_ret = -1; + op_errno = ENOMEM; + goto unlock; + } + } - if (op_ret >= 0) { - local->stbuf = *buf; - } + ctx->buf = *buf; + } +unlock: + UNLOCK(&ctx->lock); - STACK_WIND (frame, quota_rmdir_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->rmdir, - &local->loc); +out: + QUOTA_STACK_UNWIND(rename, frame, op_ret, op_errno, buf, preoldparent, + postoldparent, prenewparent, postnewparent, xdata); - return 0; + return 0; } +int32_t +quota_rename_helper(call_frame_t *frame, xlator_t *this, loc_t *oldloc, + loc_t *newloc, dict_t *xdata) +{ + quota_local_t *local = NULL; + int32_t op_errno = EINVAL; -int -quota_rmdir (call_frame_t *frame, xlator_t *this, loc_t *loc) + local = frame->local; + + GF_VALIDATE_OR_GOTO("quota", local, unwind); + + op_errno = local->op_errno; + + if (local->op_ret == -1) { + goto unwind; + } + + STACK_WIND(frame, quota_rename_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->rename, oldloc, newloc, xdata); + + return 0; + +unwind: + QUOTA_STACK_UNWIND(rename, frame, -1, op_errno, NULL, NULL, NULL, NULL, + NULL, NULL); + return 0; +} + +static int32_t +quota_rename_get_size_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, dict_t *xdata, + struct iatt *postparent) { - struct quota_local *local = NULL; - struct quota_priv *priv = NULL; + quota_local_t *local = NULL; + int32_t ret = 0; + int64_t *size = 0; + + GF_ASSERT(frame); + GF_VALIDATE_OR_GOTO_WITH_ERROR("quota", this, out, op_errno, EINVAL); + GF_VALIDATE_OR_GOTO_WITH_ERROR(this->name, xdata, out, op_errno, EINVAL); + local = frame->local; + GF_ASSERT(local); + local->link_count = 1; + + if (op_ret < 0) + goto out; + + ret = dict_get_bin(xdata, QUOTA_SIZE_KEY, (void **)&size); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, EINVAL, Q_MSG_SIZE_KEY_MISSING, + "size key not present in dict"); + op_errno = EINVAL; + goto out; + } + local->delta = ntoh64(*size); + local->object_delta = 1; + quota_check_limit(frame, local->newloc.parent, this); + return 0; + +out: + quota_handle_validate_error(frame, -1, op_errno); + return 0; +} + +void +quota_rename_continue(call_frame_t *frame) +{ + int32_t ret = -1; + int32_t op_errno = EIO; + quota_local_t *local = NULL; + uuid_t common_ancestor = {0}; + xlator_t *this = NULL; + quota_inode_ctx_t *ctx = NULL; + + local = frame->local; + this = THIS; + + if (local->op_ret < 0) { + op_errno = local->op_errno; + goto err; + } + + ret = quota_find_common_ancestor(local->oldloc.parent, local->newloc.parent, + &common_ancestor); + if (ret < 0 || gf_uuid_is_null(common_ancestor)) { + gf_msg(this->name, GF_LOG_ERROR, ESTALE, Q_MSG_ANCESTRY_BUILD_FAILED, + "failed to get " + "common_ancestor for %s and %s", + local->oldloc.path, local->newloc.path); + op_errno = ESTALE; + goto err; + } + + LOCK(&local->lock); + { + local->link_count = 1; + gf_uuid_copy(local->common_ancestor, common_ancestor); + } + UNLOCK(&local->lock); + + if (QUOTA_REG_OR_LNK_FILE(local->oldloc.inode->ia_type)) { + ret = quota_inode_ctx_get(local->oldloc.inode, this, &ctx, 0); + if (ctx == NULL) { + gf_msg(this->name, GF_LOG_WARNING, 0, Q_MSG_INODE_CTX_GET_FAILED, + "quota context not set in inode (gfid:%s), " + "considering file size as zero while enforcing " + "quota on new ancestry", + uuid_utoa(local->oldloc.inode->gfid)); + + local->delta = 0; + local->object_delta = 1; + } else { + /* FIXME: We need to account for the size occupied by + * this inode on the target directory. To avoid double + * accounting, we need to modify enforcer to perform + * quota_check_limit only up till the least common + * ancestor directory inode*/ + + /* FIXME: The following code assumes that regular files + * and link files are present, in their entirety, in a + * single brick. This *assumption is invalid in the + * case of stripe.*/ + + local->delta = ctx->buf.ia_blocks * 512; + local->object_delta = 1; + } - priv = this->private; + } else if (IA_ISDIR(local->oldloc.inode->ia_type)) { + ret = quota_validate(frame, local->oldloc.inode, this, + quota_rename_get_size_cbk); + if (ret) { + op_errno = -ret; + goto err; + } - if (priv->disk_usage_limit) { - local = CALLOC (1, sizeof (struct quota_local)); - frame->local = local; + return; + } - loc_copy (&local->loc, loc); + quota_check_limit(frame, local->newloc.parent, this); + return; - STACK_WIND (frame, quota_rmdir_stat_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->stat, loc); - return 0; - } +err: + QUOTA_STACK_UNWIND(rename, frame, -1, op_errno, NULL, NULL, NULL, NULL, + NULL, NULL); + return; +} - STACK_WIND (frame, quota_rmdir_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->rmdir, - loc); - return 0; +int32_t +quota_rename(call_frame_t *frame, xlator_t *this, loc_t *oldloc, loc_t *newloc, + dict_t *xdata) +{ + quota_priv_t *priv = NULL; + int32_t ret = -1; + int32_t op_errno = ENOMEM; + quota_local_t *local = NULL; + call_stub_t *stub = NULL; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + local = quota_local_new(); + if (local == NULL) { + goto err; + } + + frame->local = local; + + ret = loc_copy(&local->oldloc, oldloc); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + goto err; + } + + ret = loc_copy(&local->newloc, newloc); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + goto err; + } + + /* No need to check quota limit if src and dst parents are same */ + if (oldloc->parent && newloc->parent && + !gf_uuid_compare(oldloc->parent->gfid, newloc->parent->gfid)) { + gf_msg_debug(this->name, 0, + "rename %s -> %s are " + "in the same directory, so skip check limit", + oldloc->path, newloc->path); + goto wind; + } + + stub = fop_rename_stub(frame, quota_rename_helper, oldloc, newloc, xdata); + if (stub == NULL) { + goto err; + } + + LOCK(&local->lock); + { + /* link_count here tell how many check_ancestry should be done + * before continuing the FOP + */ + local->link_count = 2; + local->stub = stub; + local->fop_continue_cbk = quota_rename_continue; + } + UNLOCK(&local->lock); + + check_ancestory(frame, newloc->parent); + check_ancestory(frame, oldloc->parent); + return 0; + +err: + QUOTA_STACK_UNWIND(rename, frame, -1, op_errno, NULL, NULL, NULL, NULL, + NULL, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->rename, + oldloc, newloc, xdata); + return 0; + +wind: + STACK_WIND(frame, quota_rename_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->rename, oldloc, newloc, xdata); + return 0; } +int32_t +quota_symlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + quota_dentry_t *dentry = NULL; + int32_t ret = -1; + + if (op_ret < 0) { + goto out; + } + + local = frame->local; + + ret = quota_inode_ctx_get(local->loc.inode, this, &ctx, 1); + if ((ret == -1) || (ctx == NULL)) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(local->loc.inode->gfid)); + + goto out; + } + + LOCK(&ctx->lock); + { + ctx->buf = *buf; + + dentry = __quota_dentry_new(ctx, (char *)local->loc.name, + local->loc.parent->gfid); + if (dentry == NULL) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "cannot create " + "a new dentry (name:%s) for inode(gfid:%s)", + local->loc.name, uuid_utoa(local->loc.inode->gfid)); + op_ret = -1; + op_errno = ENOMEM; + } + } + UNLOCK(&ctx->lock); + +out: + QUOTA_STACK_UNWIND(symlink, frame, op_ret, op_errno, inode, buf, preparent, + postparent, xdata); + + return 0; +} int -quota_symlink_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, inode_t *inode, - struct stat *buf, struct stat *preparent, - struct stat *postparent) +quota_symlink_helper(call_frame_t *frame, xlator_t *this, const char *linkpath, + loc_t *loc, mode_t umask, dict_t *xdata) { - struct quota_priv *priv = NULL; + quota_local_t *local = NULL; + int32_t op_errno = EINVAL; - priv = this->private; + local = frame->local; - if ((op_ret >= 0) && priv->disk_usage_limit) { - gf_quota_usage_add (this, buf->st_blocks * 512); - } + GF_VALIDATE_OR_GOTO("quota", local, unwind); - STACK_UNWIND_STRICT (symlink, frame, op_ret, op_errno, inode, buf, - preparent, postparent); - return 0; -} + if (local->op_ret == -1) { + op_errno = local->op_errno; + goto unwind; + } + STACK_WIND(frame, quota_symlink_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->symlink, linkpath, loc, umask, xdata); + return 0; + +unwind: + QUOTA_STACK_UNWIND(symlink, frame, -1, op_errno, NULL, NULL, NULL, NULL, + NULL); + return 0; +} int -quota_symlink (call_frame_t *frame, xlator_t *this, - const char *linkpath, loc_t *loc) +quota_symlink(call_frame_t *frame, xlator_t *this, const char *linkpath, + loc_t *loc, mode_t umask, dict_t *xdata) +{ + quota_priv_t *priv = NULL; + int32_t ret = -1; + int32_t op_errno = ENOMEM; + quota_local_t *local = NULL; + call_stub_t *stub = NULL; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + local = quota_local_new(); + if (local == NULL) { + goto err; + } + + frame->local = local; + + ret = loc_copy(&local->loc, loc); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + goto err; + } + + stub = fop_symlink_stub(frame, quota_symlink_helper, linkpath, loc, umask, + xdata); + if (stub == NULL) { + goto err; + } + + LOCK(&local->lock); + { + local->stub = stub; + local->delta = strlen(linkpath); + local->object_delta = 1; + local->link_count = 1; + } + UNLOCK(&local->lock); + + quota_check_limit(frame, loc->parent, this); + return 0; + +err: + QUOTA_STACK_UNWIND(symlink, frame, -1, op_errno, NULL, NULL, NULL, NULL, + NULL); + + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->symlink, + linkpath, loc, umask, xdata); + return 0; +} + +int32_t +quota_truncate_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) { - struct quota_priv *priv = NULL; + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + + if (op_ret < 0) { + goto out; + } + + local = frame->local; + + GF_VALIDATE_OR_GOTO("quota", local, out); + + quota_inode_ctx_get(local->loc.inode, this, &ctx, 0); + if (ctx == NULL) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(local->loc.inode->gfid)); + goto out; + } + + LOCK(&ctx->lock); + { + ctx->buf = *postbuf; + } + UNLOCK(&ctx->lock); + +out: + QUOTA_STACK_UNWIND(truncate, frame, op_ret, op_errno, prebuf, postbuf, + xdata); + return 0; +} - priv = this->private; +int32_t +quota_truncate(call_frame_t *frame, xlator_t *this, loc_t *loc, off_t offset, + dict_t *xdata) +{ + quota_priv_t *priv = NULL; + int32_t ret = -1; + quota_local_t *local = NULL; - if (gf_quota_check_free_disk (this) == -1) { - gf_log (this->name, GF_LOG_ERROR, - "min-free-disk limit (%u) crossed, current available is %u", - priv->min_free_disk_limit, priv->current_free_disk); - STACK_UNWIND_STRICT (symlink, frame, -1, ENOSPC, NULL, NULL, - NULL, NULL); - return 0; - - } - if (priv->current_disk_usage > priv->disk_usage_limit) { - gf_log (this->name, GF_LOG_ERROR, - "Disk usage limit (%"PRIu64") crossed, current usage is %"PRIu64"", - priv->disk_usage_limit, priv->current_disk_usage); - STACK_UNWIND_STRICT (symlink, frame, -1, ENOSPC, NULL, NULL, - NULL, NULL); - return 0; + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + local = quota_local_new(); + if (local == NULL) { + goto err; + } + + frame->local = local; + + ret = loc_copy(&local->loc, loc); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + goto err; + } + + STACK_WIND(frame, quota_truncate_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->truncate, loc, offset, xdata); + + return 0; + +err: + QUOTA_STACK_UNWIND(truncate, frame, -1, ENOMEM, NULL, NULL, NULL); + + return 0; +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->truncate, + loc, offset, xdata); + return 0; +} + +int32_t +quota_ftruncate_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) +{ + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + + if (op_ret < 0) { + goto out; + } + + local = frame->local; + + GF_VALIDATE_OR_GOTO("quota", local, out); + + quota_inode_ctx_get(local->loc.inode, this, &ctx, 0); + if (ctx == NULL) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(local->loc.inode->gfid)); + goto out; + } + + LOCK(&ctx->lock); + { + ctx->buf = *postbuf; + } + UNLOCK(&ctx->lock); + +out: + QUOTA_STACK_UNWIND(ftruncate, frame, op_ret, op_errno, prebuf, postbuf, + xdata); + return 0; +} + +int32_t +quota_ftruncate(call_frame_t *frame, xlator_t *this, fd_t *fd, off_t offset, + dict_t *xdata) +{ + quota_priv_t *priv = NULL; + quota_local_t *local = NULL; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + local = quota_local_new(); + if (local == NULL) + goto err; + + frame->local = local; + + local->loc.inode = inode_ref(fd->inode); + + STACK_WIND(frame, quota_ftruncate_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->ftruncate, fd, offset, xdata); + + return 0; +err: + QUOTA_STACK_UNWIND(ftruncate, frame, -1, ENOMEM, NULL, NULL, NULL); + + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->ftruncate, fd, offset, xdata); + return 0; +} + +static int32_t +quota_send_dir_limit_to_cli(call_frame_t *frame, xlator_t *this, inode_t *inode, + const char *name, const int namelen) +{ + int32_t ret = 0; + int dir_limit_len = 0; + char dir_limit[64] = { + 0, + }; + dict_t *dict = NULL; + quota_inode_ctx_t *ctx = NULL; + uint64_t value = 0; + quota_priv_t *priv = NULL; + + priv = this->private; + if (!priv->is_quota_on) { + dir_limit_len = snprintf(dir_limit, sizeof(dir_limit), + "Quota is disabled please turn on"); + goto dict_set; + } + + ret = inode_ctx_get(inode, this, &value); + if (ret < 0) + goto out; + + ctx = (quota_inode_ctx_t *)(unsigned long)value; + dir_limit_len = snprintf(dir_limit, sizeof(dir_limit), + "%" PRId64 ",%" PRId64, ctx->size, ctx->hard_lim); + +dict_set: + dict = dict_new(); + if (dict == NULL) { + ret = -1; + goto out; + } + + ret = dict_set_nstrn(dict, (char *)name, namelen, dir_limit, dir_limit_len); + if (ret < 0) + goto out; + + gf_msg_debug(this->name, 0, "str = %s", dir_limit); + + QUOTA_STACK_UNWIND(getxattr, frame, 0, 0, dict, NULL); + + ret = 0; + +out: + if (dict) + dict_unref(dict); + return ret; +} + +int32_t +quota_fgetxattr(call_frame_t *frame, xlator_t *this, fd_t *fd, const char *name, + dict_t *xdata) +{ + int32_t ret = 0; + + if (name && strcasecmp(name, "trusted.limit.list") == 0) { + ret = quota_send_dir_limit_to_cli(frame, this, fd->inode, + "trusted.limit.list", + SLEN("trusted.limit.list")); + if (ret == 0) { + return 0; } + } - STACK_WIND (frame, quota_symlink_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->symlink, - linkpath, loc); - return 0; + STACK_WIND(frame, default_fgetxattr_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->fgetxattr, fd, name, xdata); + return 0; } +int32_t +quota_getxattr(call_frame_t *frame, xlator_t *this, loc_t *loc, + const char *name, dict_t *xdata) +{ + int32_t ret = 0; + + if ((name != NULL) && strcasecmp(name, "trusted.limit.list") == 0) { + ret = quota_send_dir_limit_to_cli(frame, this, loc->inode, + "trusted.limit.list", + SLEN("trusted.limit.list")); + if (ret == 0) + return 0; + } + + STACK_WIND(frame, default_getxattr_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->getxattr, loc, name, xdata); + return 0; +} -int -quota_create_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, - fd_t *fd, inode_t *inode, struct stat *buf, - struct stat *preparent, struct stat *postparent) +int32_t +quota_stat_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + dict_t *xdata) { - struct quota_priv *priv = this->private; - int ret = 0; + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + + if (op_ret < 0) { + goto out; + } - if ((op_ret >= 0) && priv->disk_usage_limit) { - gf_quota_usage_add (this, buf->st_blocks * 512); + local = frame->local; - ret = fd_ctx_set (fd, this, 1); - } + GF_VALIDATE_OR_GOTO("quota", local, out); - STACK_UNWIND_STRICT (create, frame, op_ret, op_errno, fd, inode, buf, - preparent, postparent); - return 0; + quota_inode_ctx_get(local->loc.inode, this, &ctx, 0); + if (ctx == NULL) { + if (!IA_ISDIR(buf->ia_type)) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(local->loc.inode->gfid)); + } + + goto out; + } + + if (buf) { + LOCK(&ctx->lock); + ctx->buf = *buf; + UNLOCK(&ctx->lock); + } + +out: + QUOTA_STACK_UNWIND(stat, frame, op_ret, op_errno, buf, xdata); + return 0; } +int32_t +quota_stat(call_frame_t *frame, xlator_t *this, loc_t *loc, dict_t *xdata) +{ + quota_priv_t *priv = NULL; + quota_local_t *local = NULL; + int32_t ret = -1; -int -quota_create (call_frame_t *frame, xlator_t *this, - loc_t *loc, int32_t flags, mode_t mode, fd_t *fd) + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + local = quota_local_new(); + if (local == NULL) { + goto unwind; + } + + frame->local = local; + ret = loc_copy(&local->loc, loc); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + goto unwind; + } + + STACK_WIND(frame, quota_stat_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->stat, loc, xdata); + return 0; + +unwind: + QUOTA_STACK_UNWIND(stat, frame, -1, ENOMEM, NULL, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->stat, + loc, xdata); + return 0; +} + +int32_t +quota_fstat_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *buf, + dict_t *xdata) { - struct quota_priv *priv = NULL; + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + + if (op_ret < 0) { + goto out; + } + + local = frame->local; - priv = this->private; + GF_VALIDATE_OR_GOTO("quota", local, out); - if (gf_quota_check_free_disk (this) == -1) { - gf_log (this->name, GF_LOG_ERROR, - "min-free-disk limit (%u) crossed, current available is %u", - priv->min_free_disk_limit, priv->current_free_disk); - STACK_UNWIND_STRICT (create, frame, -1, ENOSPC, NULL, NULL, NULL, - NULL, NULL); - return 0; - - } - if (priv->current_disk_usage > priv->disk_usage_limit) { - gf_log (this->name, GF_LOG_ERROR, - "Disk usage limit (%"PRIu64") crossed, current usage is %"PRIu64"", - priv->disk_usage_limit, priv->current_disk_usage); - STACK_UNWIND_STRICT (create, frame, -1, ENOSPC, NULL, NULL, NULL, - NULL, NULL); - return 0; + quota_inode_ctx_get(local->loc.inode, this, &ctx, 0); + if (ctx == NULL) { + if (!IA_ISDIR(buf->ia_type)) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(local->loc.inode->gfid)); } - STACK_WIND (frame, quota_create_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->create, - loc, flags, mode, fd); - return 0; + goto out; + } + + if (buf) { + LOCK(&ctx->lock); + ctx->buf = *buf; + UNLOCK(&ctx->lock); + } + +out: + QUOTA_STACK_UNWIND(fstat, frame, op_ret, op_errno, buf, xdata); + return 0; } +int32_t +quota_fstat(call_frame_t *frame, xlator_t *this, fd_t *fd, dict_t *xdata) +{ + quota_priv_t *priv = NULL; + quota_local_t *local = NULL; -int -quota_open_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, fd_t *fd) + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + local = quota_local_new(); + if (local == NULL) { + goto unwind; + } + + frame->local = local; + + local->loc.inode = inode_ref(fd->inode); + + STACK_WIND(frame, quota_fstat_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->fstat, fd, xdata); + return 0; + +unwind: + QUOTA_STACK_UNWIND(fstat, frame, -1, ENOMEM, NULL, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->fstat, + fd, xdata); + return 0; +} + +int32_t +quota_readlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, const char *path, + struct iatt *buf, dict_t *xdata) { - int ret = 0; + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + + if (op_ret < 0) { + goto out; + } + + local = frame->local; + + GF_VALIDATE_OR_GOTO("quota", local, out); + + quota_inode_ctx_get(local->loc.inode, this, &ctx, 0); + if (ctx == NULL) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(local->loc.inode->gfid)); + goto out; + } + + LOCK(&ctx->lock); + { + ctx->buf = *buf; + } + UNLOCK(&ctx->lock); + +out: + QUOTA_STACK_UNWIND(readlink, frame, op_ret, op_errno, path, buf, xdata); + return 0; +} - if (op_ret >= 0) - ret = fd_ctx_set (fd, this, 1); +int32_t +quota_readlink(call_frame_t *frame, xlator_t *this, loc_t *loc, size_t size, + dict_t *xdata) +{ + quota_priv_t *priv = NULL; + quota_local_t *local = NULL; + int32_t ret = -1; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + local = quota_local_new(); + if (local == NULL) { + goto unwind; + } + + frame->local = local; - STACK_UNWIND_STRICT (open, frame, op_ret, op_errno, fd); - return 0; + ret = loc_copy(&local->loc, loc); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + goto unwind; + } + + STACK_WIND(frame, quota_readlink_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->readlink, loc, size, xdata); + return 0; + +unwind: + QUOTA_STACK_UNWIND(readlink, frame, -1, ENOMEM, NULL, NULL, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->readlink, + loc, size, xdata); + return 0; } +int32_t +quota_readv_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iovec *vector, + int32_t count, struct iatt *buf, struct iobref *iobref, + dict_t *xdata) +{ + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + + if (op_ret < 0) { + goto out; + } + + local = frame->local; + + GF_VALIDATE_OR_GOTO("quota", local, out); + + quota_inode_ctx_get(local->loc.inode, this, &ctx, 0); + if (ctx == NULL) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(local->loc.inode->gfid)); + goto out; + } + + LOCK(&ctx->lock); + { + ctx->buf = *buf; + } + UNLOCK(&ctx->lock); + +out: + QUOTA_STACK_UNWIND(readv, frame, op_ret, op_errno, vector, count, buf, + iobref, xdata); + return 0; +} -int -quota_open (call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t flags, - fd_t *fd, int32_t wbflags) +int32_t +quota_readv(call_frame_t *frame, xlator_t *this, fd_t *fd, size_t size, + off_t offset, uint32_t flags, dict_t *xdata) { - STACK_WIND (frame, quota_open_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->open, - loc, flags, fd, wbflags); - return 0; + quota_priv_t *priv = NULL; + quota_local_t *local = NULL; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + local = quota_local_new(); + if (local == NULL) { + goto unwind; + } + + frame->local = local; + + local->loc.inode = inode_ref(fd->inode); + + STACK_WIND(frame, quota_readv_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->readv, fd, size, offset, flags, xdata); + return 0; + +unwind: + QUOTA_STACK_UNWIND(readv, frame, -1, ENOMEM, NULL, -1, NULL, NULL, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->readv, + fd, size, offset, flags, xdata); + return 0; +} + +int32_t +quota_fsync_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) +{ + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + + if (op_ret < 0) { + goto out; + } + + local = frame->local; + + GF_VALIDATE_OR_GOTO("quota", local, out); + + quota_inode_ctx_get(local->loc.inode, this, &ctx, 0); + if (ctx == NULL) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(local->loc.inode->gfid)); + goto out; + } + + LOCK(&ctx->lock); + { + ctx->buf = *postbuf; + } + UNLOCK(&ctx->lock); + +out: + QUOTA_STACK_UNWIND(fsync, frame, op_ret, op_errno, prebuf, postbuf, xdata); + return 0; } +int32_t +quota_fsync(call_frame_t *frame, xlator_t *this, fd_t *fd, int32_t flags, + dict_t *xdata) +{ + quota_priv_t *priv = NULL; + quota_local_t *local = NULL; -int -quota_writev_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, struct stat *prebuf, - struct stat *postbuf) + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + local = quota_local_new(); + if (local == NULL) { + goto unwind; + } + + local->loc.inode = inode_ref(fd->inode); + + frame->local = local; + + STACK_WIND(frame, quota_fsync_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->fsync, fd, flags, xdata); + return 0; + +unwind: + QUOTA_STACK_UNWIND(fsync, frame, -1, ENOMEM, NULL, NULL, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->fsync, + fd, flags, xdata); + return 0; +} + +int32_t +quota_setattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *statpre, + struct iatt *statpost, dict_t *xdata) { - struct quota_priv *priv = NULL; - struct quota_local *local = NULL; + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + if (op_ret < 0) { + goto out; + } - priv = this->private; - local = frame->local; + local = frame->local; - if (priv->disk_usage_limit) { - if (op_ret >= 0) { - gf_quota_usage_add (this, (postbuf->st_blocks - - local->stbuf.st_blocks) * 512); - } - fd_unref (local->fd); - iobref_unref (local->iobref); - } + GF_VALIDATE_OR_GOTO("quota", local, out); - STACK_UNWIND_STRICT (writev, frame, op_ret, op_errno, prebuf, postbuf); - return 0; + quota_inode_ctx_get(local->loc.inode, this, &ctx, 0); + if (ctx == NULL) { + if (!IA_ISDIR(statpost->ia_type)) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(local->loc.inode->gfid)); + } + + goto out; + } + + if (statpost) { + LOCK(&ctx->lock); + ctx->buf = *statpost; + UNLOCK(&ctx->lock); + } + +out: + QUOTA_STACK_UNWIND(setattr, frame, op_ret, op_errno, statpre, statpost, + xdata); + return 0; } +int32_t +quota_setattr(call_frame_t *frame, xlator_t *this, loc_t *loc, + struct iatt *stbuf, int32_t valid, dict_t *xdata) +{ + quota_priv_t *priv = NULL; + quota_local_t *local = NULL; + int32_t ret = -1; + + priv = this->private; -int -quota_writev_fstat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, struct stat *buf) + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + local = quota_local_new(); + if (local == NULL) { + goto unwind; + } + + frame->local = local; + + ret = loc_copy(&local->loc, loc); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + goto unwind; + } + + STACK_WIND(frame, quota_setattr_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->setattr, loc, stbuf, valid, xdata); + return 0; + +unwind: + QUOTA_STACK_UNWIND(setattr, frame, -1, ENOMEM, NULL, NULL, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->setattr, + loc, stbuf, valid, xdata); + return 0; +} + +int32_t +quota_fsetattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *statpre, + struct iatt *statpost, dict_t *xdata) { - struct quota_local *local = NULL; - struct quota_priv *priv = NULL; - int iovlen = 0; + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + + if (op_ret < 0) { + goto out; + } + local = frame->local; - local = frame->local; - priv = this->private; + GF_VALIDATE_OR_GOTO("quota", local, out); - if (op_ret >= 0) { - if (priv->current_disk_usage > priv->disk_usage_limit) { - iovlen = iov_length (local->vector, local->count); + quota_inode_ctx_get(local->loc.inode, this, &ctx, 0); + if (ctx == NULL) { + if (!IA_ISDIR(statpost->ia_type)) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(local->loc.inode->gfid)); + } - if (iovlen > (buf->st_blksize - (buf->st_size % buf->st_blksize))) { - fd_unref (local->fd); - iobref_unref (local->iobref); - STACK_UNWIND_STRICT (writev, frame, -1, ENOSPC, - NULL, NULL); - return 0; - } - } - local->stbuf = *buf; - } - - STACK_WIND (frame, quota_writev_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->writev, - local->fd, local->vector, local->count, local->offset, - local->iobref); + goto out; + } - return 0; + LOCK(&ctx->lock); + { + ctx->buf = *statpost; + } + UNLOCK(&ctx->lock); + +out: + QUOTA_STACK_UNWIND(fsetattr, frame, op_ret, op_errno, statpre, statpost, + xdata); + return 0; } +int32_t +quota_fsetattr(call_frame_t *frame, xlator_t *this, fd_t *fd, + struct iatt *stbuf, int32_t valid, dict_t *xdata) +{ + quota_priv_t *priv = NULL; + quota_local_t *local = NULL; -int -quota_writev (call_frame_t *frame, xlator_t *this, fd_t *fd, - struct iovec *vector, int32_t count, off_t off, - struct iobref *iobref) -{ - struct quota_local *local = NULL; - struct quota_priv *priv = NULL; - - priv = this->private; - - if (gf_quota_check_free_disk (this) == -1) { - gf_log (this->name, GF_LOG_ERROR, - "min-free-disk limit (%u) crossed, current available is %u", - priv->min_free_disk_limit, priv->current_free_disk); - STACK_UNWIND_STRICT (writev, frame, -1, ENOSPC, - NULL, NULL); - return 0; - } - - if (priv->disk_usage_limit) { - local = CALLOC (1, sizeof (struct quota_local)); - local->fd = fd_ref (fd); - local->iobref = iobref_ref (iobref); - local->vector = vector; - local->count = count; - local->offset = off; - frame->local = local; - - STACK_WIND (frame, quota_writev_fstat_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->fstat, fd); - return 0; - } - - STACK_WIND (frame, quota_writev_cbk, - FIRST_CHILD(this), - FIRST_CHILD(this)->fops->writev, - fd, vector, count, off, iobref); - return 0; + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + local = quota_local_new(); + if (local == NULL) { + goto unwind; + } + + frame->local = local; + + local->loc.inode = inode_ref(fd->inode); + + STACK_WIND(frame, quota_fsetattr_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->fsetattr, fd, stbuf, valid, xdata); + return 0; + +unwind: + QUOTA_STACK_UNWIND(fsetattr, frame, -1, ENOMEM, NULL, NULL, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->fsetattr, + fd, stbuf, valid, xdata); + return 0; } +int32_t +quota_mknod_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, struct iatt *preparent, + struct iatt *postparent, dict_t *xdata) +{ + int32_t ret = -1; + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + quota_dentry_t *dentry = NULL; + + local = frame->local; + if (op_ret < 0) { + goto unwind; + } + + ret = quota_inode_ctx_get(inode, this, &ctx, 1); + if ((ret == -1) || (ctx == NULL)) { + gf_msg(this->name, GF_LOG_WARNING, 0, Q_MSG_INODE_CTX_GET_FAILED, + "cannot create quota context in " + "inode(gfid:%s)", + uuid_utoa(inode->gfid)); + op_ret = -1; + op_errno = ENOMEM; + goto unwind; + } + + LOCK(&ctx->lock); + { + ctx->buf = *buf; + + dentry = __quota_dentry_new(ctx, (char *)local->loc.name, + local->loc.parent->gfid); + if (dentry == NULL) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "cannot create a new dentry " + "(name:%s) for inode(gfid:%s)", + local->loc.name, uuid_utoa(local->loc.inode->gfid)); + op_ret = -1; + op_errno = ENOMEM; + goto unlock; + } + } +unlock: + UNLOCK(&ctx->lock); + +unwind: + QUOTA_STACK_UNWIND(mknod, frame, op_ret, op_errno, inode, buf, preparent, + postparent, xdata); + return 0; +} int -quota_removexattr_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno) +quota_mknod_helper(call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode, + dev_t rdev, mode_t umask, dict_t *xdata) { - if (op_ret == -1) { - gf_log (this->name, GF_LOG_CRITICAL, - "failed to remove the disk-usage value: %s", - strerror (op_errno)); - } - - STACK_DESTROY (frame->root); - return 0; -} + quota_local_t *local = NULL; + int32_t op_errno = EINVAL; + + local = frame->local; + + GF_VALIDATE_OR_GOTO("quota", local, unwind); + + if (local->op_ret == -1) { + op_errno = local->op_errno; + goto unwind; + } + + STACK_WIND(frame, quota_mknod_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->mknod, loc, mode, rdev, umask, xdata); + return 0; + +unwind: + QUOTA_STACK_UNWIND(mknod, frame, -1, op_errno, NULL, NULL, NULL, NULL, + NULL); + return 0; +} int -quota_setxattr_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno) +quota_mknod(call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode, + dev_t rdev, mode_t umask, dict_t *xdata) { - dict_t *dict = NULL; + quota_priv_t *priv = NULL; + int32_t ret = -1; + quota_local_t *local = NULL; + call_stub_t *stub = NULL; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + QUOTA_WIND_FOR_INTERNAL_FOP(xdata, off); + + local = quota_local_new(); + if (local == NULL) { + goto err; + } + + frame->local = local; + + ret = loc_copy(&local->loc, loc); + if (ret) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "loc_copy failed"); + goto err; + } + + stub = fop_mknod_stub(frame, quota_mknod_helper, loc, mode, rdev, umask, + xdata); + if (stub == NULL) { + goto err; + } + + LOCK(&local->lock); + { + local->link_count = 1; + local->stub = stub; + local->delta = 0; + local->object_delta = 1; + } + UNLOCK(&local->lock); + + quota_check_limit(frame, loc->parent, this); + return 0; + +err: + QUOTA_STACK_UNWIND(mknod, frame, -1, ENOMEM, NULL, NULL, NULL, NULL, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->mknod, + loc, mode, rdev, umask, xdata); + return 0; +} - if (op_ret == -1) { - gf_log (this->name, GF_LOG_CRITICAL, - "failed to set the disk-usage value: %s", - strerror (op_errno)); - } +int +quota_setxattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int op_ret, int op_errno, dict_t *xdata) +{ + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + int ret = 0; + + if (op_ret < 0) { + goto out; + } + + local = frame->local; + if (!local) + goto out; + + ret = quota_inode_ctx_get(local->loc.inode, this, &ctx, 1); + if ((ret < 0) || (ctx == NULL)) { + op_errno = -1; + goto out; + } + + LOCK(&ctx->lock); + { + ctx->hard_lim = local->limit.hl; + ctx->soft_lim = local->limit.sl; + ctx->object_hard_lim = local->object_limit.hl; + ctx->object_soft_lim = local->object_limit.sl; + } + UNLOCK(&ctx->lock); + +out: + QUOTA_STACK_UNWIND(setxattr, frame, op_ret, op_errno, xdata); + return 0; +} - if (cookie) { - dict = (dict_t *) cookie; - dict_unref (dict); +int +quota_setxattr(call_frame_t *frame, xlator_t *this, loc_t *loc, dict_t *dict, + int flags, dict_t *xdata) +{ + quota_priv_t *priv = NULL; + int op_errno = EINVAL; + int op_ret = -1; + int64_t hard_lim = -1; + int64_t soft_lim = -1; + int64_t object_hard_limit = -1; + int64_t object_soft_limit = -1; + quota_local_t *local = NULL; + gf_boolean_t internal_fop = _gf_false; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + VALIDATE_OR_GOTO(frame, err); + VALIDATE_OR_GOTO(this, err); + VALIDATE_OR_GOTO(loc, err); + + if (xdata && dict_get_sizen(xdata, GLUSTERFS_INTERNAL_FOP_KEY)) + internal_fop = _gf_true; + + if (frame->root->pid >= 0 && internal_fop == _gf_false) { + GF_IF_INTERNAL_XATTR_GOTO("trusted.glusterfs.quota*", dict, op_errno, + err); + GF_IF_INTERNAL_XATTR_GOTO("trusted.pgfid*", dict, op_errno, err); + } + + quota_get_limits(this, dict, &hard_lim, &soft_lim, &object_hard_limit, + &object_soft_limit); + + if (hard_lim > 0 || object_hard_limit > 0) { + local = quota_local_new(); + if (local == NULL) { + op_errno = ENOMEM; + goto err; } + frame->local = local; + loc_copy(&local->loc, loc); + } + + if (hard_lim > 0) { + local->limit.hl = hard_lim; + local->limit.sl = soft_lim; + } + + if (object_hard_limit > 0) { + local->object_limit.hl = object_hard_limit; + local->object_limit.sl = object_soft_limit; + } + + STACK_WIND(frame, quota_setxattr_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->setxattr, loc, dict, flags, xdata); + return 0; +err: + QUOTA_STACK_UNWIND(setxattr, frame, op_ret, op_errno, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->setxattr, + loc, dict, flags, xdata); + return 0; +} - STACK_DESTROY (frame->root); - return 0; +int +quota_fsetxattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int op_ret, int op_errno, dict_t *xdata) +{ + quota_inode_ctx_t *ctx = NULL; + quota_local_t *local = NULL; + + if (op_ret < 0) + goto out; + + local = frame->local; + if (!local) + goto out; + + op_ret = quota_inode_ctx_get(local->loc.inode, this, &ctx, 1); + if ((op_ret < 0) || (ctx == NULL)) { + op_errno = ENOMEM; + goto out; + } + + LOCK(&ctx->lock); + { + ctx->hard_lim = local->limit.hl; + ctx->soft_lim = local->limit.sl; + ctx->object_hard_lim = local->object_limit.hl; + ctx->object_soft_lim = local->object_limit.sl; + } + UNLOCK(&ctx->lock); + +out: + QUOTA_STACK_UNWIND(fsetxattr, frame, op_ret, op_errno, xdata); + return 0; } +int +quota_fsetxattr(call_frame_t *frame, xlator_t *this, fd_t *fd, dict_t *dict, + int flags, dict_t *xdata) +{ + quota_priv_t *priv = NULL; + int32_t op_ret = -1; + int32_t op_errno = EINVAL; + quota_local_t *local = NULL; + int64_t hard_lim = -1; + int64_t soft_lim = -1; + int64_t object_hard_limit = -1; + int64_t object_soft_limit = -1; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + VALIDATE_OR_GOTO(frame, err); + VALIDATE_OR_GOTO(this, err); + VALIDATE_OR_GOTO(fd, err); + + if (0 <= frame->root->pid) { + GF_IF_INTERNAL_XATTR_GOTO("trusted.glusterfs.quota*", dict, op_errno, + err); + GF_IF_INTERNAL_XATTR_GOTO("trusted.pgfid*", dict, op_errno, err); + } + + quota_get_limits(this, dict, &hard_lim, &soft_lim, &object_hard_limit, + &object_soft_limit); + + if (hard_lim > 0 || object_hard_limit > 0) { + local = quota_local_new(); + if (local == NULL) { + op_errno = ENOMEM; + goto err; + } + frame->local = local; + local->loc.inode = inode_ref(fd->inode); + } + + if (hard_lim > 0) { + local->limit.hl = hard_lim; + local->limit.sl = soft_lim; + } + + if (object_hard_limit > 0) { + local->object_limit.hl = object_hard_limit; + local->object_limit.sl = object_soft_limit; + } + + STACK_WIND(frame, quota_fsetxattr_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->fsetxattr, fd, dict, flags, xdata); + return 0; +err: + QUOTA_STACK_UNWIND(fsetxattr, frame, op_ret, op_errno, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->fsetxattr, fd, dict, flags, xdata); + return 0; +} int -quota_statfs_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, struct statvfs *statvfs) +quota_removexattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, dict_t *xdata) { - struct quota_priv *priv = NULL; - uint64_t f_blocks = 0; - int64_t f_bfree = 0; - uint64_t f_bused = 0; + QUOTA_STACK_UNWIND(removexattr, frame, op_ret, op_errno, xdata); + return 0; +} +int +quota_removexattr(call_frame_t *frame, xlator_t *this, loc_t *loc, + const char *name, dict_t *xdata) +{ + quota_priv_t *priv = NULL; + int32_t op_errno = EINVAL; - priv = this->private; + priv = this->private; - if (op_ret != 0) - goto unwind; + WIND_IF_QUOTAOFF(priv->is_quota_on, off); - f_blocks = priv->disk_usage_limit / statvfs->f_frsize; - f_bused = priv->current_disk_usage / statvfs->f_frsize; + VALIDATE_OR_GOTO(this, err); - if (f_blocks && (f_blocks < statvfs->f_blocks)) - statvfs->f_blocks = f_blocks; + /* all quota xattrs can be cleaned up by doing setxattr on special key. + * Hence its ok that we don't allow removexattr on quota keys here. + */ + if (frame->root->pid >= 0) { + GF_IF_NATIVE_XATTR_GOTO("trusted.glusterfs.quota*", name, op_errno, + err); + GF_IF_NATIVE_XATTR_GOTO("trusted.pgfid*", name, op_errno, err); + } - f_bfree = (statvfs->f_blocks - f_bused); + VALIDATE_OR_GOTO(frame, err); + VALIDATE_OR_GOTO(loc, err); - if (f_bfree >= 0) - statvfs->f_bfree = statvfs->f_bavail = f_bfree; - else - statvfs->f_bfree = statvfs->f_bavail = 0; + STACK_WIND(frame, quota_removexattr_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->removexattr, loc, name, xdata); + return 0; -unwind: - STACK_UNWIND_STRICT (statfs, frame, op_ret, op_errno, statvfs); - return 0; -} +err: + QUOTA_STACK_UNWIND(removexattr, frame, -1, op_errno, NULL); + return 0; +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->removexattr, loc, name, xdata); + return 0; +} int -quota_statfs (call_frame_t *frame, xlator_t *this, loc_t *loc) +quota_fremovexattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, dict_t *xdata) { - STACK_WIND (frame, quota_statfs_cbk, - FIRST_CHILD (this), FIRST_CHILD (this)->fops->statfs, loc); + QUOTA_STACK_UNWIND(fremovexattr, frame, op_ret, op_errno, xdata); + return 0; +} - return 0; +int +quota_fremovexattr(call_frame_t *frame, xlator_t *this, fd_t *fd, + const char *name, dict_t *xdata) +{ + quota_priv_t *priv = NULL; + int32_t op_ret = -1; + int32_t op_errno = EINVAL; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + VALIDATE_OR_GOTO(frame, err); + VALIDATE_OR_GOTO(this, err); + VALIDATE_OR_GOTO(fd, err); + + if (frame->root->pid >= 0) { + GF_IF_NATIVE_XATTR_GOTO("trusted.glusterfs.quota*", name, op_errno, + err); + GF_IF_NATIVE_XATTR_GOTO("trusted.pgfid*", name, op_errno, err); + } + STACK_WIND(frame, quota_fremovexattr_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->fremovexattr, fd, name, xdata); + return 0; +err: + QUOTA_STACK_UNWIND(fremovexattr, frame, op_ret, op_errno, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->fremovexattr, fd, name, xdata); + return 0; } +int32_t +quota_statfs_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct statvfs *buf, + dict_t *xdata) +{ + inode_t *inode = NULL; + uint64_t value = 0; + int64_t usage = -1; + int64_t avail = -1; + int64_t blocks = 0; + quota_inode_ctx_t *ctx = NULL; + int ret = 0; + + inode = cookie; + + /* This fop will fail mostly in case of client disconnect, + * which is already logged. Hence, not logging here */ + if (op_ret == -1) + goto unwind; + /* + * We should never get here unless quota_statfs (below) sent us a + * cookie, and it would only do so if the value was non-NULL. This + * check is therefore just routine defensive coding. + */ + + GF_VALIDATE_OR_GOTO("quota", inode, unwind); + + inode_ctx_get(inode, this, &value); + ctx = (quota_inode_ctx_t *)(unsigned long)value; + if (!ctx || ctx->hard_lim <= 0) + goto unwind; + + { /* statfs is adjusted in this code block */ + usage = (ctx->size) / buf->f_bsize; + + blocks = ctx->hard_lim / buf->f_bsize; + buf->f_blocks = blocks; + + avail = buf->f_blocks - usage; + avail = max(avail, 0); + + buf->f_bfree = avail; + /* + * We have to assume that the total assigned quota + * won't cause us to dip into the reserved space, + * because dealing with the overcommitted cases is + * just too hairy (especially when different bricks + * might be using different reserved percentages and + * such). + */ + buf->f_bavail = buf->f_bfree; + } + + xdata = xdata ? dict_ref(xdata) : dict_new(); + if (!xdata) + goto unwind; + + ret = dict_set_int8(xdata, "quota-deem-statfs", 1); + if (-1 == ret) + gf_msg(this->name, GF_LOG_ERROR, ENOMEM, Q_MSG_ENOMEM, + "Dict set failed, deem-statfs option may " + "have no effect"); -int -quota_getxattr_cbk (call_frame_t *frame, void *cookie, xlator_t *this, - int32_t op_ret, int32_t op_errno, dict_t *value) +unwind: + QUOTA_STACK_UNWIND(statfs, frame, op_ret, op_errno, buf, xdata); + + if (xdata) + dict_unref(xdata); + + return 0; +} + +int32_t +quota_statfs_helper(call_frame_t *frame, xlator_t *this, loc_t *loc, + dict_t *xdata) { - data_t *data = NULL; - struct quota_priv *priv = this->private; - - if (op_ret >= 0) { - data = dict_get (value, "trusted.glusterfs-quota-du"); - if (data) { - LOCK (&priv->lock); - { - priv->current_disk_usage = data_to_uint64 (data); - } - UNLOCK (&priv->lock); + quota_local_t *local = frame->local; + int op_errno = EINVAL; + + GF_VALIDATE_OR_GOTO("quota", local, err); - return 0; - } - } + if (-1 == local->op_ret) { + op_errno = local->op_errno; + goto err; + } - STACK_DESTROY (frame->root); + STACK_WIND_COOKIE(frame, quota_statfs_cbk, local->inode, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->statfs, loc, xdata); + return 0; +err: + QUOTA_STACK_UNWIND(statfs, frame, -1, op_errno, NULL, NULL); - return 0; + return 0; } +int32_t +quota_statfs_validate_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, inode_t *inode, + struct iatt *buf, dict_t *xdata, + struct iatt *postparent) +{ + quota_local_t *local = NULL; + int32_t ret = 0; + quota_inode_ctx_t *ctx = NULL; + uint64_t value = 0; + quota_meta_t size = { + 0, + }; + + local = frame->local; + + if (op_ret < 0) + goto resume; + + GF_ASSERT(local); + GF_ASSERT(frame); + GF_VALIDATE_OR_GOTO_WITH_ERROR("quota", this, resume, op_errno, EINVAL); + GF_VALIDATE_OR_GOTO_WITH_ERROR(this->name, xdata, resume, op_errno, EINVAL); + + ret = inode_ctx_get(local->validate_loc.inode, this, &value); + + ctx = (quota_inode_ctx_t *)(unsigned long)value; + if ((ret == -1) || (ctx == NULL)) { + gf_msg(this->name, GF_LOG_WARNING, EINVAL, Q_MSG_INODE_CTX_GET_FAILED, + "quota context is not present in inode (gfid:%s)", + uuid_utoa(local->validate_loc.inode->gfid)); + op_errno = EINVAL; + goto resume; + } + + ret = quota_dict_get_meta(xdata, QUOTA_SIZE_KEY, SLEN(QUOTA_SIZE_KEY), + &size); + if (ret == -1) { + gf_msg(this->name, GF_LOG_WARNING, EINVAL, Q_MSG_SIZE_KEY_MISSING, + "size key not present in " + "dict"); + op_errno = EINVAL; + } + + LOCK(&ctx->lock); + { + ctx->size = size.size; + ctx->validate_time = gf_time(); + ctx->file_count = size.file_count; + ctx->dir_count = size.dir_count; + } + UNLOCK(&ctx->lock); + +resume: + local->op_errno = op_errno; + quota_link_count_decrement(frame); + return 0; +} void -gf_quota_get_disk_usage (xlator_t *this) +quota_get_limit_dir_continuation(struct list_head *parents, inode_t *inode, + int32_t op_ret, int32_t op_errno, void *data) { - call_frame_t *frame = NULL; - call_pool_t *pool = NULL; - loc_t loc; + call_frame_t *frame = NULL; + xlator_t *this = NULL; + quota_dentry_t *entry = NULL; + inode_t *parent = NULL; + + frame = data; + this = THIS; + + if ((op_ret < 0) || list_empty(parents)) { + if (op_ret >= 0) { + gf_msg(this->name, GF_LOG_WARNING, EIO, Q_MSG_ANCESTRY_BUILD_FAILED, + "Couldn't build ancestry for inode (gfid:%s). " + "Without knowing ancestors till root, quota " + "cannot be enforced. " + "Hence, failing fop with EIO", + uuid_utoa(inode->gfid)); + op_errno = EIO; + } + + quota_handle_validate_error(frame, -1, op_errno); + goto out; + } + + entry = list_entry(parents, quota_dentry_t, next); + parent = inode_find(inode->table, entry->par); - pool = this->ctx->pool; - frame = create_frame (this, pool); - build_root_loc (this, &loc); + quota_get_limit_dir(frame, parent, this); - STACK_WIND (frame, quota_getxattr_cbk, - this->children->xlator, - this->children->xlator->fops->getxattr, - &loc, - "trusted.glusterfs-quota-du"); - return ; + inode_unref(parent); +out: + return; } +void +quota_statfs_continue(call_frame_t *frame, xlator_t *this, inode_t *inode) +{ + quota_local_t *local = frame->local; + int ret = -1; + + LOCK(&local->lock); + { + local->inode = inode_ref(inode); + } + UNLOCK(&local->lock); + + ret = quota_validate(frame, local->inode, this, quota_statfs_validate_cbk); + if (0 > ret) + quota_handle_validate_error(frame, -1, -ret); +} void -gf_quota_cache_sync (xlator_t *this) +quota_get_limit_dir(call_frame_t *frame, inode_t *cur_inode, xlator_t *this) { - struct quota_priv *priv = NULL; - call_frame_t *frame = NULL; - dict_t *dict = get_new_dict (); - loc_t loc; + inode_t *inode = NULL; + inode_t *parent = NULL; + uint64_t value = 0; + quota_inode_ctx_t *ctx = NULL; + quota_local_t *local = frame->local; + + if (!cur_inode) + goto out; + + inode = inode_ref(cur_inode); + while (inode) { + value = 0; + inode_ctx_get(inode, this, &value); + + if (value) { + ctx = (quota_inode_ctx_t *)(unsigned long)value; + if (ctx->hard_lim > 0) + break; + } + + if (__is_root_gfid(inode->gfid)) + goto off; + + parent = inode_parent(inode, 0, NULL); + if (!parent) { + (void)quota_build_ancestry(inode, quota_get_limit_dir_continuation, + frame); + goto out; + } + inode_unref(inode); + inode = parent; + } - priv = this->private; - build_root_loc (this, &loc); + quota_statfs_continue(frame, this, inode); + inode_unref(inode); + return; - frame = create_frame (this, this->ctx->pool); - dict_set (dict, "trusted.glusterfs-quota-du", - data_from_uint64 (priv->current_disk_usage)); +off: + gf_msg_debug(this->name, 0, "No limit set on the inode or it's parents."); - dict_ref (dict); + QUOTA_STACK_WIND_TAIL(frame, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->statfs, &local->loc, + local->xdata); +out: + inode_unref(inode); - STACK_WIND_COOKIE (frame, quota_setxattr_cbk, - (void *) (dict_t *) dict, - this->children->xlator, - this->children->xlator->fops->setxattr, - &loc, dict, 0); + return; } +int32_t +quota_statfs(call_frame_t *frame, xlator_t *this, loc_t *loc, dict_t *xdata) +{ + int op_errno = 0; + int ret = -1; + int8_t ignore_deem_statfs = 0; + quota_priv_t *priv = NULL; + quota_local_t *local = NULL; + call_stub_t *stub = NULL; + + priv = this->private; + GF_ASSERT(loc); + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + ret = dict_get_int8(xdata, GF_INTERNAL_IGNORE_DEEM_STATFS, + &ignore_deem_statfs); + ret = 0; + + if (ignore_deem_statfs) + goto off; + + if (priv->consider_statfs && loc->inode) { + local = quota_local_new(); + if (!local) { + op_errno = ENOMEM; + goto err; + } + frame->local = local; + + ret = loc_copy(&local->loc, loc); + if (-1 == ret) { + op_errno = ENOMEM; + goto err; + } + + if (xdata) + local->xdata = dict_ref(xdata); + + stub = fop_statfs_stub(frame, quota_statfs_helper, &local->loc, + local->xdata); + if (!stub) { + op_errno = ENOMEM; + goto err; + } + + LOCK(&local->lock); + { + local->link_count = 1; + local->stub = stub; + } + UNLOCK(&local->lock); + + quota_get_limit_dir(frame, loc->inode, this); + + return 0; + } + + /* + * We have to make sure that we never get to quota_statfs_cbk + * with a cookie that points to something other than an inode, + * which is exactly what would happen with STACK_UNWIND using + * that as a callback. Therefore, use default_statfs_cbk in + * this case instead. + * + * Also if the option deem-statfs is not set to "on" don't + * bother calculating quota limit on / in statfs_cbk. + */ + if (priv->consider_statfs) + gf_log(this->name, GF_LOG_ERROR, + "Missing inode, can't adjust for quota"); + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->statfs, + loc, xdata); + return 0; + +err: + QUOTA_STACK_UNWIND(statfs, frame, -1, op_errno, NULL, NULL); + + return 0; +} int -quota_release (xlator_t *this, fd_t *fd) +quota_readdirp_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int op_ret, int op_errno, gf_dirent_t *entries, + dict_t *xdata) { - gf_quota_cache_sync (this); + gf_dirent_t *entry = NULL; + quota_local_t *local = NULL; + loc_t loc = { + 0, + }; + + if (op_ret <= 0) + goto unwind; + + local = frame->local; + + list_for_each_entry(entry, &entries->list, list) + { + if ((strcmp(entry->d_name, ".") == 0) || + (strcmp(entry->d_name, "..") == 0) || entry->inode == NULL) + continue; + + gf_uuid_copy(loc.gfid, entry->d_stat.ia_gfid); + loc.inode = inode_ref(entry->inode); + loc.parent = inode_ref(local->loc.inode); + gf_uuid_copy(loc.pargfid, loc.parent->gfid); + loc.name = entry->d_name; + + quota_fill_inodectx(this, entry->inode, entry->dict, &loc, + &entry->d_stat, &op_errno); + + loc_wipe(&loc); + } + +unwind: + QUOTA_STACK_UNWIND(readdirp, frame, op_ret, op_errno, entries, xdata); + + return 0; +} + +int +quota_readdirp(call_frame_t *frame, xlator_t *this, fd_t *fd, size_t size, + off_t offset, dict_t *dict) +{ + quota_priv_t *priv = NULL; + int ret = 0; + gf_boolean_t new_dict = _gf_false; + quota_local_t *local = NULL; + + priv = this->private; + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); - return 0; + local = quota_local_new(); + + if (local == NULL) { + goto err; + } + + frame->local = local; + + local->loc.inode = inode_ref(fd->inode); + + if (dict == NULL) { + dict = dict_new(); + new_dict = _gf_true; + } + + if (dict) { + ret = dict_set_int8(dict, QUOTA_LIMIT_KEY, 1); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "dict set of key for hard-limit"); + goto err; + } + } + + if (dict) { + ret = dict_set_int8(dict, QUOTA_LIMIT_OBJECTS_KEY, 1); + if (ret < 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "dict set of key for hard-limit " + "failed"); + goto err; + } + } + + STACK_WIND(frame, quota_readdirp_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->readdirp, fd, size, offset, dict); + + if (new_dict) { + dict_unref(dict); + } + + return 0; +err: + STACK_UNWIND_STRICT(readdirp, frame, -1, EINVAL, NULL, NULL); + + if (new_dict) { + dict_unref(dict); + } + + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->readdirp, + fd, size, offset, dict); + return 0; } +int32_t +quota_fallocate_cbk(call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) +{ + int32_t ret = 0; + uint64_t ctx_int = 0; + quota_inode_ctx_t *ctx = NULL; + quota_local_t *local = NULL; + + local = frame->local; + + if ((op_ret < 0) || (local == NULL)) { + goto out; + } + + ret = inode_ctx_get(local->loc.inode, this, &ctx_int); + if (ret) { + gf_msg(this->name, GF_LOG_WARNING, 0, Q_MSG_INODE_CTX_GET_FAILED, + "%s: failed to get the context", local->loc.path); + goto out; + } + + ctx = (quota_inode_ctx_t *)(unsigned long)ctx_int; + + if (ctx == NULL) { + gf_msg(this->name, GF_LOG_WARNING, 0, Q_MSG_INODE_CTX_GET_FAILED, + "quota context not set in %s (gfid:%s)", local->loc.path, + uuid_utoa(local->loc.inode->gfid)); + goto out; + } + + LOCK(&ctx->lock); + { + ctx->buf = *postbuf; + } + UNLOCK(&ctx->lock); + +out: + QUOTA_STACK_UNWIND(fallocate, frame, op_ret, op_errno, prebuf, postbuf, + xdata); + + return 0; +} -/* notify */ int32_t -notify (xlator_t *this, - int32_t event, - void *data, - ...) -{ - struct quota_priv *priv = this->private; - - switch (event) - { - case GF_EVENT_CHILD_UP: - if (priv->only_first_time) { - priv->only_first_time = 0; - if (priv->disk_usage_limit) { - gf_quota_get_disk_usage (this); - } - } - default: - default_notify (this, event, data); - break; - } - - return 0; -} - - -int32_t -init (xlator_t *this) -{ - int ret = 0; - data_t *data = NULL; - struct quota_priv *_private = NULL; - - if (!this->children || this->children->next) { - gf_log (this->name, GF_LOG_ERROR, - "FATAL: quota should have exactly one child"); - return -1; - } - - if (!this->parents) { - gf_log (this->name, GF_LOG_WARNING, - "dangling volume. check volfile "); - } - - _private = CALLOC (1, sizeof (struct quota_priv)); - _private->disk_usage_limit = 0; - data = dict_get (this->options, "disk-usage-limit"); - if (data) { - if (gf_string2bytesize (data->data, &_private->disk_usage_limit) != 0) { - gf_log (this->name, GF_LOG_ERROR, - "invalid number '%s' for disk-usage limit", data->data); - ret = -1; - goto out; - } +quota_fallocate_helper(call_frame_t *frame, xlator_t *this, fd_t *fd, + int32_t mode, off_t offset, size_t len, dict_t *xdata) +{ + quota_local_t *local = NULL; + int32_t op_errno = EINVAL; + + local = frame->local; + + GF_VALIDATE_OR_GOTO("quota", local, unwind); + + if (local->op_ret == -1) { + op_errno = local->op_errno; + if (op_errno == ENOENT || op_errno == ESTALE) { + /* We may get ENOENT/ESTALE in case of below scenario + * fd = open file.txt + * unlink file.txt + * fallocate on fd + * Here build_ancestry can fail as the file is removed. + * For now ignore ENOENT/ESTALE on active fd + * We need to re-visit this code once we understand + * how other file-system behave in this scenario + */ + gf_msg_debug(this->name, 0, + "quota enforcer failed " + "with ENOENT/ESTALE on %s, cannot check " + "quota limits and allowing fallocate", + uuid_utoa(fd->inode->gfid)); + } else { + goto unwind; + } + } - LOCK_INIT (&_private->lock); - _private->current_disk_usage = 0; - } - - _private->min_free_disk_limit = 0; - data = dict_get (this->options, "min-free-disk-limit"); - if (data) { - if (gf_string2percent (data->data, &_private->min_free_disk_limit) != 0) { - gf_log (this->name, GF_LOG_ERROR, - "invalid percent '%s' for min-free-disk limit", data->data); - ret = -1; - goto out; + STACK_WIND(frame, quota_fallocate_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->fallocate, fd, mode, offset, len, + xdata); + return 0; + +unwind: + QUOTA_STACK_UNWIND(fallocate, frame, -1, op_errno, NULL, NULL, NULL); + return 0; +} + +int32_t +quota_fallocate(call_frame_t *frame, xlator_t *this, fd_t *fd, int32_t mode, + off_t offset, size_t len, dict_t *xdata) +{ + int32_t op_errno = EINVAL; + int32_t parents = 0; + int32_t fail_count = 0; + quota_local_t *local = NULL; + quota_inode_ctx_t *ctx = NULL; + quota_priv_t *priv = NULL; + quota_dentry_t *dentry = NULL; + quota_dentry_t *tmp = NULL; + call_stub_t *stub = NULL; + struct list_head head = { + 0, + }; + inode_t *par_inode = NULL; + + priv = this->private; + GF_VALIDATE_OR_GOTO(this->name, priv, unwind); + + WIND_IF_QUOTAOFF(priv->is_quota_on, off); + + INIT_LIST_HEAD(&head); + + GF_ASSERT(frame); + GF_VALIDATE_OR_GOTO("quota", this, unwind); + GF_VALIDATE_OR_GOTO(this->name, fd, unwind); + + local = quota_local_new(); + if (local == NULL) { + goto unwind; + } + + frame->local = local; + local->loc.inode = inode_ref(fd->inode); + + (void)quota_inode_ctx_get(fd->inode, this, &ctx, 0); + if (ctx == NULL) { + gf_msg_debug(this->name, 0, + "quota context is NULL on inode" + " (%s). If quota is not enabled recently and " + "crawler has finished crawling, its an error", + uuid_utoa(local->loc.inode->gfid)); + } + + stub = fop_fallocate_stub(frame, quota_fallocate_helper, fd, mode, offset, + len, xdata); + if (stub == NULL) { + op_errno = ENOMEM; + goto unwind; + } + + priv = this->private; + GF_VALIDATE_OR_GOTO(this->name, priv, unwind); + + parents = quota_add_parents_from_ctx(ctx, &head); + if (parents == -1) { + op_errno = errno; + goto unwind; + } + + /* + * Note that by using len as the delta we're assuming the range from + * offset to offset+len has not already been allocated. This can result + * in ENOSPC errors attempting to allocate an already allocated range. + */ + local->delta = len; + local->object_delta = 0; + local->stub = stub; + local->link_count = parents; + + if (parents == 0) { + local->link_count = 1; + quota_check_limit(frame, fd->inode, this); + } else { + list_for_each_entry_safe(dentry, tmp, &head, next) + { + par_inode = do_quota_check_limit(frame, fd->inode, this, dentry, + _gf_false); + if (par_inode == NULL) { + /* remove stale entry from inode_ctx */ + quota_dentry_del(ctx, dentry->name, dentry->par); + parents--; + fail_count++; + } else { + inode_unref(par_inode); + } + __quota_dentry_free(dentry); + } + + if (parents == 0) { + LOCK(&local->lock); + { + local->link_count++; + } + UNLOCK(&local->lock); + quota_check_limit(frame, fd->inode, this); + } + + while (fail_count != 0) { + quota_link_count_decrement(frame); + fail_count--; + } + } + + return 0; + +unwind: + QUOTA_STACK_UNWIND(fallocate, frame, -1, op_errno, NULL, NULL, NULL); + return 0; + +off: + STACK_WIND_TAIL(frame, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->fallocate, fd, mode, offset, len, + xdata); + return 0; +} + +void +quota_log_helper(char **usage_str, int64_t cur_size, inode_t *inode, + char **path, time_t *cur_time) +{ + xlator_t *this = THIS; + + if (!usage_str || !inode || !path || !cur_time) { + gf_log(this->name, GF_LOG_ERROR, "Received null argument"); + return; + } + + *usage_str = gf_uint64_2human_readable(cur_size); + if (!(*usage_str)) + gf_msg(this->name, GF_LOG_ERROR, ENOMEM, Q_MSG_ENOMEM, + "integer to string conversion failed Reason" + ":\"Cannot allocate memory\""); + + inode_path(inode, NULL, path); + if (!(*path)) + *path = uuid_utoa(inode->gfid); + + *cur_time = gf_time(); +} + +/* Logs if + * i. Usage crossed soft limit + * ii. Usage above soft limit and alert-time elapsed + */ +void +quota_log_usage(xlator_t *this, quota_inode_ctx_t *ctx, inode_t *inode, + int64_t delta) +{ + time_t cur_time = 0; + char *usage_str = NULL; + char *path = NULL; + int64_t cur_size = 0; + quota_priv_t *priv = NULL; + + priv = this->private; + cur_size = ctx->size + delta; + + if ((ctx->soft_lim <= 0) || cur_size < ctx->soft_lim) + return; + + /* Usage crossed/reached soft limit */ + if (DID_REACH_LIMIT(ctx->soft_lim, ctx->size, cur_size)) { + quota_log_helper(&usage_str, cur_size, inode, &path, &cur_time); + + gf_msg(this->name, GF_LOG_ALERT, 0, Q_MSG_CROSSED_SOFT_LIMIT, + "Usage crossed soft limit: " + "%s used by %s", + usage_str, path); + + gf_event(EVENT_QUOTA_CROSSED_SOFT_LIMIT, + "Usage=%s;volume=%s;" + "path=%s", + usage_str, priv->volume_uuid, path); + + ctx->prev_log_time = cur_time; + + } + /* Usage is above soft limit */ + else if (cur_size > ctx->soft_lim && + quota_timeout(ctx->prev_log_time, priv->log_timeout)) { + quota_log_helper(&usage_str, cur_size, inode, &path, &cur_time); + + gf_msg(this->name, GF_LOG_ALERT, 0, Q_MSG_CROSSED_SOFT_LIMIT, + "Usage is above soft limit: %s used by %s", usage_str, path); + + gf_event(EVENT_QUOTA_CROSSED_SOFT_LIMIT, + "Usage=%s;volume=%s;" + "path=%s", + usage_str, priv->volume_uuid, path); + + ctx->prev_log_time = cur_time; + } + + if (path) + GF_FREE(path); + + if (usage_str) + GF_FREE(usage_str); +} + +int32_t +mem_acct_init(xlator_t *this) +{ + int ret = -1; + + if (!this) + return ret; + + ret = xlator_mem_acct_init(this, gf_quota_mt_end + 1); + + if (ret != 0) { + gf_msg(this->name, GF_LOG_WARNING, ENOMEM, Q_MSG_ENOMEM, + "Memory accounting init failed"); + return ret; + } + + return ret; +} + +int32_t +quota_forget(xlator_t *this, inode_t *inode) +{ + int32_t ret = 0; + uint64_t ctx_int = 0; + quota_inode_ctx_t *ctx = NULL; + quota_dentry_t *dentry = NULL, *tmp; + + ret = inode_ctx_del(inode, this, &ctx_int); + + if (ret < 0) { + return 0; + } + + ctx = (quota_inode_ctx_t *)(long)ctx_int; + + LOCK(&ctx->lock); + { + list_for_each_entry_safe(dentry, tmp, &ctx->parents, next) + { + __quota_dentry_free(dentry); + } + } + UNLOCK(&ctx->lock); + + LOCK_DESTROY(&ctx->lock); + + GF_FREE(ctx); + + return 0; +} + +int +notify(xlator_t *this, int event, void *data, ...) +{ + quota_priv_t *priv = NULL; + int ret = 0; + rpc_clnt_t *rpc = NULL; + gf_boolean_t conn_status = _gf_true; + xlator_t *victim = data; + + priv = this->private; + if (!priv || !priv->is_quota_on) + goto out; + + if (event == GF_EVENT_PARENT_DOWN) { + rpc = priv->rpc_clnt; + if (rpc) { + rpc_clnt_disable(rpc); + pthread_mutex_lock(&priv->conn_mutex); + { + conn_status = priv->conn_status; + while (conn_status) { + (void)pthread_cond_wait(&priv->conn_cond, + &priv->conn_mutex); + conn_status = priv->conn_status; } - _private->refresh_interval = 20; /* 20seconds is default */ - data = dict_get (this->options, "refresh-interval"); - if (data) { - if (gf_string2time (data->data, - &_private->refresh_interval)!= 0) { - gf_log (this->name, GF_LOG_ERROR, - "invalid time '%s' for refresh " - "interval", data->data); - ret = -1; - goto out; - } - } + } + pthread_mutex_unlock(&priv->conn_mutex); + gf_log(this->name, GF_LOG_INFO, + "Notify GF_EVENT_PARENT_DOWN for brick %s", victim->name); + } + } + +out: + ret = default_notify(this, event, data); + return ret; +} + +int32_t +init(xlator_t *this) +{ + int32_t ret = -1; + quota_priv_t *priv = NULL; + rpc_clnt_t *rpc = NULL; + + if ((this->children == NULL) || this->children->next) { + gf_msg(this->name, GF_LOG_ERROR, 0, Q_MSG_INVALID_VOLFILE, + "FATAL: quota (%s) not configured with " + "exactly one child", + this->name); + return -1; + } + + if (this->parents == NULL) { + gf_msg(this->name, GF_LOG_WARNING, 0, Q_MSG_INVALID_VOLFILE, + "dangling volume. check volfile"); + } + + QUOTA_ALLOC_OR_GOTO(priv, quota_priv_t, err); + + LOCK_INIT(&priv->lock); + + this->private = priv; + + GF_OPTION_INIT("deem-statfs", priv->consider_statfs, bool, err); + GF_OPTION_INIT("server-quota", priv->is_quota_on, bool, err); + GF_OPTION_INIT("default-soft-limit", priv->default_soft_lim, percent, err); + GF_OPTION_INIT("soft-timeout", priv->soft_timeout, time, err); + GF_OPTION_INIT("hard-timeout", priv->hard_timeout, time, err); + GF_OPTION_INIT("alert-time", priv->log_timeout, time, err); + GF_OPTION_INIT("volume-uuid", priv->volume_uuid, str, err); + + this->local_pool = mem_pool_new(quota_local_t, 64); + if (!this->local_pool) { + ret = -1; + gf_msg(this->name, GF_LOG_ERROR, ENOMEM, Q_MSG_ENOMEM, + "failed to create local_t's memory pool"); + goto err; + } + + pthread_mutex_init(&priv->conn_mutex, NULL); + pthread_cond_init(&priv->conn_cond, NULL); + priv->conn_status = _gf_false; + + if (priv->is_quota_on) { + rpc = quota_enforcer_init(this, this->options); + if (rpc == NULL) { + ret = -1; + gf_msg(this->name, GF_LOG_WARNING, 0, + Q_MSG_QUOTA_ENFORCER_RPC_INIT_FAILED, + "quota enforcer rpc init failed"); + goto err; } - _private->only_first_time = 1; - this->private = (void *)_private; - ret = 0; - out: - return ret; + LOCK(&priv->lock); + { + priv->rpc_clnt = rpc; + } + UNLOCK(&priv->lock); + } + + ret = 0; +err: + return ret; } -void -fini (xlator_t *this) +int +reconfigure(xlator_t *this, dict_t *options) { - struct quota_priv *_private = this->private; + int32_t ret = -1; + quota_priv_t *priv = NULL; + gf_boolean_t quota_on = _gf_false; + rpc_clnt_t *rpc = NULL; + + priv = this->private; + + GF_OPTION_RECONF("deem-statfs", priv->consider_statfs, options, bool, out); + GF_OPTION_RECONF("server-quota", quota_on, options, bool, out); + GF_OPTION_RECONF("default-soft-limit", priv->default_soft_lim, options, + percent, out); + GF_OPTION_RECONF("alert-time", priv->log_timeout, options, time, out); + GF_OPTION_RECONF("soft-timeout", priv->soft_timeout, options, time, out); + GF_OPTION_RECONF("hard-timeout", priv->hard_timeout, options, time, out); + + if (quota_on) { + priv->rpc_clnt = quota_enforcer_init(this, this->options); + if (priv->rpc_clnt == NULL) { + ret = -1; + gf_msg(this->name, GF_LOG_WARNING, 0, + Q_MSG_QUOTA_ENFORCER_RPC_INIT_FAILED, + "quota enforcer rpc init failed"); + goto out; + } + + } else { + LOCK(&priv->lock); + { + rpc = priv->rpc_clnt; + priv->rpc_clnt = NULL; + } + UNLOCK(&priv->lock); + + if (rpc != NULL) { + // Quotad is shutdown when there is no started volume + // which has quota enabled. So, we should disable the + // enforcer client when quota is disabled on a volume, + // to avoid spurious reconnect attempts to a service + // (quotad), that is known to be down. + rpc_clnt_unref(rpc); + } + } + + priv->is_quota_on = quota_on; - if (_private) { - gf_quota_cache_sync (this); - this->private = NULL; - } - - return ; + ret = 0; +out: + return ret; +} + +int32_t +quota_priv_dump(xlator_t *this) +{ + quota_priv_t *priv = NULL; + int32_t ret = -1; + + GF_ASSERT(this); + + priv = this->private; + if (!priv) + goto out; + + gf_proc_dump_add_section("xlators.features.quota.priv"); + + ret = TRY_LOCK(&priv->lock); + if (ret) + goto out; + else { + gf_proc_dump_write("soft-timeout", "%u", priv->soft_timeout); + gf_proc_dump_write("hard-timeout", "%u", priv->hard_timeout); + gf_proc_dump_write("alert-time", "%u", priv->log_timeout); + gf_proc_dump_write("quota-on", "%d", priv->is_quota_on); + gf_proc_dump_write("statfs", "%d", priv->consider_statfs); + gf_proc_dump_write("volume-uuid", "%s", priv->volume_uuid); + gf_proc_dump_write("validation-count", "%" PRIu64, + priv->validation_count); + } + UNLOCK(&priv->lock); + +out: + return 0; +} + +void +fini(xlator_t *this) +{ + quota_priv_t *priv = NULL; + rpc_clnt_t *rpc = NULL; + + priv = this->private; + if (!priv) + return; + rpc = priv->rpc_clnt; + priv->rpc_clnt = NULL; + if (rpc) { + rpc_clnt_connection_cleanup(&rpc->conn); + rpc_clnt_unref(rpc); + } + + this->private = NULL; + LOCK_DESTROY(&priv->lock); + pthread_mutex_destroy(&priv->conn_mutex); + pthread_cond_destroy(&priv->conn_cond); + + GF_FREE(priv); + if (this->local_pool) { + mem_pool_destroy(this->local_pool); + this->local_pool = NULL; + } + return; } struct xlator_fops fops = { - .create = quota_create, - .open = quota_open, - .truncate = quota_truncate, - .ftruncate = quota_ftruncate, - .writev = quota_writev, - .unlink = quota_unlink, - .rmdir = quota_rmdir, - .mknod = quota_mknod, - .mkdir = quota_mkdir, - .symlink = quota_symlink, - .statfs = quota_statfs, + .statfs = quota_statfs, + .lookup = quota_lookup, + .writev = quota_writev, + .create = quota_create, + .mkdir = quota_mkdir, + .truncate = quota_truncate, + .ftruncate = quota_ftruncate, + .unlink = quota_unlink, + .symlink = quota_symlink, + .link = quota_link, + .rename = quota_rename, + .getxattr = quota_getxattr, + .fgetxattr = quota_fgetxattr, + .stat = quota_stat, + .fstat = quota_fstat, + .readlink = quota_readlink, + .readv = quota_readv, + .fsync = quota_fsync, + .setattr = quota_setattr, + .fsetattr = quota_fsetattr, + .mknod = quota_mknod, + .setxattr = quota_setxattr, + .fsetxattr = quota_fsetxattr, + .removexattr = quota_removexattr, + .fremovexattr = quota_fremovexattr, + .readdirp = quota_readdirp, + .fallocate = quota_fallocate, }; -struct xlator_mops mops = { -}; +struct xlator_cbks cbks = {.forget = quota_forget}; -struct xlator_cbks cbks = { - .release = quota_release +struct xlator_dumpops dumpops = { + .priv = quota_priv_dump, }; - struct volume_options options[] = { - { .key = {"min-free-disk-limit"}, - .type = GF_OPTION_TYPE_PERCENT - }, - { .key = {"refresh-interval"}, - .type = GF_OPTION_TYPE_TIME - }, - { .key = {"disk-usage-limit"}, - .type = GF_OPTION_TYPE_SIZET - }, - { .key = {NULL} }, + { + .key = {"enable"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "off", + .description = "enable is the volume option that can be used " + "to turn on quota.", + .op_version = {1}, + .flags = OPT_FLAG_SETTABLE | OPT_FLAG_DOC, + .level = OPT_STATUS_BASIC, + .tags = {}, + }, + { + .key = {"deem-statfs"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "on", + .description = "If set to on, it takes quota limits into" + " consideration while estimating fs size. (df command)" + " (Default is on).", + .op_version = {2}, + .flags = OPT_FLAG_SETTABLE | OPT_FLAG_DOC, + .tags = {}, + }, + { + .key = {"server-quota"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "off", + .description = "Skip the quota enforcement if the feature is" + " not turned on. This is not a user exposed option.", + .flags = OPT_FLAG_NONE, + }, + { + .key = {"default-soft-limit"}, + .type = GF_OPTION_TYPE_PERCENT, + .default_value = "80%", + .op_version = {3}, + .description = "Soft limit is expressed as a proportion of hard limit." + " Default-soft-limit is the proportion used when the " + " user does not supply any soft limit value.", + .flags = OPT_FLAG_SETTABLE | OPT_FLAG_DOC, + .tags = {}, + }, + { + .key = {"soft-timeout"}, + .type = GF_OPTION_TYPE_TIME, + .min = 0, + .max = 1800, + .default_value = "60", + .description = "quota caches the directory sizes on client. " + "soft-timeout indicates the timeout for the validity of" + " cache before soft-limit has been crossed.", + .op_version = {3}, + .flags = OPT_FLAG_SETTABLE | OPT_FLAG_DOC, + .tags = {}, + }, + { + .key = {"hard-timeout"}, + .type = GF_OPTION_TYPE_TIME, + .min = 0, + .max = 60, + .default_value = "5", + .description = "quota caches the directory sizes on client. " + "hard-timeout indicates the timeout for the validity of" + " cache after soft-limit has been crossed.", + .op_version = {3}, + .flags = OPT_FLAG_SETTABLE | OPT_FLAG_DOC, + .tags = {}, + }, + {.key = {"volume-uuid"}, + .type = GF_OPTION_TYPE_STR, + .default_value = "{{ volume.id }}", + .description = "uuid of the volume this brick is part of."}, + { + .key = {"alert-time"}, + .type = GF_OPTION_TYPE_TIME, + .min = 0, + .max = 7 * 86400, + .default_value = "86400", + .op_version = {3}, + .flags = OPT_FLAG_SETTABLE | OPT_FLAG_DOC, + .description = "Frequency of limit breach messages in log.", + .tags = {}, + }, + {.key = {NULL}}}; + +xlator_api_t xlator_api = { + .init = init, + .fini = fini, + .notify = notify, + .reconfigure = reconfigure, + .mem_acct_init = mem_acct_init, + .op_version = {1}, /* Present from the initial version */ + .fops = &fops, + .cbks = &cbks, + .options = options, + .identifier = "quota", + .category = GF_MAINTAINED, }; |
