/*
   Copyright (c) 2011-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.
*/
#include <glusterfs/common-utils.h>
#include "cli1-xdr.h"
#include "xdr-generic.h"
#include "glusterd.h"
#include "glusterd-op-sm.h"
#include "glusterd-store.h"
#include "glusterd-utils.h"
#include "glusterd-quotad-svc.h"
#include "glusterd-volgen.h"
#include "glusterd-messages.h"
#include <glusterfs/run.h>
#include <glusterfs/syscall.h>
#include <glusterfs/byte-order.h>
#include <glusterfs/compat-errno.h>
#include <glusterfs/quota-common-utils.h>
#include "glusterd-quota.h"

#include <sys/wait.h>
#include <dlfcn.h>

#ifndef _PATH_SETFATTR
#ifdef GF_LINUX_HOST_OS
#define _PATH_SETFATTR "setfattr"
#endif
#ifdef __NetBSD__
#define _PATH_SETFATTR "/usr/pkg/bin/setfattr"
#endif
#endif

/* Any negative pid to make it special client */
#define QUOTA_CRAWL_PID "-100"

#define GLUSTERFS_GET_QUOTA_LIMIT_MOUNT_PIDFILE(pidfile, volname)              \
    {                                                                          \
        snprintf(pidfile, PATH_MAX - 1,                                        \
                 DEFAULT_VAR_RUN_DIRECTORY "/%s_quota_limit.pid", volname);    \
    }

#define GLUSTERFS_GET_QUOTA_LIST_MOUNT_PIDFILE(pidfile, volname)               \
    {                                                                          \
        snprintf(pidfile, PATH_MAX - 1,                                        \
                 DEFAULT_VAR_RUN_DIRECTORY "/%s_quota_list.pid", volname);     \
    }

#define GLUSTERD_GET_QUOTA_CRAWL_PIDDIR(piddir, volinfo, type)                 \
    do {                                                                       \
        char _volpath[PATH_MAX] = {                                            \
            0,                                                                 \
        };                                                                     \
        int32_t _crawl_pid_len;                                                \
        GLUSTERD_GET_VOLUME_DIR(_volpath, volinfo, priv);                      \
        if (type == GF_QUOTA_OPTION_TYPE_ENABLE ||                             \
            type == GF_QUOTA_OPTION_TYPE_ENABLE_OBJECTS)                       \
            _crawl_pid_len = snprintf(piddir, PATH_MAX, "%s/run/quota/enable", \
                                      _volpath);                               \
        else                                                                   \
            _crawl_pid_len = snprintf(piddir, PATH_MAX,                        \
                                      "%s/run/quota/disable", _volpath);       \
        if ((_crawl_pid_len < 0) || (_crawl_pid_len >= PATH_MAX)) {            \
            piddir[0] = 0;                                                     \
        }                                                                      \
    } while (0)

#define GLUSTERD_GET_TMP_PATH(abspath, path)                                   \
    do {                                                                       \
        snprintf(abspath, sizeof(abspath) - 1,                                 \
                 DEFAULT_VAR_RUN_DIRECTORY "/tmp%s", path);                    \
    } while (0)

#define GLUSTERD_GET_QUOTA_LIST_MOUNT_PATH(abspath, volname, path)             \
    do {                                                                       \
        snprintf(abspath, sizeof(abspath) - 1,                                 \
                 DEFAULT_VAR_RUN_DIRECTORY "/%s_quota_list%s", volname, path); \
    } while (0)

const char *gd_quota_op_list[GF_QUOTA_OPTION_TYPE_MAX + 1] = {
    [GF_QUOTA_OPTION_TYPE_NONE] = "none",
    [GF_QUOTA_OPTION_TYPE_ENABLE] = "enable",
    [GF_QUOTA_OPTION_TYPE_DISABLE] = "disable",
    [GF_QUOTA_OPTION_TYPE_LIMIT_USAGE] = "limit-usage",
    [GF_QUOTA_OPTION_TYPE_REMOVE] = "remove",
    [GF_QUOTA_OPTION_TYPE_LIST] = "list",
    [GF_QUOTA_OPTION_TYPE_VERSION] = "version",
    [GF_QUOTA_OPTION_TYPE_ALERT_TIME] = "alert-time",
    [GF_QUOTA_OPTION_TYPE_SOFT_TIMEOUT] = "soft-timeout",
    [GF_QUOTA_OPTION_TYPE_HARD_TIMEOUT] = "hard-timeout",
    [GF_QUOTA_OPTION_TYPE_DEFAULT_SOFT_LIMIT] = "default-soft-limit",
    [GF_QUOTA_OPTION_TYPE_LIMIT_OBJECTS] = "limit-objects",
    [GF_QUOTA_OPTION_TYPE_LIST_OBJECTS] = "list-objects",
    [GF_QUOTA_OPTION_TYPE_REMOVE_OBJECTS] = "remove-objects",
    [GF_QUOTA_OPTION_TYPE_ENABLE_OBJECTS] = "enable-objects",
    [GF_QUOTA_OPTION_TYPE_UPGRADE] = "upgrade",
    [GF_QUOTA_OPTION_TYPE_MAX] = NULL};

gf_boolean_t
glusterd_is_quota_supported(int32_t type, char **op_errstr)
{
    xlator_t *this = THIS;
    glusterd_conf_t *conf = NULL;
    gf_boolean_t supported = _gf_false;

    conf = this->private;
    GF_VALIDATE_OR_GOTO(this->name, conf, out);

    if ((conf->op_version == GD_OP_VERSION_MIN) &&
        (type > GF_QUOTA_OPTION_TYPE_VERSION))
        goto out;

    if ((conf->op_version < GD_OP_VERSION_3_7_0) &&
        (type > GF_QUOTA_OPTION_TYPE_VERSION_OBJECTS))
        goto out;

    /* Quota Operations that change quota.conf shouldn't
     * be allowed as the quota.conf format changes in 3.7
     */
    if ((conf->op_version < GD_OP_VERSION_3_7_0) &&
        (type == GF_QUOTA_OPTION_TYPE_ENABLE ||
         type == GF_QUOTA_OPTION_TYPE_LIMIT_USAGE ||
         type == GF_QUOTA_OPTION_TYPE_REMOVE))
        goto out;

    /* Quota xattr version implemented in 3.7.6
     * quota-version is incremented when quota is enabled
     * Quota enable and disable performance enhancement has been done
     * in version 3.7.12.
     * so don't allow enabling/disabling quota in heterogeneous
     * cluster during upgrade
     */
    if (type == GF_QUOTA_OPTION_TYPE_ENABLE ||
        type == GF_QUOTA_OPTION_TYPE_ENABLE_OBJECTS ||
        type == GF_QUOTA_OPTION_TYPE_DISABLE) {
        if (conf->op_version < GD_OP_VERSION_3_7_12)
            goto out;
    }

    supported = _gf_true;

out:
    if (!supported && op_errstr != NULL && conf)
        gf_asprintf(op_errstr,
                    "Volume quota failed. The cluster is "
                    "operating at version %d. Quota command"
                    " %s is unavailable in this version.",
                    conf->op_version, gd_quota_op_list[type]);

    return supported;
}

int
__glusterd_handle_quota(rpcsvc_request_t *req)
{
    int32_t ret = -1;
    gf_cli_req cli_req = {{
        0,
    }};
    dict_t *dict = NULL;
    glusterd_op_t cli_op = GD_OP_QUOTA;
    char *volname = NULL;
    int32_t type = 0;
    char msg[2048] = {
        0,
    };
    xlator_t *this = THIS;
    glusterd_conf_t *conf = NULL;

    GF_ASSERT(req);
    conf = this->private;
    GF_ASSERT(conf);

    ret = xdr_to_generic(req->msg[0], &cli_req, (xdrproc_t)xdr_gf_cli_req);
    if (ret < 0) {
        // failed to decode msg;
        req->rpc_err = GARBAGE_ARGS;
        goto out;
    }

    if (cli_req.dict.dict_len) {
        /* Unserialize the dictionary */
        dict = dict_new();

        ret = dict_unserialize(cli_req.dict.dict_val, cli_req.dict.dict_len,
                               &dict);
        if (ret < 0) {
            gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_UNSERIALIZE_FAIL,
                   "failed to "
                   "unserialize req-buffer to dictionary");
            snprintf(msg, sizeof(msg),
                     "Unable to decode the "
                     "command");
            goto out;
        } else {
            dict->extra_stdfree = cli_req.dict.dict_val;
        }
    }

    ret = dict_get_strn(dict, "volname", SLEN("volname"), &volname);
    if (ret) {
        snprintf(msg, sizeof(msg), "Unable to get volume name");
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "Unable to get volume name, "
               "while handling quota command");
        goto out;
    }

    ret = dict_get_int32n(dict, "type", SLEN("type"), &type);
    if (ret) {
        snprintf(msg, sizeof(msg), "Unable to get type of command");
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "Unable to get type of cmd, "
               "while handling quota command");
        goto out;
    }

    if (!glusterd_is_quota_supported(type, NULL)) {
        snprintf(msg, sizeof(msg),
                 "Volume quota failed. The cluster "
                 "is operating at version %d. Quota command"
                 " %s is unavailable in this version.",
                 conf->op_version, gd_quota_op_list[type]);
        ret = -1;
        goto out;
    }

    ret = glusterd_op_begin_synctask(req, GD_OP_QUOTA, dict);

out:
    if (ret) {
        if (msg[0] == '\0')
            snprintf(msg, sizeof(msg), "Operation failed");
        ret = glusterd_op_send_cli_response(cli_op, ret, 0, req, dict, msg);
    }

    return ret;
}

int
glusterd_handle_quota(rpcsvc_request_t *req)
{
    return glusterd_big_locked_handler(req, __glusterd_handle_quota);
}

int32_t
glusterd_check_if_quota_trans_enabled(glusterd_volinfo_t *volinfo)
{
    int32_t ret = 0;
    int flag = _gf_false;

    flag = glusterd_volinfo_get_boolean(volinfo, VKEY_FEATURES_QUOTA);
    if (flag == -1) {
        gf_msg("glusterd", GF_LOG_ERROR, 0, GD_MSG_QUOTA_GET_STAT_FAIL,
               "failed to get the quota status");
        ret = -1;
        goto out;
    }

    if (flag == _gf_false) {
        ret = -1;
        goto out;
    }
    ret = 0;
out:
    return ret;
}

int32_t
_glusterd_quota_initiate_fs_crawl(glusterd_conf_t *priv,
                                  glusterd_volinfo_t *volinfo,
                                  glusterd_brickinfo_t *brick, int type,
                                  char *pid_dir)
{
    pid_t pid;
    int32_t ret = -1;
    int status = 0;
    char mountdir[PATH_MAX] = {
        0,
    };
    char logfile[PATH_MAX] = {
        0,
    };
    char brickpath[PATH_MAX] = {
        0,
    };
    char vol_id[PATH_MAX] = {
        0,
    };
    char pidfile[PATH_MAX] = {
        0,
    };
    runner_t runner = {0};
    char *volfileserver = NULL;
    FILE *pidfp = NULL;
    int32_t len = 0;
    xlator_t *this = THIS;

    GLUSTERD_GET_TMP_PATH(mountdir, "/");
    ret = sys_mkdir(mountdir, 0755);
    if (ret && errno != EEXIST) {
        gf_msg(this->name, GF_LOG_WARNING, errno, GD_MSG_MOUNT_REQ_FAIL,
               "failed to create temporary "
               "directory %s",
               mountdir);
        ret = -1;
        goto out;
    }

    strcat(mountdir, "mntXXXXXX");
    if (mkdtemp(mountdir) == NULL) {
        gf_msg(this->name, GF_LOG_WARNING, errno, GD_MSG_MOUNT_REQ_FAIL,
               "failed to create a temporary "
               "mount directory: %s",
               mountdir);
        ret = -1;
        goto out;
    }

    GLUSTERD_REMOVE_SLASH_FROM_PATH(brick->path, brickpath);
    len = snprintf(logfile, sizeof(logfile),
                   DEFAULT_QUOTA_CRAWL_LOG_DIRECTORY "/%s.log", brickpath);
    if ((len < 0) || (len >= sizeof(vol_id))) {
        ret = -1;
        goto out;
    }

    if (dict_get_strn(this->options, "transport.socket.bind-address",
                      SLEN("transport.socket.bind-address"),
                      &volfileserver) != 0)
        volfileserver = "localhost";

    len = snprintf(vol_id, sizeof(vol_id), "client_per_brick/%s.%s.%s.%s.vol",
                   volinfo->volname, "client", brick->hostname, brickpath);
    if ((len < 0) || (len >= sizeof(vol_id))) {
        ret = -1;
        goto out;
    }

    runinit(&runner);

    if (type == GF_QUOTA_OPTION_TYPE_ENABLE ||
        type == GF_QUOTA_OPTION_TYPE_ENABLE_OBJECTS)
        runner_add_args(&runner, SBIN_DIR "/glusterfs", "-s", volfileserver,
                        "--volfile-id", vol_id, "--use-readdirp=yes",
                        "--client-pid", QUOTA_CRAWL_PID, "-l", logfile,
                        mountdir, NULL);
    else
        runner_add_args(&runner, SBIN_DIR "/glusterfs", "-s", volfileserver,
                        "--volfile-id", vol_id, "--use-readdirp=no",
                        "--client-pid", QUOTA_CRAWL_PID, "-l", logfile,
                        mountdir, NULL);

    synclock_unlock(&priv->big_lock);
    ret = runner_run_reuse(&runner);
    synclock_lock(&priv->big_lock);
    if (ret == -1) {
        runner_log(&runner, "glusterd", GF_LOG_DEBUG, "command failed");
        runner_end(&runner);
        goto out;
    }
    runner_end(&runner);

    if ((pid = fork()) < 0) {
        gf_msg(this->name, GF_LOG_WARNING, 0, GD_MSG_FORK_FAIL,
               "fork from parent failed");
        gf_umount_lazy("glusterd", mountdir, 1);
        ret = -1;
        goto out;
    } else if (pid == 0) {  // first child
        /* fork one more to not hold back main process on
         * blocking call below
         */
        pid = fork();
        if (pid < 0) {
            gf_umount_lazy("glusterd", mountdir, 1);
            _exit(EXIT_FAILURE);
        } else if (pid > 0) {
            _exit(EXIT_SUCCESS);
        }

        ret = chdir(mountdir);
        if (ret == -1) {
            gf_msg(this->name, GF_LOG_WARNING, errno, GD_MSG_DIR_OP_FAILED,
                   "chdir %s failed", mountdir);
            gf_umount_lazy("glusterd", mountdir, 1);
            exit(EXIT_FAILURE);
        }
        runinit(&runner);

        if (type == GF_QUOTA_OPTION_TYPE_ENABLE ||
            type == GF_QUOTA_OPTION_TYPE_ENABLE_OBJECTS)
            runner_add_args(&runner, "/usr/bin/find", ".", "-exec",
                            "/usr/bin/stat", "{}", "\\", ";", NULL);

        else if (type == GF_QUOTA_OPTION_TYPE_DISABLE) {
#if defined(GF_DARWIN_HOST_OS)
            runner_add_args(
                &runner, "/usr/bin/find", ".", "-exec", "/usr/bin/xattr", "-w",
                VIRTUAL_QUOTA_XATTR_CLEANUP_KEY, "1", "{}", "\\", ";", NULL);
#elif defined(__FreeBSD__)
            runner_add_args(&runner, "/usr/bin/find", ".", "-exec",
                            "/usr/sbin/setextattr", EXTATTR_NAMESPACE_USER,
                            VIRTUAL_QUOTA_XATTR_CLEANUP_KEY, "1", "{}", "\\",
                            ";", NULL);
#else
            runner_add_args(&runner, "find", ".", "-exec", _PATH_SETFATTR, "-n",
                            VIRTUAL_QUOTA_XATTR_CLEANUP_KEY, "-v", "1", "{}",
                            "\\", ";", NULL);
#endif
        }

        if (runner_start(&runner) == -1) {
            gf_umount_lazy("glusterd", mountdir, 1);
            _exit(EXIT_FAILURE);
        }

        len = snprintf(pidfile, sizeof(pidfile), "%s/%s.pid", pid_dir,
                       brickpath);
        if ((len >= 0) && (len < sizeof(pidfile))) {
            pidfp = fopen(pidfile, "w");
            if (pidfp != NULL) {
                fprintf(pidfp, "%d\n", runner.chpid);
                fflush(pidfp);
                fclose(pidfp);
            }
        }

#ifndef GF_LINUX_HOST_OS
        runner_end(&runner); /* blocks in waitpid */
#endif
        gf_umount_lazy("glusterd", mountdir, 1);

        _exit(EXIT_SUCCESS);
    }
    ret = (waitpid(pid, &status, 0) == pid && WIFEXITED(status) &&
           WEXITSTATUS(status) == EXIT_SUCCESS)
              ? 0
              : -1;

out:
    return ret;
}

void
glusterd_stop_all_quota_crawl_service(glusterd_conf_t *priv,
                                      glusterd_volinfo_t *volinfo, int type)
{
    DIR *dir = NULL;
    struct dirent *entry = NULL;
    struct dirent scratch[2] = {
        {
            0,
        },
    };
    char pid_dir[PATH_MAX] = {
        0,
    };
    char pidfile[PATH_MAX] = {
        0,
    };
    int32_t len = 0;

    GLUSTERD_GET_QUOTA_CRAWL_PIDDIR(pid_dir, volinfo, type);

    dir = sys_opendir(pid_dir);
    if (dir == NULL)
        return;

    while ((entry = sys_readdir(dir, scratch))) {
        if (gf_irrelevant_entry(entry))
            continue;
        len = snprintf(pidfile, sizeof(pidfile), "%s/%s", pid_dir,
                       entry->d_name);
        if ((len >= 0) && (len < sizeof(pidfile))) {
            glusterd_service_stop_nolock("quota_crawl", pidfile, SIGKILL,
                                         _gf_true);
            sys_unlink(pidfile);
        }
    }
    sys_closedir(dir);
}

int32_t
glusterd_quota_initiate_fs_crawl(glusterd_conf_t *priv,
                                 glusterd_volinfo_t *volinfo, int type)
{
    int32_t ret = -1;
    glusterd_brickinfo_t *brick = NULL;
    char pid_dir[PATH_MAX] = {
        0,
    };

    ret = glusterd_generate_client_per_brick_volfile(volinfo);
    if (ret) {
        gf_msg(THIS->name, GF_LOG_ERROR, 0, GD_MSG_GLUSTERD_OP_FAILED,
               "failed to generate client volume file");
        goto out;
    }

    ret = mkdir_p(DEFAULT_QUOTA_CRAWL_LOG_DIRECTORY, 0755, _gf_true);
    if (ret) {
        gf_msg(THIS->name, GF_LOG_ERROR, errno, GD_MSG_GLUSTERD_OP_FAILED,
               "failed to create dir %s: %s", DEFAULT_QUOTA_CRAWL_LOG_DIRECTORY,
               strerror(errno));
        goto out;
    }

    GLUSTERD_GET_QUOTA_CRAWL_PIDDIR(pid_dir, volinfo, type);
    ret = mkdir_p(pid_dir, 0755, _gf_true);
    if (ret) {
        gf_msg(THIS->name, GF_LOG_ERROR, errno, GD_MSG_GLUSTERD_OP_FAILED,
               "failed to create dir %s: %s", pid_dir, strerror(errno));
        goto out;
    }

    /* When quota enable is performed, stop alreday running enable crawl
     * process and start fresh crawl process. let disable process continue
     * if running to cleanup the older xattrs
     * When quota disable is performed, stop both enable/disable crawl
     * process and start fresh crawl process to cleanup the xattrs
     */
    glusterd_stop_all_quota_crawl_service(priv, volinfo,
                                          GF_QUOTA_OPTION_TYPE_ENABLE);
    if (type == GF_QUOTA_OPTION_TYPE_DISABLE)
        glusterd_stop_all_quota_crawl_service(priv, volinfo,
                                              GF_QUOTA_OPTION_TYPE_DISABLE);

    cds_list_for_each_entry(brick, &volinfo->bricks, brick_list)
    {
        if (gf_uuid_compare(brick->uuid, MY_UUID))
            continue;

        ret = _glusterd_quota_initiate_fs_crawl(priv, volinfo, brick, type,
                                                pid_dir);

        if (ret)
            goto out;
    }

    ret = 0;
out:
    return ret;
}

int32_t
glusterd_quota_get_default_soft_limit(glusterd_volinfo_t *volinfo,
                                      dict_t *rsp_dict)
{
    int32_t ret = 0;
    char *default_limit = NULL;
    char *val = NULL;

    if (rsp_dict == NULL)
        return -1;

    ret = glusterd_volinfo_get(volinfo, "features.default-soft-limit",
                               &default_limit);
    if (default_limit)
        val = gf_strdup(default_limit);
    else
        val = gf_strdup("80%");

    ret = dict_set_dynstr_sizen(rsp_dict, "default-soft-limit", val);
    if (ret) {
        gf_msg(THIS->name, GF_LOG_ERROR, 0, GD_MSG_DICT_SET_FAILED,
               "Failed to set default "
               "soft-limit into dict");
        goto out;
    }
    ret = 0;

out:
    return ret;
}

int32_t
glusterd_inode_quota_enable(glusterd_volinfo_t *volinfo, char **op_errstr,
                            gf_boolean_t *crawl)
{
    int32_t ret = -1;
    xlator_t *this = THIS;

    GF_VALIDATE_OR_GOTO(this->name, volinfo, out);
    GF_VALIDATE_OR_GOTO(this->name, crawl, out);
    GF_VALIDATE_OR_GOTO(this->name, op_errstr, out);

    if (glusterd_is_volume_started(volinfo) == 0) {
        *op_errstr = gf_strdup(
            "Volume is stopped, start volume "
            "to enable inode quota.");
        ret = -1;
        goto out;
    }

    ret = glusterd_check_if_quota_trans_enabled(volinfo);
    if (ret != 0) {
        *op_errstr = gf_strdup(
            "Quota is disabled. Enabling quota "
            "will enable inode quota");
        ret = -1;
        goto out;
    }

    if (glusterd_is_volume_inode_quota_enabled(volinfo)) {
        *op_errstr = gf_strdup("Inode Quota is already enabled");
        ret = -1;
        goto out;
    }

    ret = dict_set_dynstr_with_alloc(volinfo->dict, VKEY_FEATURES_INODE_QUOTA,
                                     "on");
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, -ret, GD_MSG_DICT_SET_FAILED,
               "dict set failed");
        goto out;
    }

    *crawl = _gf_true;

    ret = glusterd_store_quota_config(
        volinfo, NULL, NULL, GF_QUOTA_OPTION_TYPE_ENABLE_OBJECTS, op_errstr);

    ret = 0;
out:
    if (ret && op_errstr && !*op_errstr)
        gf_asprintf(op_errstr,
                    "Enabling inode quota on volume %s has "
                    "been unsuccessful",
                    volinfo->volname);
    return ret;
}

int32_t
glusterd_quota_enable(glusterd_volinfo_t *volinfo, char **op_errstr,
                      gf_boolean_t *crawl)
{
    int32_t ret = -1;
    xlator_t *this = THIS;

    GF_VALIDATE_OR_GOTO(this->name, volinfo, out);
    GF_VALIDATE_OR_GOTO(this->name, crawl, out);
    GF_VALIDATE_OR_GOTO(this->name, op_errstr, out);

    if (glusterd_is_volume_started(volinfo) == 0) {
        *op_errstr = gf_strdup(
            "Volume is stopped, start volume "
            "to enable quota.");
        ret = -1;
        goto out;
    }

    ret = glusterd_check_if_quota_trans_enabled(volinfo);
    if (ret == 0) {
        *op_errstr = gf_strdup("Quota is already enabled");
        ret = -1;
        goto out;
    }

    ret = dict_set_dynstr_with_alloc(volinfo->dict, VKEY_FEATURES_QUOTA, "on");
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, -ret, GD_MSG_DICT_SET_FAILED,
               "dict set failed");
        goto out;
    }

    ret = dict_set_dynstr_with_alloc(volinfo->dict, VKEY_FEATURES_INODE_QUOTA,
                                     "on");
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_SET_FAILED,
               "dict set failed");
        goto out;
    }

    ret = dict_set_dynstr_with_alloc(volinfo->dict,
                                     "features.quota-deem-statfs", "on");
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, -ret, GD_MSG_DICT_SET_FAILED,
               "setting quota-deem-statfs"
               "in volinfo failed");
        goto out;
    }

    *crawl = _gf_true;

    ret = glusterd_store_quota_config(volinfo, NULL, NULL,
                                      GF_QUOTA_OPTION_TYPE_ENABLE, op_errstr);

    ret = 0;
out:
    if (ret && op_errstr && !*op_errstr)
        gf_asprintf(op_errstr,
                    "Enabling quota on volume %s has been "
                    "unsuccessful",
                    volinfo->volname);
    return ret;
}

int32_t
glusterd_quota_disable(glusterd_volinfo_t *volinfo, char **op_errstr,
                       gf_boolean_t *crawl)
{
    int32_t ret = -1;
    int i = 0;
    char *value = NULL;
    xlator_t *this = THIS;
    glusterd_conf_t *conf = NULL;
    char *quota_options[] = {"features.soft-timeout",
                             "features.hard-timeout",
                             "features.alert-time",
                             "features.default-soft-limit",
                             "features.quota-deem-statfs",
                             "features.quota-timeout",
                             NULL};

    conf = this->private;
    GF_ASSERT(conf);

    GF_VALIDATE_OR_GOTO(this->name, volinfo, out);
    GF_VALIDATE_OR_GOTO(this->name, op_errstr, out);

    ret = glusterd_check_if_quota_trans_enabled(volinfo);
    if (ret == -1) {
        *op_errstr = gf_strdup("Quota is already disabled");
        goto out;
    }

    ret = dict_set_dynstr_with_alloc(volinfo->dict, VKEY_FEATURES_QUOTA, "off");
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, -ret, GD_MSG_DICT_SET_FAILED,
               "dict set failed");
        goto out;
    }

    ret = dict_set_dynstr_with_alloc(volinfo->dict, VKEY_FEATURES_INODE_QUOTA,
                                     "off");
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_SET_FAILED,
               "dict set failed");
        goto out;
    }

    for (i = 0; quota_options[i]; i++) {
        ret = glusterd_volinfo_get(volinfo, quota_options[i], &value);
        if (ret) {
            gf_msg(this->name, GF_LOG_INFO, 0, GD_MSG_VOLINFO_GET_FAIL,
                   "failed to get option"
                   " %s",
                   quota_options[i]);
        } else {
            dict_del(volinfo->dict, quota_options[i]);
        }
    }

    *crawl = _gf_true;

    (void)glusterd_clean_up_quota_store(volinfo);

    ret = 0;
out:
    if (ret && op_errstr && !*op_errstr)
        gf_asprintf(op_errstr,
                    "Disabling quota on volume %s has been "
                    "unsuccessful",
                    volinfo->volname);
    return ret;
}

static int
glusterd_set_quota_limit(char *volname, char *path, char *hard_limit,
                         char *soft_limit, char *key, char **op_errstr)
{
    int ret = -1;
    char abspath[PATH_MAX] = {
        0,
    };
    quota_limits_t existing_limit = {
        0,
    };
    quota_limits_t new_limit = {
        0,
    };
    double soft_limit_double = 0;
    int64_t local_hl = 0;

    GLUSTERD_GET_QUOTA_LIMIT_MOUNT_PATH(abspath, volname, path);
    ret = gf_lstat_dir(abspath, NULL);
    if (ret) {
        gf_asprintf(op_errstr,
                    "Failed to find the directory %s. "
                    "Reason : %s",
                    abspath, strerror(errno));
        goto out;
    }

    if (!soft_limit) {
        ret = sys_lgetxattr(abspath, key, (void *)&existing_limit,
                            sizeof(existing_limit));
        if (ret < 0) {
            switch (errno) {
#if defined(ENOATTR) && (ENOATTR != ENODATA)
                case ENODATA: /* FALLTHROUGH */
#endif
                case ENOATTR:
                    existing_limit.sl = -1;
                    break;
                default:
                    gf_asprintf(op_errstr,
                                "Failed to get the "
                                "xattr %s from %s. Reason : %s",
                                key, abspath, strerror(errno));
                    goto out;
            }
        } else {
            existing_limit.hl = ntoh64(existing_limit.hl);
            existing_limit.sl = ntoh64(existing_limit.sl);
        }
        new_limit.sl = existing_limit.sl;

    } else {
        ret = gf_string2percent(soft_limit, &soft_limit_double);
        if (ret)
            goto out;
        new_limit.sl = soft_limit_double;
    }

    new_limit.sl = hton64(new_limit.sl);

    ret = gf_string2bytesize_int64(hard_limit, &local_hl);
    if (ret)
        goto out;

    new_limit.hl = hton64(local_hl);

    ret = sys_lsetxattr(abspath, key, (char *)(void *)&new_limit,
                        sizeof(new_limit), 0);
    if (ret == -1) {
        gf_asprintf(op_errstr,
                    "setxattr of %s failed on %s."
                    " Reason : %s",
                    key, abspath, strerror(errno));
        goto out;
    }
    ret = 0;

out:
    return ret;
}

static int
glusterd_update_quota_conf_version(glusterd_volinfo_t *volinfo)
{
    volinfo->quota_conf_version++;
    return 0;
}

/*The function glusterd_find_gfid_match () does the following:
 * Given a buffer of gfids, the number of bytes read and the key gfid that needs
 * to be found, the function compares 16 bytes at a time from @buf against
 * @gfid.
 *
 * What happens when the match is found:
 * i. If the function was called as part of 'limit-usage' operation, the call
 *    returns with write_byte_count = bytes_read
 *ii. If the function as called as part of 'quota remove' operation, @buf
 *    is modified in memory such that the match is deleted from the buffer, and
 *    also @write_byte_count is set to original buf size minus the sixteen bytes
 *    that was deleted as part of 'remove'.
 *
 * What happens when the match is not found in the current buffer:
 * The function returns with write_byte_count = bytes_read, which means to say
 * that the caller of this function must write the entire buffer to the tmp file
 * and continue the search.
 */
static gf_boolean_t
glusterd_find_gfid_match_3_6(uuid_t gfid, unsigned char *buf, size_t bytes_read,
                             int opcode, size_t *write_byte_count)
{
    int gfid_index = 0;
    int shift_count = 0;
    unsigned char tmp_buf[17] = {
        0,
    };

    /* This function if for backward compatibility */

    while (gfid_index != bytes_read) {
        memcpy((void *)tmp_buf, (void *)&buf[gfid_index], 16);
        if (!gf_uuid_compare(gfid, tmp_buf)) {
            if (opcode == GF_QUOTA_OPTION_TYPE_REMOVE) {
                shift_count = bytes_read - (gfid_index + 16);
                memmove((void *)&buf[gfid_index], (void *)&buf[gfid_index + 16],
                        shift_count);
                *write_byte_count = bytes_read - 16;
            } else {
                *write_byte_count = bytes_read;
            }
            return _gf_true;
        } else {
            gfid_index += 16;
        }
    }
    if (gfid_index == bytes_read)
        *write_byte_count = bytes_read;

    return _gf_false;
}

static gf_boolean_t
glusterd_find_gfid_match(uuid_t gfid, char gfid_type, unsigned char *buf,
                         size_t bytes_read, int opcode,
                         size_t *write_byte_count)
{
    int gfid_index = 0;
    int shift_count = 0;
    unsigned char tmp_buf[17] = {
        0,
    };
    char type = 0;
    xlator_t *this = THIS;
    glusterd_conf_t *conf = NULL;

    conf = this->private;
    GF_VALIDATE_OR_GOTO(this->name, conf, out);

    if (conf->op_version < GD_OP_VERSION_3_7_0)
        return glusterd_find_gfid_match_3_6(gfid, buf, bytes_read, opcode,
                                            write_byte_count);

    while (gfid_index != bytes_read) {
        memcpy((void *)tmp_buf, (void *)&buf[gfid_index], 16);
        type = buf[gfid_index + 16];

        if (!gf_uuid_compare(gfid, tmp_buf) && type == gfid_type) {
            if (opcode == GF_QUOTA_OPTION_TYPE_REMOVE ||
                opcode == GF_QUOTA_OPTION_TYPE_REMOVE_OBJECTS) {
                shift_count = bytes_read - (gfid_index + 17);
                memmove((void *)&buf[gfid_index], (void *)&buf[gfid_index + 17],
                        shift_count);
                *write_byte_count = bytes_read - 17;
            } else {
                *write_byte_count = bytes_read;
            }
            return _gf_true;
        } else {
            gfid_index += 17;
        }
    }
    if (gfid_index == bytes_read)
        *write_byte_count = bytes_read;

out:

    return _gf_false;
}

/* The function glusterd_copy_to_tmp_file() reads the "remaining" bytes from
 * the source fd and writes them to destination fd, at the rate of 1000 entries
 * a time (qconf_line_sz is the size of an entry)
 */

static int
glusterd_copy_to_tmp_file(int src_fd, int dst_fd, int qconf_line_sz)
{
    int ret = 0;
    ssize_t bytes_read = 0;
    xlator_t *this = THIS;
    unsigned char *buf = 0;
    int buf_sz = qconf_line_sz * 1000;

    GF_ASSERT(buf_sz > 0);

    buf = GF_CALLOC(buf_sz, 1, gf_common_mt_char);
    if (!buf) {
        ret = -1;
        goto out;
    }

    while ((bytes_read = sys_read(src_fd, buf, buf_sz)) > 0) {
        if (bytes_read % qconf_line_sz != 0) {
            gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_QUOTA_CONF_CORRUPT,
                   "quota.conf "
                   "corrupted");
            ret = -1;
            goto out;
        }
        ret = sys_write(dst_fd, (void *)buf, bytes_read);
        if (ret == -1) {
            gf_msg(this->name, GF_LOG_ERROR, errno,
                   GD_MSG_QUOTA_CONF_WRITE_FAIL,
                   "write into quota.conf failed.");
            goto out;
        }
    }
    ret = 0;

out:
    if (buf)
        GF_FREE(buf);
    return ret;
}

int
glusterd_store_quota_conf_upgrade(glusterd_volinfo_t *volinfo)
{
    int ret = -1;
    int fd = -1;
    int conf_fd = -1;
    unsigned char gfid[17] = {
        0,
    };
    xlator_t *this = THIS;
    char type = 0;

    fd = gf_store_mkstemp(volinfo->quota_conf_shandle);
    if (fd < 0) {
        ret = -1;
        goto out;
    }

    conf_fd = open(volinfo->quota_conf_shandle->path, O_RDONLY);
    if (conf_fd == -1) {
        ret = -1;
        goto out;
    }

    ret = quota_conf_skip_header(conf_fd);
    if (ret)
        goto out;

    ret = glusterd_quota_conf_write_header(fd);
    if (ret)
        goto out;

    while (1) {
        ret = quota_conf_read_gfid(conf_fd, gfid, &type, 1.1);
        if (ret == 0)
            break;
        else if (ret < 0)
            goto out;

        ret = glusterd_quota_conf_write_gfid(fd, gfid,
                                             GF_QUOTA_CONF_TYPE_USAGE);
        if (ret < 0)
            goto out;
    }

out:
    if (conf_fd != -1)
        sys_close(conf_fd);

    if (ret && (fd > 0)) {
        gf_store_unlink_tmppath(volinfo->quota_conf_shandle);
    } else if (!ret) {
        ret = gf_store_rename_tmppath(volinfo->quota_conf_shandle);
        if (ret) {
            gf_msg(this->name, GF_LOG_ERROR, errno, GD_MSG_FILE_OP_FAILED,
                   "Failed to rename "
                   "quota conf file");
            return ret;
        }

        ret = glusterd_compute_cksum(volinfo, _gf_true);
        if (ret) {
            gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_CKSUM_COMPUTE_FAIL,
                   "Failed to "
                   "compute cksum for quota conf file");
            return ret;
        }

        ret = glusterd_store_save_quota_version_and_cksum(volinfo);
        if (ret)
            gf_msg(this->name, GF_LOG_ERROR, 0,
                   GD_MSG_QUOTA_CKSUM_VER_STORE_FAIL,
                   "Failed to "
                   "store quota version and cksum");
    }

    return ret;
}

int
glusterd_store_quota_config(glusterd_volinfo_t *volinfo, char *path,
                            char *gfid_str, int opcode, char **op_errstr)
{
    int ret = -1;
    int fd = -1;
    int conf_fd = -1;
    ssize_t bytes_read = 0;
    size_t bytes_to_write = 0;
    uuid_t gfid = {
        0,
    };
    xlator_t *this = THIS;
    gf_boolean_t found = _gf_false;
    gf_boolean_t modified = _gf_false;
    gf_boolean_t is_file_empty = _gf_false;
    gf_boolean_t is_first_read = _gf_true;
    glusterd_conf_t *conf = NULL;
    float version = 0.0f;
    char type = 0;
    int quota_conf_line_sz = 16;
    unsigned char *buf = 0;
    int buf_sz = 0;

    conf = this->private;
    GF_ASSERT(conf);

    glusterd_store_create_quota_conf_sh_on_absence(volinfo);

    conf_fd = open(volinfo->quota_conf_shandle->path, O_RDONLY);
    if (conf_fd == -1) {
        ret = -1;
        goto out;
    }

    ret = quota_conf_read_version(conf_fd, &version);
    if (ret)
        goto out;

    if (version < 1.2f && conf->op_version >= GD_OP_VERSION_3_7_0) {
        /* Upgrade quota.conf file to newer format */
        sys_close(conf_fd);
        conf_fd = -1;

        ret = glusterd_store_quota_conf_upgrade(volinfo);
        if (ret)
            goto out;

        if (GF_QUOTA_OPTION_TYPE_UPGRADE == opcode) {
            /* Nothing more to be done here */
            goto out;
        }

        conf_fd = open(volinfo->quota_conf_shandle->path, O_RDONLY);
        if (conf_fd == -1) {
            ret = -1;
            goto out;
        }

        ret = quota_conf_skip_header(conf_fd);
        if (ret)
            goto out;
    } else if (GF_QUOTA_OPTION_TYPE_UPGRADE == opcode) {
        /* No change to be done in quota_conf*/
        goto out;
    }

    /* If op-ver is gt 3.7, then quota.conf will be upgraded, and 17 bytes
     * storted in the new format. 16 bytes uuid and
     * 1 byte type (usage/object)
     */
    if (conf->op_version >= GD_OP_VERSION_3_7_0)
        quota_conf_line_sz++;

    buf_sz = quota_conf_line_sz * 1000;

    buf = GF_CALLOC(buf_sz, 1, gf_common_mt_char);
    if (!buf) {
        ret = -1;
        goto out;
    }

    fd = gf_store_mkstemp(volinfo->quota_conf_shandle);
    if (fd < 0) {
        ret = -1;
        goto out;
    }

    ret = glusterd_quota_conf_write_header(fd);
    if (ret)
        goto out;

    /* Just create empty quota.conf file if create */
    if (GF_QUOTA_OPTION_TYPE_ENABLE == opcode ||
        GF_QUOTA_OPTION_TYPE_ENABLE_OBJECTS == opcode) {
        modified = _gf_true;
        goto out;
    }

    /* Check if gfid_str is given for opts other than ENABLE */
    if (!gfid_str) {
        ret = -1;
        goto out;
    }
    gf_uuid_parse(gfid_str, gfid);

    if (opcode > GF_QUOTA_OPTION_TYPE_VERSION_OBJECTS)
        type = GF_QUOTA_CONF_TYPE_OBJECTS;
    else
        type = GF_QUOTA_CONF_TYPE_USAGE;

    for (;;) {
        bytes_read = sys_read(conf_fd, buf, buf_sz);
        if (bytes_read <= 0) {
            /*The flag @is_first_read is TRUE when the loop is
             * entered, and is set to false if the first read
             * reads non-zero bytes of data. The flag is used to
             * detect if quota.conf is an empty file, but for the
             * header. This is done to log appropriate error message
             * when 'quota remove' is attempted when there are no
             * limits set on the given volume.
             */
            if (is_first_read)
                is_file_empty = _gf_true;
            break;
        }
        if ((bytes_read % quota_conf_line_sz) != 0) {
            gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_QUOTA_CONF_CORRUPT,
                   "quota.conf "
                   "corrupted");
            ret = -1;
            goto out;
        }
        found = glusterd_find_gfid_match(gfid, type, buf, bytes_read, opcode,
                                         &bytes_to_write);

        ret = sys_write(fd, (void *)buf, bytes_to_write);
        if (ret == -1) {
            gf_msg(this->name, GF_LOG_ERROR, errno,
                   GD_MSG_QUOTA_CONF_WRITE_FAIL,
                   "write into quota.conf failed.");
            goto out;
        }

        /*If the match is found in this iteration, copy the rest of
         * quota.conf into quota.conf.tmp and break.
         * Else continue with the search.
         */
        if (found) {
            ret = glusterd_copy_to_tmp_file(conf_fd, fd, quota_conf_line_sz);
            if (ret)
                goto out;
            break;
        }
        is_first_read = _gf_false;
    }

    switch (opcode) {
        case GF_QUOTA_OPTION_TYPE_LIMIT_USAGE:
            if (!found) {
                ret = glusterd_quota_conf_write_gfid(fd, gfid,
                                                     GF_QUOTA_CONF_TYPE_USAGE);
                if (ret == -1) {
                    gf_msg(this->name, GF_LOG_ERROR, errno,
                           GD_MSG_QUOTA_CONF_WRITE_FAIL,
                           "write into quota.conf failed. ");
                    goto out;
                }
                modified = _gf_true;
            }
            break;
        case GF_QUOTA_OPTION_TYPE_LIMIT_OBJECTS:
            if (!found) {
                ret = glusterd_quota_conf_write_gfid(
                    fd, gfid, GF_QUOTA_CONF_TYPE_OBJECTS);
                if (ret == -1) {
                    gf_msg(this->name, GF_LOG_ERROR, errno,
                           GD_MSG_QUOTA_CONF_WRITE_FAIL,
                           "write into quota.conf failed. ");
                    goto out;
                }
                modified = _gf_true;
            }
            break;

        case GF_QUOTA_OPTION_TYPE_REMOVE:
        case GF_QUOTA_OPTION_TYPE_REMOVE_OBJECTS:
            if (is_file_empty) {
                gf_asprintf(op_errstr,
                            "Cannot remove limit on"
                            " %s. The quota configuration file"
                            " for volume %s is empty.",
                            path, volinfo->volname);
                ret = -1;
                goto out;
            } else {
                if (!found) {
                    gf_asprintf(op_errstr,
                                "Error. gfid %s"
                                " for path %s not found in"
                                " store",
                                gfid_str, path);
                    ret = -1;
                    goto out;
                } else {
                    modified = _gf_true;
                }
            }
            break;

        default:
            ret = 0;
            break;
    }

    if (modified)
        glusterd_update_quota_conf_version(volinfo);

    ret = 0;
out:
    if (conf_fd != -1) {
        sys_close(conf_fd);
    }

    if (buf)
        GF_FREE(buf);

    if (ret && (fd > 0)) {
        gf_store_unlink_tmppath(volinfo->quota_conf_shandle);
    } else if (!ret && GF_QUOTA_OPTION_TYPE_UPGRADE != opcode) {
        ret = gf_store_rename_tmppath(volinfo->quota_conf_shandle);
        if (modified) {
            ret = glusterd_compute_cksum(volinfo, _gf_true);
            if (ret) {
                gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_CKSUM_COMPUTE_FAIL,
                       "Failed to "
                       "compute cksum for quota conf file");
                return ret;
            }

            ret = glusterd_store_save_quota_version_and_cksum(volinfo);
            if (ret)
                gf_msg(this->name, GF_LOG_ERROR, 0,
                       GD_MSG_VERS_CKSUM_STORE_FAIL,
                       "Failed to "
                       "store quota version and cksum");
        }
    }
    return ret;
}

int32_t
glusterd_quota_limit_usage(glusterd_volinfo_t *volinfo, dict_t *dict,
                           int opcode, char **op_errstr)
{
    int32_t ret = -1;
    char *path = NULL;
    char *hard_limit = NULL;
    char *soft_limit = NULL;
    char *gfid_str = NULL;
    xlator_t *this = THIS;

    GF_VALIDATE_OR_GOTO(this->name, dict, out);
    GF_VALIDATE_OR_GOTO(this->name, volinfo, out);
    GF_VALIDATE_OR_GOTO(this->name, op_errstr, out);

    ret = glusterd_check_if_quota_trans_enabled(volinfo);
    if (ret == -1) {
        *op_errstr = gf_strdup(
            "Quota is disabled, please enable "
            "quota");
        goto out;
    }

    ret = dict_get_strn(dict, "path", SLEN("path"), &path);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "Unable to fetch path");
        goto out;
    }
    ret = gf_canonicalize_path(path);
    if (ret)
        goto out;

    ret = dict_get_strn(dict, "hard-limit", SLEN("hard-limit"), &hard_limit);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "Unable to fetch hard limit");
        goto out;
    }

    if (dict_getn(dict, "soft-limit", SLEN("soft-limit"))) {
        ret = dict_get_strn(dict, "soft-limit", SLEN("soft-limit"),
                            &soft_limit);
        if (ret) {
            gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
                   "Unable to fetch "
                   "soft limit");
            goto out;
        }
    }

    if (is_origin_glusterd(dict)) {
        if (opcode == GF_QUOTA_OPTION_TYPE_LIMIT_USAGE) {
            ret = glusterd_set_quota_limit(volinfo->volname, path, hard_limit,
                                           soft_limit, QUOTA_LIMIT_KEY,
                                           op_errstr);
        } else {
            ret = glusterd_set_quota_limit(volinfo->volname, path, hard_limit,
                                           soft_limit, QUOTA_LIMIT_OBJECTS_KEY,
                                           op_errstr);
        }
        if (ret)
            goto out;
    }

    ret = dict_get_strn(dict, "gfid", SLEN("gfid"), &gfid_str);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "Failed to get gfid of path "
               "%s",
               path);
        goto out;
    }

    ret = glusterd_store_quota_config(volinfo, path, gfid_str, opcode,
                                      op_errstr);
    if (ret)
        goto out;

    ret = 0;
out:

    if (ret && op_errstr && !*op_errstr)
        gf_asprintf(op_errstr,
                    "Failed to set hard limit on path %s "
                    "for volume %s",
                    path, volinfo->volname);
    return ret;
}

static int
glusterd_remove_quota_limit(char *volname, char *path, char **op_errstr,
                            int type)
{
    int ret = -1;
    char abspath[PATH_MAX] = {
        0,
    };

    GLUSTERD_GET_QUOTA_LIMIT_MOUNT_PATH(abspath, volname, path);
    ret = gf_lstat_dir(abspath, NULL);
    if (ret) {
        gf_asprintf(op_errstr,
                    "Failed to find the directory %s. "
                    "Reason : %s",
                    abspath, strerror(errno));
        goto out;
    }

    if (type == GF_QUOTA_OPTION_TYPE_REMOVE) {
        ret = sys_lremovexattr(abspath, QUOTA_LIMIT_KEY);
        if (ret) {
            gf_asprintf(op_errstr,
                        "removexattr failed on %s. "
                        "Reason : %s",
                        abspath, strerror(errno));
            goto out;
        }
    }

    if (type == GF_QUOTA_OPTION_TYPE_REMOVE_OBJECTS) {
        ret = sys_lremovexattr(abspath, QUOTA_LIMIT_OBJECTS_KEY);
        if (ret) {
            gf_asprintf(op_errstr,
                        "removexattr failed on %s. "
                        "Reason : %s",
                        abspath, strerror(errno));
            goto out;
        }
    }
    ret = 0;

out:
    return ret;
}

int32_t
glusterd_quota_remove_limits(glusterd_volinfo_t *volinfo, dict_t *dict,
                             int opcode, char **op_errstr, int type)
{
    int32_t ret = -1;
    char *path = NULL;
    char *gfid_str = NULL;
    xlator_t *this = THIS;

    GF_VALIDATE_OR_GOTO(this->name, dict, out);
    GF_VALIDATE_OR_GOTO(this->name, volinfo, out);
    GF_VALIDATE_OR_GOTO(this->name, op_errstr, out);

    ret = glusterd_check_if_quota_trans_enabled(volinfo);
    if (ret == -1) {
        *op_errstr = gf_strdup(
            "Quota is disabled, please enable "
            "quota");
        goto out;
    }

    ret = dict_get_strn(dict, "path", SLEN("path"), &path);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "Unable to fetch path");
        goto out;
    }

    ret = gf_canonicalize_path(path);
    if (ret)
        goto out;

    if (is_origin_glusterd(dict)) {
        ret = glusterd_remove_quota_limit(volinfo->volname, path, op_errstr,
                                          type);
        if (ret)
            goto out;
    }

    ret = dict_get_strn(dict, "gfid", SLEN("gfid"), &gfid_str);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "Failed to get gfid of path "
               "%s",
               path);
        goto out;
    }

    ret = glusterd_store_quota_config(volinfo, path, gfid_str, opcode,
                                      op_errstr);
    if (ret)
        goto out;

    ret = 0;

out:
    return ret;
}

int
glusterd_set_quota_option(glusterd_volinfo_t *volinfo, dict_t *dict, char *key,
                          char **op_errstr)
{
    int ret = 0;
    char *value = NULL;
    xlator_t *this = THIS;
    char *option = NULL;

    ret = glusterd_check_if_quota_trans_enabled(volinfo);
    if (ret == -1) {
        gf_asprintf(op_errstr,
                    "Cannot set %s. Quota on volume %s is "
                    "disabled",
                    key, volinfo->volname);
        return -1;
    }

    ret = dict_get_strn(dict, "value", SLEN("value"), &value);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "Option value absent.");
        return -1;
    }

    option = gf_strdup(value);
    ret = dict_set_dynstr(volinfo->dict, key, option);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "Failed to set option %s", key);
        return -1;
    }

    return 0;
}

static int
glusterd_quotad_op(int opcode)
{
    int ret = -1;
    glusterd_conf_t *priv = NULL;

    priv = THIS->private;
    GF_ASSERT(priv);

    switch (opcode) {
        case GF_QUOTA_OPTION_TYPE_ENABLE:
        case GF_QUOTA_OPTION_TYPE_DISABLE:

            if (glusterd_all_volumes_with_quota_stopped())
                ret = glusterd_svc_stop(&(priv->quotad_svc), SIGTERM);
            else
                ret = priv->quotad_svc.manager(&(priv->quotad_svc), NULL,
                                               PROC_START_NO_WAIT);
            break;

        default:
            ret = 0;
            break;
    }
    return ret;
}

int
glusterd_op_quota(dict_t *dict, char **op_errstr, dict_t *rsp_dict)
{
    glusterd_volinfo_t *volinfo = NULL;
    int32_t ret = -1;
    char *volname = NULL;
    int type = -1;
    gf_boolean_t start_crawl = _gf_false;
    glusterd_conf_t *priv = NULL;
    xlator_t *this = THIS;

    GF_ASSERT(dict);
    GF_ASSERT(op_errstr);

    priv = this->private;
    GF_ASSERT(priv);

    ret = dict_get_strn(dict, "volname", SLEN("volname"), &volname);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "Unable to get volume name");
        goto out;
    }

    ret = glusterd_volinfo_find(volname, &volinfo);
    if (ret) {
        gf_asprintf(op_errstr, FMTSTR_CHECK_VOL_EXISTS, volname);
        goto out;
    }

    ret = dict_get_int32n(dict, "type", SLEN("type"), &type);

    if (!glusterd_is_quota_supported(type, op_errstr)) {
        ret = -1;
        goto out;
    }

    switch (type) {
        case GF_QUOTA_OPTION_TYPE_ENABLE:
            ret = glusterd_quota_enable(volinfo, op_errstr, &start_crawl);
            if (ret < 0)
                goto out;
            break;

        case GF_QUOTA_OPTION_TYPE_ENABLE_OBJECTS:
            ret = glusterd_inode_quota_enable(volinfo, op_errstr, &start_crawl);
            if (ret < 0)
                goto out;
            break;

        case GF_QUOTA_OPTION_TYPE_DISABLE:
            ret = glusterd_quota_disable(volinfo, op_errstr, &start_crawl);
            if (ret < 0)
                goto out;

            break;

        case GF_QUOTA_OPTION_TYPE_LIMIT_USAGE:
        case GF_QUOTA_OPTION_TYPE_LIMIT_OBJECTS:
            ret = glusterd_quota_limit_usage(volinfo, dict, type, op_errstr);
            goto out;

        case GF_QUOTA_OPTION_TYPE_REMOVE:
        case GF_QUOTA_OPTION_TYPE_REMOVE_OBJECTS:
            ret = glusterd_quota_remove_limits(volinfo, dict, type, op_errstr,
                                               type);
            goto out;

        case GF_QUOTA_OPTION_TYPE_LIST:
        case GF_QUOTA_OPTION_TYPE_LIST_OBJECTS:
            ret = glusterd_check_if_quota_trans_enabled(volinfo);
            if (ret == -1) {
                *op_errstr = gf_strdup(
                    "Cannot list limits, "
                    "quota is disabled");
                goto out;
            }
            ret = glusterd_quota_get_default_soft_limit(volinfo, rsp_dict);
            goto out;

        case GF_QUOTA_OPTION_TYPE_SOFT_TIMEOUT:
            ret = glusterd_set_quota_option(volinfo, dict,
                                            "features.soft-timeout", op_errstr);
            if (ret)
                goto out;
            break;

        case GF_QUOTA_OPTION_TYPE_HARD_TIMEOUT:
            ret = glusterd_set_quota_option(volinfo, dict,
                                            "features.hard-timeout", op_errstr);
            if (ret)
                goto out;
            break;

        case GF_QUOTA_OPTION_TYPE_ALERT_TIME:
            ret = glusterd_set_quota_option(volinfo, dict,
                                            "features.alert-time", op_errstr);
            if (ret)
                goto out;
            break;

        case GF_QUOTA_OPTION_TYPE_DEFAULT_SOFT_LIMIT:
            ret = glusterd_set_quota_option(
                volinfo, dict, "features.default-soft-limit", op_errstr);
            if (ret)
                goto out;
            break;

        default:
            gf_asprintf(op_errstr,
                        "Quota command failed. Invalid "
                        "opcode");
            ret = -1;
            goto out;
    }

    if (priv->op_version > GD_OP_VERSION_MIN) {
        ret = glusterd_quotad_op(type);
        if (ret)
            goto out;
    }

    if (GF_QUOTA_OPTION_TYPE_ENABLE == type)
        volinfo->quota_xattr_version++;
    ret = glusterd_store_volinfo(volinfo, GLUSTERD_VOLINFO_VER_AC_INCREMENT);
    if (ret) {
        if (GF_QUOTA_OPTION_TYPE_ENABLE == type)
            volinfo->quota_xattr_version--;
        goto out;
    }

    ret = glusterd_create_volfiles_and_notify_services(volinfo);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_VOLFILE_CREATE_FAIL,
               "Unable to re-create "
               "volfiles");
        if (GF_QUOTA_OPTION_TYPE_ENABLE == type) {
            /* rollback volinfo */
            volinfo->quota_xattr_version--;
            ret = glusterd_store_volinfo(volinfo,
                                         GLUSTERD_VOLINFO_VER_AC_INCREMENT);
            if (ret) {
                gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_VOLINFO_SET_FAIL,
                       "Failed to store volinfo for volume %s",
                       volinfo->volname);
            }
        }

        ret = -1;
        goto out;
    }

#if BUILD_GNFS
    if (GLUSTERD_STATUS_STARTED == volinfo->status) {
        if (priv->op_version == GD_OP_VERSION_MIN)
            (void)priv->nfs_svc.manager(&(priv->nfs_svc), NULL, 0);
    }
#endif

    if (rsp_dict && start_crawl == _gf_true)
        glusterd_quota_initiate_fs_crawl(priv, volinfo, type);

    ret = 0;
out:
    if (type == GF_QUOTA_OPTION_TYPE_LIMIT_USAGE ||
        type == GF_QUOTA_OPTION_TYPE_LIMIT_OBJECTS ||
        type == GF_QUOTA_OPTION_TYPE_REMOVE ||
        type == GF_QUOTA_OPTION_TYPE_REMOVE_OBJECTS) {
        /* During a list operation we need the aux mount to be
         * accessible until the listing is done at the cli
         */
        glusterd_remove_auxiliary_mount(volinfo->volname);
    }

    return ret;
}

/*
 * glusterd_get_gfid_from_brick() fetches the 'trusted.gfid' attribute of @path
 * from each brick in the backend and places the same in the rsp_dict with the
 * keys being gfid0, gfid1, gfid2 and so on. The absence of @path in the backend
 * is not treated as error.
 */
static int
glusterd_get_gfid_from_brick(dict_t *dict, glusterd_volinfo_t *volinfo,
                             dict_t *rsp_dict, char **op_errstr)
{
    int ret = -1;
    int count = 0;
    char *path = NULL;
    char backend_path[PATH_MAX] = {
        0,
    };
    xlator_t *this = THIS;
    glusterd_conf_t *priv = NULL;
    glusterd_brickinfo_t *brickinfo = NULL;
    char key[64] = {
        0,
    };
    int keylen;
    char *gfid_str = NULL;
    uuid_t gfid;

    priv = this->private;
    GF_ASSERT(priv);

    ret = dict_get_strn(dict, "path", SLEN("path"), &path);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "Failed to get path");
        goto out;
    }

    cds_list_for_each_entry(brickinfo, &volinfo->bricks, brick_list)
    {
        ret = glusterd_resolve_brick(brickinfo);
        if (ret) {
            gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_RESOLVE_BRICK_FAIL,
                   FMTSTR_RESOLVE_BRICK, brickinfo->hostname, brickinfo->path);
            goto out;
        }

        if (gf_uuid_compare(brickinfo->uuid, MY_UUID))
            continue;

        if (brickinfo->vg[0])
            continue;

        snprintf(backend_path, sizeof(backend_path), "%s%s", brickinfo->path,
                 path);

        ret = gf_lstat_dir(backend_path, NULL);
        if (ret) {
            gf_msg(this->name, GF_LOG_INFO, errno, GD_MSG_DIR_OP_FAILED,
                   "Failed to find "
                   "directory %s.",
                   backend_path);
            ret = 0;
            continue;
        }
        ret = sys_lgetxattr(backend_path, GFID_XATTR_KEY, gfid, 16);
        if (ret < 0) {
            gf_smsg(this->name, GF_LOG_INFO, errno, GD_MSG_GET_XATTR_FAIL,
                    "Attribute=%s, Directory=%s", GFID_XATTR_KEY, backend_path,
                    NULL);
            ret = 0;
            continue;
        }
        keylen = snprintf(key, sizeof(key), "gfid%d", count);

        gfid_str = gf_strdup(uuid_utoa(gfid));
        if (!gfid_str) {
            ret = -1;
            goto out;
        }

        ret = dict_set_dynstrn(rsp_dict, key, keylen, gfid_str);
        if (ret) {
            gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_SET_FAILED,
                   "Failed to place "
                   "gfid of %s in dict",
                   backend_path);
            GF_FREE(gfid_str);
            goto out;
        }
        count++;
    }

    ret = dict_set_int32n(rsp_dict, "count", SLEN("count"), count);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_SET_FAILED,
               "Failed to set count");
        goto out;
    }

    ret = 0;
out:
    return ret;
}

static int
_glusterd_validate_quota_opts(dict_t *dict, int type, char **errstr)
{
    int ret = -1;
    xlator_t *this = THIS;
    void *quota_xl = NULL;
    volume_opt_list_t opt_list = {
        {0},
    };
    volume_option_t *opt = NULL;
    char *key = NULL;
    char *value = NULL;

    GF_ASSERT(dict);

    ret = xlator_volopt_dynload("features/quota", &quota_xl, &opt_list);
    if (ret)
        goto out;

    switch (type) {
        case GF_QUOTA_OPTION_TYPE_SOFT_TIMEOUT:
        case GF_QUOTA_OPTION_TYPE_HARD_TIMEOUT:
        case GF_QUOTA_OPTION_TYPE_ALERT_TIME:
        case GF_QUOTA_OPTION_TYPE_DEFAULT_SOFT_LIMIT:
            key = (char *)gd_quota_op_list[type];
            break;
        default:
            ret = -1;
            goto out;
    }

    opt = xlator_volume_option_get_list(&opt_list, key);
    if (!opt) {
        ret = -1;
        gf_msg(this->name, GF_LOG_ERROR, EINVAL, GD_MSG_UNKNOWN_KEY,
               "Unknown option: %s", key);
        goto out;
    }
    ret = dict_get_strn(dict, "value", SLEN("value"), &value);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "Value not found for key %s", key);
        goto out;
    }

    ret = xlator_option_validate(this, key, value, opt, errstr);

out:
    if (quota_xl) {
        dlclose(quota_xl);
        quota_xl = NULL;
    }
    return ret;
}

static int
glusterd_create_quota_auxiliary_mount(xlator_t *this, char *volname, int type)
{
    int ret = -1;
    char mountdir[PATH_MAX] = {
        0,
    };
    char pidfile_path[PATH_MAX] = {
        0,
    };
    char logfile[PATH_MAX] = {
        0,
    };
    char qpid[16] = {
        0,
    };
    char *volfileserver = NULL;
    glusterd_conf_t *priv = NULL;
    struct stat buf = {
        0,
    };
    FILE *file = NULL;

    GF_VALIDATE_OR_GOTO("glusterd", this, out);
    priv = this->private;
    GF_VALIDATE_OR_GOTO(this->name, priv, out);

    if (type == GF_QUOTA_OPTION_TYPE_LIST ||
        type == GF_QUOTA_OPTION_TYPE_LIST_OBJECTS) {
        GLUSTERFS_GET_QUOTA_LIST_MOUNT_PIDFILE(pidfile_path, volname);
        GLUSTERD_GET_QUOTA_LIST_MOUNT_PATH(mountdir, volname, "/");
    } else {
        GLUSTERFS_GET_QUOTA_LIMIT_MOUNT_PIDFILE(pidfile_path, volname);
        GLUSTERD_GET_QUOTA_LIMIT_MOUNT_PATH(mountdir, volname, "/");
    }

    file = fopen(pidfile_path, "r");
    if (file) {
        /* Previous command did not clean up pid file.
         * remove aux mount if it exists*/
        gf_umount_lazy(this->name, mountdir, 1);
        fclose(file);
    }

    ret = sys_mkdir(mountdir, 0755);
    if (ret && errno != EEXIST) {
        gf_msg(this->name, GF_LOG_ERROR, errno, GD_MSG_MOUNT_REQ_FAIL,
               "Failed to create auxiliary "
               "mount directory %s",
               mountdir);
        goto out;
    }
    snprintf(logfile, PATH_MAX - 1, "%s/quota-mount-%s.log", priv->logdir,
             volname);
    snprintf(qpid, 15, "%d", GF_CLIENT_PID_QUOTA_MOUNT);

    if (dict_get_strn(this->options, "transport.socket.bind-address",
                      SLEN("transport.socket.bind-address"),
                      &volfileserver) != 0)
        volfileserver = "localhost";

    synclock_unlock(&priv->big_lock);
    ret = runcmd(SBIN_DIR "/glusterfs", "--volfile-server", volfileserver,
                 "--volfile-id", volname, "-l", logfile, "-p", pidfile_path,
                 "--client-pid", qpid, mountdir, NULL);
    if (ret == 0) {
        /* Block here till mount process is ready to accept FOPs.
         * Else, if glusterd acquires biglock below before
         * mount process is ready, then glusterd and mount process
         * can get into a deadlock situation.
         */
        ret = sys_stat(mountdir, &buf);
        if (ret < 0)
            ret = -errno;
    } else {
        ret = -errno;
    }

    synclock_lock(&priv->big_lock);

    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, -ret, GD_MSG_MOUNT_REQ_FAIL,
               "Failed to mount glusterfs "
               "client. Please check the log file %s for more details",
               logfile);
        ret = -1;
        goto out;
    }

    ret = 0;

out:
    return ret;
}

int
glusterd_op_stage_quota(dict_t *dict, char **op_errstr, dict_t *rsp_dict)
{
    int ret = 0;
    char *volname = NULL;
    int type = 0;
    xlator_t *this = THIS;
    glusterd_conf_t *priv = NULL;
    glusterd_volinfo_t *volinfo = NULL;
    char *hard_limit_str = NULL;
    int64_t hard_limit = 0;
    gf_boolean_t get_gfid = _gf_false;

    priv = this->private;
    GF_ASSERT(priv);

    GF_ASSERT(dict);
    GF_ASSERT(op_errstr);

    ret = dict_get_strn(dict, "volname", SLEN("volname"), &volname);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "Unable to get volume name");
        goto out;
    }

    ret = glusterd_volinfo_find(volname, &volinfo);
    if (ret) {
        gf_asprintf(op_errstr, FMTSTR_CHECK_VOL_EXISTS, volname);
        goto out;
    }

    if (!glusterd_is_volume_started(volinfo)) {
        *op_errstr = gf_strdup(
            "Volume is stopped, start volume "
            "before executing quota command.");
        ret = -1;
        goto out;
    }

    ret = dict_get_int32n(dict, "type", SLEN("type"), &type);
    if (ret) {
        *op_errstr = gf_strdup(
            "Volume quota failed, internal error, "
            "unable to get type of operation");
        goto out;
    }

    if ((!glusterd_is_volume_quota_enabled(volinfo)) &&
        (type != GF_QUOTA_OPTION_TYPE_ENABLE)) {
        *op_errstr = gf_strdup(
            "Quota is disabled, please enable "
            "quota");
        ret = -1;
        goto out;
    }

    if (type > GF_QUOTA_OPTION_TYPE_VERSION_OBJECTS) {
        if (!glusterd_is_volume_inode_quota_enabled(volinfo) &&
            type != GF_QUOTA_OPTION_TYPE_ENABLE_OBJECTS) {
            *op_errstr = gf_strdup(
                "Inode Quota is disabled, "
                "please enable inode quota");
            ret = -1;
            goto out;
        }
    }

    if (!glusterd_is_quota_supported(type, op_errstr)) {
        ret = -1;
        goto out;
    }

    if ((GF_QUOTA_OPTION_TYPE_ENABLE != type) &&
        (glusterd_check_if_quota_trans_enabled(volinfo) != 0)) {
        ret = -1;
        gf_asprintf(op_errstr, "Quota is not enabled on volume %s", volname);
        goto out;
    }

    switch (type) {
        case GF_QUOTA_OPTION_TYPE_LIST:
        case GF_QUOTA_OPTION_TYPE_LIST_OBJECTS:
        case GF_QUOTA_OPTION_TYPE_LIMIT_USAGE:
        case GF_QUOTA_OPTION_TYPE_LIMIT_OBJECTS:
        case GF_QUOTA_OPTION_TYPE_REMOVE:
        case GF_QUOTA_OPTION_TYPE_REMOVE_OBJECTS:
            /* Quota auxiliary mount is needed by CLI
             * for list command and need by glusterd for
             * setting/removing limit
             */
            if (is_origin_glusterd(dict)) {
                ret = glusterd_create_quota_auxiliary_mount(this, volname,
                                                            type);
                if (ret) {
                    *op_errstr = gf_strdup(
                        "Failed to start aux "
                        "mount");
                    goto out;
                }
            }
            break;
    }

    switch (type) {
        case GF_QUOTA_OPTION_TYPE_LIMIT_USAGE:
            ret = dict_get_strn(dict, "hard-limit", SLEN("hard-limit"),
                                &hard_limit_str);
            if (ret) {
                gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
                       "Failed to get hard-limit from dict");
                goto out;
            }
            ret = gf_string2bytesize_int64(hard_limit_str, &hard_limit);
            if (ret) {
                if (errno == ERANGE || hard_limit < 0)
                    gf_asprintf(op_errstr,
                                "Hard-limit "
                                "value out of range (0 - %" PRId64 "): %s",
                                hard_limit, hard_limit_str);
                else
                    gf_msg(this->name, GF_LOG_ERROR, errno,
                           GD_MSG_CONVERSION_FAILED,
                           "Failed to convert hard-limit "
                           "string to value");
                goto out;
            }
            get_gfid = _gf_true;
            break;
        case GF_QUOTA_OPTION_TYPE_LIMIT_OBJECTS:
            get_gfid = _gf_true;
            break;

        case GF_QUOTA_OPTION_TYPE_REMOVE:
        case GF_QUOTA_OPTION_TYPE_REMOVE_OBJECTS:
            get_gfid = _gf_true;
            break;

        case GF_QUOTA_OPTION_TYPE_SOFT_TIMEOUT:
        case GF_QUOTA_OPTION_TYPE_HARD_TIMEOUT:
        case GF_QUOTA_OPTION_TYPE_ALERT_TIME:
        case GF_QUOTA_OPTION_TYPE_DEFAULT_SOFT_LIMIT:
            ret = _glusterd_validate_quota_opts(dict, type, op_errstr);
            if (ret)
                goto out;
            break;

        default:
            break;
    }

    if (get_gfid == _gf_true) {
        ret = glusterd_get_gfid_from_brick(dict, volinfo, rsp_dict, op_errstr);
        if (ret)
            goto out;
    }

    ret = 0;

out:
    if (ret && op_errstr && *op_errstr)
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_OP_STAGE_QUOTA_FAIL, "%s",
               *op_errstr);
    gf_msg_debug(this->name, 0, "Returning %d", ret);

    return ret;
}
