/*
   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 <inttypes.h>
#include <fnmatch.h>
#include <pwd.h>

#include <glusterfs/globals.h>
#include <glusterfs/glusterfs.h>
#include <glusterfs/compat.h>
#include <glusterfs/dict.h>
#include <glusterfs/list.h>
#include <glusterfs/logging.h>
#include <glusterfs/syscall.h>
#include <glusterfs/defaults.h>
#include <glusterfs/compat.h>
#include <glusterfs/compat-errno.h>
#include <glusterfs/run.h>
#include "glusterd-mem-types.h"
#include "glusterd.h"
#include "glusterd-utils.h"
#include <glusterfs/common-utils.h>
#include "glusterd-mountbroker.h"
#include "glusterd-op-sm.h"
#include "glusterd-messages.h"

static int
seq_dict_foreach(dict_t *dict, int (*fn)(char *str, void *data), void *data)
{
    char index[] = "4294967296";  // 1<<32
    int i = 0;
    char *val = NULL;
    int ret = 0;

    for (;; i++) {
        snprintf(index, sizeof(index), "%d", i);
        ret = dict_get_str(dict, index, &val);
        if (ret != 0)
            return ret == -ENOENT ? 0 : ret;
        ret = fn(val, data);
        if (ret != 0)
            return ret;
    }
}

int
parse_mount_pattern_desc(gf_mount_spec_t *mspec, char *pdesc)
#define SYNTAX_ERR -2
{
    char *curs = NULL;
    char *c2 = NULL;
    char sc = '\0';
    char **cc = NULL;
    gf_mount_pattern_t *pat = NULL;
    int pnum = 0;
    int ret = 0;
    int lastsup = -1;
    int incl = -1;
    char **pcc = NULL;
    int pnc = 0;

    skipwhite(&pdesc);

    /* a bow to theory */
    if (!*pdesc)
        return 0;

    /* count number of components, separated by '&' */
    mspec->len = 0;
    for (curs = pdesc; *curs; curs++) {
        if (*curs == ')')
            mspec->len++;
    }

    mspec->patterns = GF_CALLOC(mspec->len, sizeof(*mspec->patterns),
                                gf_gld_mt_mount_pattern);
    if (!mspec->patterns) {
        gf_smsg("glusterd", GF_LOG_ERROR, errno, GD_MSG_NO_MEMORY, NULL);
        ret = -1;
        goto out;
    }

    pat = mspec->patterns;
    curs = pdesc;
    skipwhite(&curs);
    for (;;) {
        incl = -1;

        /* check for pattern signedness modifier */
        if (*curs == '-') {
            pat->negative = _gf_true;
            curs++;
        }

        /* now should come condition specifier,
         * then opening paren
         */
        c2 = nwstrtail(curs, "SUB(");
        if (c2) {
            pat->condition = SET_SUB;
            goto got_cond;
        }
        c2 = nwstrtail(curs, "SUP(");
        if (c2) {
            pat->condition = SET_SUPER;
            lastsup = pat - mspec->patterns;
            goto got_cond;
        }
        c2 = nwstrtail(curs, "EQL(");
        if (c2) {
            pat->condition = SET_EQUAL;
            goto got_cond;
        }
        c2 = nwstrtail(curs, "MEET(");
        if (c2) {
            pat->condition = SET_INTERSECT;
            goto got_cond;
        }
        c2 = nwstrtail(curs, "SUB+(");
        if (c2) {
            pat->condition = SET_SUB;
            incl = lastsup;
            goto got_cond;
        }

        ret = SYNTAX_ERR;
        goto out;

    got_cond:
        curs = c2;
        skipwhite(&curs);
        /* count the number of components for pattern */
        pnum = *curs == ')' ? 0 : 1;
        for (c2 = curs; *c2 != ')';) {
            if (strchr("&|", *c2)) {
                ret = SYNTAX_ERR;
                goto out;
            }
            while (!strchr("|&)", *c2) && !isspace(*c2))
                c2++;
            skipwhite(&c2);
            switch (*c2) {
                case ')':
                    break;
                case '\0':
                case '&':
                    ret = SYNTAX_ERR;
                    goto out;
                case '|':
                    *c2 = ' ';
                    skipwhite(&c2);
                    /* fall through */
                default:
                    pnum++;
            }
        }
        if (incl >= 0) {
            pnc = 0;
            for (pcc = mspec->patterns[incl].components; *pcc; pcc++)
                pnc++;
            pnum += pnc;
        }
        pat->components = GF_CALLOC(pnum + 1, sizeof(*pat->components),
                                    gf_gld_mt_mount_comp_container);
        if (!pat->components) {
            ret = -1;
            goto out;
        }

        cc = pat->components;
        /* copy over included component set */
        if (incl >= 0) {
            memcpy(pat->components, mspec->patterns[incl].components,
                   pnc * sizeof(*pat->components));
            cc += pnc;
        }
        /* parse and add components */
        c2 = ""; /* reset c2 */
        while (*c2 != ')') {
            c2 = curs;
            while (!isspace(*c2) && *c2 != ')')
                c2++;
            sc = *c2;
            *c2 = '\0';
            ;
            *cc = gf_strdup(curs);
            if (!*cc) {
                ret = -1;
                goto out;
            }
            *c2 = sc;
            skipwhite(&c2);
            curs = c2;
            cc++;
        }

        curs++;
        skipwhite(&curs);
        if (*curs == '&') {
            curs++;
            skipwhite(&curs);
        }

        if (!*curs)
            break;
        pat++;
    }

out:
    if (ret == SYNTAX_ERR) {
        gf_msg("glusterd", GF_LOG_ERROR, EINVAL, GD_MSG_INVALID_ENTRY,
               "cannot parse mount patterns %s", pdesc);
    }

    /* We've allocted a lotta stuff here but don't bother with freeing
     * on error, in that case we'll terminate anyway
     */
    return ret ? -1 : 0;
}
#undef SYNTAX_ERR

const char *georep_mnt_desc_template =
    "SUP("
    "aux-gfid-mount "
    "acl "
    "volfile-server=localhost "
    "client-pid=%d "
    "user-map-root=%s "
    ")"
    "SUB+("
    "log-file=%s/" GEOREP
    "*/* "
    "log-level=* "
    "volfile-id=* "
    ")"
    "MEET("
    "%s"
    ")";

int
make_georep_mountspec(gf_mount_spec_t *mspec, const char *volnames, char *user,
                      char *logdir)
{
    char *georep_mnt_desc = NULL;
    char *meetspec = NULL;
    char *vols = NULL;
    char *vol = NULL;
    char *p = NULL;
    char *savetok = NULL;
    char *fa[3] = {
        0,
    };
    size_t siz = 0;
    int vc = 0;
    int i = 0;
    int ret = 0;

    vols = gf_strdup((char *)volnames);
    if (!vols) {
        gf_smsg(THIS->name, GF_LOG_ERROR, errno, GD_MSG_STRDUP_FAILED,
                "Volume name=%s", volnames, NULL);
        goto out;
    }

    for (vc = 1, p = vols; *p; p++) {
        if (*p == ',')
            vc++;
    }
    siz = strlen(volnames) + vc * SLEN("volfile-id=");
    meetspec = GF_CALLOC(1, siz + 1, gf_gld_mt_georep_meet_spec);
    if (!meetspec) {
        gf_smsg(THIS->name, GF_LOG_ERROR, errno, GD_MSG_NO_MEMORY, NULL);
        goto out;
    }

    for (p = vols;;) {
        vol = strtok_r(p, ",", &savetok);
        if (!vol) {
            GF_ASSERT(vc == 0);
            break;
        }
        p = NULL;
        strcat(meetspec, "volfile-id=");
        strcat(meetspec, vol);
        if (--vc > 0)
            strcat(meetspec, " ");
    }

    ret = gf_asprintf(&georep_mnt_desc, georep_mnt_desc_template,
                      GF_CLIENT_PID_GSYNCD, user, logdir, meetspec);
    if (ret == -1) {
        georep_mnt_desc = NULL;
        goto out;
    }

    ret = parse_mount_pattern_desc(mspec, georep_mnt_desc);

out:
    fa[0] = meetspec;
    fa[1] = vols;
    fa[2] = georep_mnt_desc;

    for (i = 0; i < 3; i++) {
        if (fa[i] == NULL)
            ret = -1;
        else
            GF_FREE(fa[i]);
    }

    return ret;
}

static gf_boolean_t
match_comp(char *str, char *patcomp)
{
    char *c1 = patcomp;
    char *c2 = str;

    GF_ASSERT(c1);
    GF_ASSERT(c2);

    while (*c1 == *c2) {
        if (!*c1)
            return _gf_true;
        c1++;
        c2++;
        if (c1[-1] == '=')
            break;
    }

    return fnmatch(c1, c2, 0) == 0 ? _gf_true : _gf_false;
}

struct gf_set_descriptor {
    gf_boolean_t priv[2];
    gf_boolean_t common;
};

static int
_gf_set_dict_iter1(char *val, void *data)
{
    void **dataa = data;
    struct gf_set_descriptor *sd = dataa[0];
    char **curs = dataa[1];
    gf_boolean_t priv = _gf_true;

    while (*curs) {
        if (match_comp(val, *curs)) {
            priv = _gf_false;
            sd->common = _gf_true;
        }
        curs++;
    }

    if (priv)
        sd->priv[0] = _gf_true;

    return 0;
}

static int
_gf_set_dict_iter2(char *val, void *data)
{
    void **dataa = data;
    gf_boolean_t *boo = dataa[0];
    char *comp = dataa[1];

    if (match_comp(val, comp))
        *boo = _gf_true;

    return 0;
}

static void
relate_sets(struct gf_set_descriptor *sd, dict_t *argdict, char **complist)
{
    void *dataa[] = {NULL, NULL};
    gf_boolean_t boo = _gf_false;

    memset(sd, 0, sizeof(*sd));

    dataa[0] = sd;
    dataa[1] = complist;
    seq_dict_foreach(argdict, _gf_set_dict_iter1, dataa);

    while (*complist) {
        boo = _gf_false;
        dataa[0] = &boo;
        dataa[1] = *complist;
        seq_dict_foreach(argdict, _gf_set_dict_iter2, dataa);

        if (boo)
            sd->common = _gf_true;
        else
            sd->priv[1] = _gf_true;

        complist++;
    }
}

static int
_arg_parse_uid(char *val, void *data)
{
    char *user = strtail(val, "user-map-root=");
    struct passwd *pw = NULL;

    if (!user)
        return 0;
    pw = getpwnam(user);
    if (!pw)
        return -EINVAL;

    if (*(int *)data >= 0)
        /* uid ambiguity, already found */
        return -EINVAL;

    *(int *)data = pw->pw_uid;
    return 0;
}

static int
evaluate_mount_request(xlator_t *this, gf_mount_spec_t *mspec, dict_t *argdict)
{
    struct gf_set_descriptor sd = {
        {
            0,
        },
    };
    int i = 0;
    int uid = -1;
    int ret = 0;
    gf_boolean_t match = _gf_false;

    for (i = 0; i < mspec->len; i++) {
        relate_sets(&sd, argdict, mspec->patterns[i].components);
        switch (mspec->patterns[i].condition) {
            case SET_SUB:
                match = !sd.priv[0];
                break;
            case SET_SUPER:
                match = !sd.priv[1];
                break;
            case SET_EQUAL:
                match = (!sd.priv[0] && !sd.priv[1]);
                break;
            case SET_INTERSECT:
                match = sd.common;
                break;
            default:
                GF_ASSERT(!"unreached");
        }
        if (mspec->patterns[i].negative)
            match = !match;

        if (!match) {
            gf_msg(this->name, GF_LOG_ERROR, EPERM,
                   GD_MSG_MNTBROKER_SPEC_MISMATCH,
                   "Mountbroker spec mismatch!!! SET: %d "
                   "COMPONENT: %d. Review the mount args passed",
                   mspec->patterns[i].condition, i);
            return -EPERM;
        }
    }

    ret = seq_dict_foreach(argdict, _arg_parse_uid, &uid);
    if (ret != 0)
        return ret;

    return uid;
}

static int
_volname_get(char *val, void *data)
{
    char **volname = data;

    *volname = strtail(val, "volfile-id=");

    return *volname ? 1 : 0;
}

static int
_runner_add(char *val, void *data)
{
    runner_t *runner = data;

    runner_argprintf(runner, "--%s", val);

    return 0;
}

int
glusterd_do_mount(char *label, dict_t *argdict, char **path, int *op_errno)
{
    glusterd_conf_t *priv = NULL;
    char *mountbroker_root = NULL;
    gf_mount_spec_t *mspec = NULL;
    int uid = -ENOENT;
    char *volname = NULL;
    glusterd_volinfo_t *vol = NULL;
    char *mtptemp = NULL;
    char *mntlink = NULL;
    char *cookieswitch = NULL;
    char *cookie = NULL;
    char *sla = NULL;
    struct stat st = {
        0,
    };
    runner_t runner = {
        0,
    };
    int ret = 0;
    xlator_t *this = THIS;
    mode_t orig_umask = 0;
    gf_boolean_t found_label = _gf_false;

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

    GF_ASSERT(op_errno);
    *op_errno = 0;

    if (dict_get_strn(this->options, "mountbroker-root",
                      SLEN("mountbroker-root"), &mountbroker_root) != 0) {
        *op_errno = ENOENT;
        gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED,
               "'option mountbroker-root' "
               "missing in glusterd vol file");
        goto out;
    }

    GF_ASSERT(label);
    if (!*label) {
        *op_errno = EINVAL;
        gf_msg(this->name, GF_LOG_ERROR, *op_errno, GD_MSG_MNTBROKER_LABEL_NULL,
               "label is NULL (%s)", strerror(*op_errno));
        goto out;
    }

    /* look up spec for label */
    cds_list_for_each_entry(mspec, &priv->mount_specs, speclist)
    {
        if (strcmp(mspec->label, label) != 0)
            continue;

        found_label = _gf_true;
        uid = evaluate_mount_request(this, mspec, argdict);
        break;
    }
    if (uid < 0) {
        *op_errno = -uid;
        if (!found_label) {
            gf_msg(this->name, GF_LOG_ERROR, *op_errno,
                   GD_MSG_MNTBROKER_LABEL_MISS,
                   "Missing mspec: Check the corresponding option "
                   "in glusterd vol file for mountbroker user: %s",
                   label);
        }
        goto out;
    }

    /* some sanity check on arguments */
    seq_dict_foreach(argdict, _volname_get, &volname);
    if (!volname) {
        *op_errno = EINVAL;
        gf_msg(this->name, GF_LOG_ERROR, EINVAL, GD_MSG_DICT_GET_FAILED,
               "Dict get failed for the key 'volname'");
        goto out;
    }
    if (glusterd_volinfo_find(volname, &vol) != 0 ||
        !glusterd_is_volume_started(vol)) {
        *op_errno = ENOENT;
        gf_msg(this->name, GF_LOG_ERROR, *op_errno, GD_MSG_MOUNT_REQ_FAIL,
               "Either volume is not started or volinfo not found");
        goto out;
    }

    /* go do mount */

    /** create actual mount dir */

    /*** "overload" string name to be possible to used for cookie
         creation, see below */
    ret = gf_asprintf(&mtptemp, "%s/user%d/mtpt-%s-XXXXXX/cookie",
                      mountbroker_root, uid, label);
    if (ret == -1) {
        mtptemp = NULL;
        *op_errno = ENOMEM;
        goto out;
    }
    /*** hide cookie part */
    cookieswitch = strrchr(mtptemp, '/');
    *cookieswitch = '\0';

    sla = strrchr(mtptemp, '/');
    *sla = '\0';
    ret = sys_mkdir(mtptemp, 0700);
    if (ret == 0)
        ret = sys_chown(mtptemp, uid, 0);
    else if (errno == EEXIST)
        ret = 0;
    if (ret == -1) {
        *op_errno = errno;
        gf_msg(this->name, GF_LOG_ERROR, *op_errno, GD_MSG_SYSCALL_FAIL,
               "Mountbroker User directory creation failed");
        goto out;
    }
    ret = sys_lstat(mtptemp, &st);
    if (ret == -1) {
        *op_errno = errno;
        gf_msg(this->name, GF_LOG_ERROR, *op_errno, GD_MSG_SYSCALL_FAIL,
               "stat on mountbroker user directory failed");
        goto out;
    }
    if (!(S_ISDIR(st.st_mode) && (st.st_mode & ~S_IFMT) == 0700 &&
          st.st_uid == uid && st.st_gid == 0)) {
        *op_errno = EACCES;
        gf_msg(this->name, GF_LOG_ERROR, *op_errno, GD_MSG_MOUNT_REQ_FAIL,
               "Incorrect mountbroker user directory attributes");
        goto out;
    }
    *sla = '/';

    if (!mkdtemp(mtptemp)) {
        *op_errno = errno;
        gf_msg(this->name, GF_LOG_ERROR, *op_errno, GD_MSG_SYSCALL_FAIL,
               "Mountbroker mount directory creation failed");
        goto out;
    }

    /** create private "cookie" symlink */

    /*** occupy an entry in the hive dir via mkstemp */
    ret = gf_asprintf(&cookie, "%s/" MB_HIVE "/mntXXXXXX", mountbroker_root);
    if (ret == -1) {
        cookie = NULL;
        *op_errno = ENOMEM;
        goto out;
    }
    orig_umask = umask(S_IRWXG | S_IRWXO);
    ret = mkstemp(cookie);
    umask(orig_umask);
    if (ret == -1) {
        *op_errno = errno;
        gf_msg(this->name, GF_LOG_ERROR, *op_errno, GD_MSG_SYSCALL_FAIL,
               "Mountbroker cookie file creation failed");
        goto out;
    }
    sys_close(ret);

    /*** assembly the path from cookie to mountpoint */
    sla = strchr(sla - 1, '/');
    GF_ASSERT(sla);
    ret = gf_asprintf(&mntlink, "../user%d%s", uid, sla);
    if (ret == -1) {
        *op_errno = ENOMEM;
        goto out;
    }

    /*** create cookie link in (to-be) mountpoint,
         move it over to the final place */
    *cookieswitch = '/';
    ret = sys_symlink(mntlink, mtptemp);
    if (ret != -1)
        ret = sys_rename(mtptemp, cookie);
    *cookieswitch = '\0';
    if (ret == -1) {
        *op_errno = errno;
        gf_msg(this->name, GF_LOG_ERROR, *op_errno, GD_MSG_SYSCALL_FAIL,
               "symlink or rename failed");
        goto out;
    }

    /** invoke glusterfs on the mountpoint */

    runinit(&runner);
    runner_add_arg(&runner, SBIN_DIR "/glusterfs");
    seq_dict_foreach(argdict, _runner_add, &runner);
    runner_add_arg(&runner, mtptemp);
    ret = runner_run_reuse(&runner);
    if (ret == -1) {
        *op_errno = EIO; /* XXX hacky fake */
        runner_log(&runner, "", GF_LOG_ERROR, "command failed");
    }
    runner_end(&runner);

out:

    if (*op_errno) {
        ret = -1;
        gf_msg(this->name, GF_LOG_WARNING, *op_errno, GD_MSG_MOUNT_REQ_FAIL,
               "unsuccessful mount request");
        if (mtptemp) {
            *cookieswitch = '/';
            sys_unlink(mtptemp);
            *cookieswitch = '\0';
            sys_rmdir(mtptemp);
        }
        if (cookie) {
            sys_unlink(cookie);
            GF_FREE(cookie);
        }

    } else {
        ret = 0;
        *path = cookie;
    }

    if (mtptemp)
        GF_FREE(mtptemp);
    if (mntlink)
        GF_FREE(mntlink);

    return ret;
}
