/*
   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 "locking.h"
#include "marker-quota.h"
#include "marker-common.h"
#include "marker-quota-helper.h"
#include "marker-mem-types.h"

int
mq_loc_fill (loc_t *loc, inode_t *inode, inode_t *parent, char *path)
{
        int ret = -1;

        GF_VALIDATE_OR_GOTO ("marker", loc, out);
        GF_VALIDATE_OR_GOTO ("marker", inode, out);
        GF_VALIDATE_OR_GOTO ("marker", path, out);
        /* Not checking for parent because while filling
         * loc of root, parent will be NULL
         */

        if (inode) {
                loc->inode = inode_ref (inode);
        }

        if (parent)
                loc->parent = inode_ref (parent);

        if (!gf_uuid_is_null (inode->gfid))
                gf_uuid_copy (loc->gfid, inode->gfid);

        loc->path = gf_strdup (path);
        if (!loc->path) {
                gf_log ("loc fill", GF_LOG_ERROR, "strdup failed");
                goto out;
        }

        loc->name = strrchr (loc->path, '/');
        if (loc->name)
                loc->name++;
        else
                goto out;

        ret = 0;

out:
        if (ret < 0)
                loc_wipe (loc);

        return ret;
}


int32_t
mq_inode_loc_fill (const char *parent_gfid, inode_t *inode, loc_t *loc)
{
        char            *resolvedpath = NULL;
        inode_t         *parent       = NULL;
        int              ret          = -1;

        if ((!inode) || (!loc))
                return ret;

        if ((inode) && __is_root_gfid (inode->gfid)) {
                loc->parent = NULL;
                goto ignore_parent;
        }

        if (parent_gfid == NULL)
                parent = inode_parent (inode, 0, NULL);
        else
                parent = inode_find (inode->table,
                                     (unsigned char *) parent_gfid);

        if (parent == NULL) {
                gf_log ("marker", GF_LOG_ERROR, "parent is NULL for %s",
                        uuid_utoa(inode->gfid));
                goto err;
        }

ignore_parent:
        ret = inode_path (inode, NULL, &resolvedpath);
        if (ret < 0) {
                gf_log ("marker", GF_LOG_ERROR, "failed to resolve path for %s",
                        uuid_utoa(inode->gfid));
                goto err;
        }

        ret = mq_loc_fill (loc, inode, parent, resolvedpath);
        if (ret < 0)
                goto err;

err:
        if (parent)
                inode_unref (parent);

        GF_FREE (resolvedpath);

        return ret;
}


quota_inode_ctx_t *
mq_alloc_inode_ctx ()
{
        int32_t                  ret    = -1;
        quota_inode_ctx_t       *ctx    = NULL;

        QUOTA_ALLOC (ctx, quota_inode_ctx_t, ret);
        if (ret == -1)
                goto out;

        ctx->size = 0;
        ctx->dirty = 0;
        ctx->updation_status = _gf_false;
        LOCK_INIT (&ctx->lock);
        INIT_LIST_HEAD (&ctx->contribution_head);
out:
        return ctx;
}

inode_contribution_t *
mq_get_contribution_node (inode_t *inode, quota_inode_ctx_t *ctx)
{
        inode_contribution_t    *contri = NULL;
        inode_contribution_t    *temp   = NULL;

        if (!inode || !ctx)
                goto out;

        list_for_each_entry (temp, &ctx->contribution_head, contri_list) {
                if (gf_uuid_compare (temp->gfid, inode->gfid) == 0) {
                        contri = temp;
                        goto out;
                }
        }
out:
        return contri;
}


int32_t
mq_delete_contribution_node (dict_t *dict, char *key,
                             inode_contribution_t *contribution)
{
        if (dict_get (dict, key) != NULL)
                goto out;

        QUOTA_FREE_CONTRIBUTION_NODE (contribution);
out:
        return 0;
}


inode_contribution_t *
__mq_add_new_contribution_node (xlator_t *this, quota_inode_ctx_t *ctx,
                                loc_t *loc)
{
        int32_t               ret          = 0;
        inode_contribution_t *contribution = NULL;

        if (!loc->parent) {
                if (!gf_uuid_is_null (loc->pargfid))
                        loc->parent = inode_find (loc->inode->table,
                                                  loc->pargfid);

                if (!loc->parent)
                        loc->parent = inode_parent (loc->inode, loc->pargfid,
                                                    loc->name);
                if (!loc->parent)
                        goto out;
        }

        list_for_each_entry (contribution, &ctx->contribution_head,
                             contri_list) {
                if (loc->parent &&
                    gf_uuid_compare (contribution->gfid, loc->parent->gfid) == 0) {
                        goto out;
                }
        }

        QUOTA_ALLOC (contribution, inode_contribution_t, ret);
        if (ret == -1)
                goto out;

        contribution->contribution = 0;

        gf_uuid_copy (contribution->gfid, loc->parent->gfid);

	LOCK_INIT (&contribution->lock);
        INIT_LIST_HEAD (&contribution->contri_list);

        list_add_tail (&contribution->contri_list, &ctx->contribution_head);

out:
        return contribution;
}


inode_contribution_t *
mq_add_new_contribution_node (xlator_t *this, quota_inode_ctx_t *ctx,
                              loc_t *loc)
{
        inode_contribution_t *contribution = NULL;

        if ((ctx == NULL) || (loc == NULL))
                return NULL;

        if (((loc->path) && (strcmp (loc->path, "/") == 0))
            || (!loc->path && gf_uuid_is_null (loc->pargfid)))
                return NULL;

        LOCK (&ctx->lock);
        {
                contribution = __mq_add_new_contribution_node (this, ctx, loc);
        }
        UNLOCK (&ctx->lock);

        return contribution;
}


int32_t
mq_dict_set_contribution (xlator_t *this, dict_t *dict, loc_t *loc,
                          uuid_t gfid, char *contri_key)
{
        int32_t ret                  = -1;
        char    key[CONTRI_KEY_MAX]  = {0, };

        GF_VALIDATE_OR_GOTO ("marker", this, out);
        GF_VALIDATE_OR_GOTO ("marker", dict, out);
        GF_VALIDATE_OR_GOTO ("marker", loc, out);

        if (gfid && !gf_uuid_is_null(gfid)) {
                GET_CONTRI_KEY (key, gfid, ret);
        } else if (loc->parent) {
                GET_CONTRI_KEY (key, loc->parent->gfid, ret);
        } else {
                /* nameless lookup, fetch contributions to all parents */
                GET_CONTRI_KEY (key, NULL, ret);
        }

        if (ret < 0)
                goto out;

        ret = dict_set_int64 (dict, key, 0);
        if (ret < 0)
                goto out;

        if (contri_key)
                strncpy (contri_key, key, CONTRI_KEY_MAX);

out:
        if (ret < 0)
                gf_log_callingfn (this->name, GF_LOG_ERROR, "dict set failed");

        return ret;
}


int32_t
mq_inode_ctx_get (inode_t *inode, xlator_t *this,
                  quota_inode_ctx_t **ctx)
{
        int32_t             ret      = -1;
        uint64_t            ctx_int  = 0;
        marker_inode_ctx_t *mark_ctx = NULL;

        GF_VALIDATE_OR_GOTO ("marker", inode, out);
        GF_VALIDATE_OR_GOTO ("marker", this, out);
        GF_VALIDATE_OR_GOTO ("marker", ctx, out);

        ret = inode_ctx_get (inode, this, &ctx_int);
        if (ret < 0) {
                ret = -1;
                *ctx = NULL;
                goto out;
        }

        mark_ctx = (marker_inode_ctx_t *) (unsigned long)ctx_int;
        if (mark_ctx->quota_ctx == NULL) {
                ret = -1;
                goto out;
        }

        *ctx = mark_ctx->quota_ctx;

        ret = 0;

out:
        return ret;
}


quota_inode_ctx_t *
__mq_inode_ctx_new (inode_t *inode, xlator_t *this)
{
        int32_t               ret        = -1;
        quota_inode_ctx_t    *quota_ctx  = NULL;
        marker_inode_ctx_t   *mark_ctx   = NULL;

        ret = marker_force_inode_ctx_get (inode, this, &mark_ctx);
        if (ret < 0) {
                gf_log (this->name, GF_LOG_ERROR,
                        "marker_force_inode_ctx_get() failed");
                goto out;
        }

        LOCK (&inode->lock);
        {
                if (mark_ctx->quota_ctx == NULL) {
                        quota_ctx = mq_alloc_inode_ctx ();
                        if (quota_ctx == NULL) {
                                ret = -1;
                                goto unlock;
                        }
                        mark_ctx->quota_ctx = quota_ctx;
                } else {
                        quota_ctx = mark_ctx->quota_ctx;
                }

                ret = 0;
        }
unlock:
        UNLOCK (&inode->lock);
out:
        return quota_ctx;
}


quota_inode_ctx_t *
mq_inode_ctx_new (inode_t * inode, xlator_t *this)
{
        return __mq_inode_ctx_new (inode, this);
}

quota_local_t *
mq_local_new ()
{
        quota_local_t  *local   = NULL;

        local = mem_get0 (THIS->local_pool);
        if (!local)
                goto out;

        local->ref = 1;
        LOCK_INIT (&local->lock);

        local->ctx = NULL;
        local->contri = NULL;

out:
        return local;
}

quota_local_t *
mq_local_ref (quota_local_t *local)
{
        LOCK (&local->lock);
        {
                local->ref ++;
        }
        UNLOCK (&local->lock);

        return local;
}


int32_t
mq_local_unref (xlator_t *this, quota_local_t *local)
{
        int32_t ref = 0;
        if (local == NULL)
                goto out;

        QUOTA_SAFE_DECREMENT (&local->lock, local->ref, ref);

        if (ref != 0)
                goto out;

        if (local->fd != NULL)
                fd_unref (local->fd);

        loc_wipe (&local->loc);

        loc_wipe (&local->parent_loc);

        LOCK_DESTROY (&local->lock);

        mem_put (local);
out:
        return 0;
}


inode_contribution_t *
mq_get_contribution_from_loc (xlator_t *this, loc_t *loc)
{
        int32_t               ret          = 0;
        quota_inode_ctx_t    *ctx          = NULL;
        inode_contribution_t *contribution = NULL;

        ret = mq_inode_ctx_get (loc->inode, this, &ctx);
        if (ret < 0) {
                gf_log_callingfn (this->name, GF_LOG_WARNING,
                                  "cannot get marker-quota context from inode "
                                  "(gfid:%s, path:%s)",
                                  uuid_utoa (loc->inode->gfid), loc->path);
                goto err;
        }

        contribution = mq_get_contribution_node (loc->parent, ctx);
        if (contribution == NULL) {
                gf_log_callingfn (this->name, GF_LOG_WARNING,
                                  "inode (gfid:%s, path:%s) has "
                                  "no contribution towards parent (gfid:%s)",
                                  uuid_utoa (loc->inode->gfid),
                                  loc->path, uuid_utoa (loc->parent->gfid));
                goto err;
        }

err:
        return contribution;
}
