/*
  Copyright (c) 2012 Gluster, Inc. <http://www.gluster.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 "xdr-nfs3.h"
#include <glusterfs/logging.h>
#include <glusterfs/mem-pool.h>
#include "nfs-mem-types.h"
#include "nfs-messages.h"
#include "mount3.h"
#include <stdio.h>
#include <stdlib.h>
#include <rpc/pmap_clnt.h>
#include <string.h>
#include <memory.h>
#include <sys/socket.h>
#include <netinet/in.h>

extern struct nfs3_fh *
nfs3_rootfh(struct svc_req *req, xlator_t *nfsx, char *dp, char *expname);

extern mountres3
mnt3svc_set_mountres3(mountstat3 stat, struct nfs3_fh *fh, int *authflavor,
                      u_int aflen);
extern int
mount3udp_add_mountlist(xlator_t *nfsx, char *host, char *expname);

extern int
mount3udp_delete_mountlist(xlator_t *nfsx, char *host, char *expname);

extern mountstat3
mnt3svc_errno_to_mnterr(int32_t errnum);

/* only this thread will use this, no locking needed */
char mnthost[INET_ADDRSTRLEN + 1];

#define MNT3UDP_AUTH_LEN 1 /* Only AUTH_UNIX for now */

mountres3 *
mountudpproc3_mnt_3_svc(dirpath **dpp, struct svc_req *req)
{
    struct mountres3 *res = NULL;
    int *autharr = NULL;
    struct nfs3_fh *fh = NULL;
    char *mpath = NULL;
    xlator_t *nfsx = THIS;
    char expname[PATH_MAX] = {
        0,
    };
    mountstat3 stat = MNT3ERR_SERVERFAULT;

    errno = 0; /* RESET errno */

    mpath = (char *)*dpp;
    while (*mpath == '/')
        mpath++;

    res = GF_CALLOC(1, sizeof(*res), gf_nfs_mt_mountres3);
    if (res == NULL) {
        gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
               "Unable to allocate memory");
        goto err;
    }
    autharr = GF_CALLOC(MNT3UDP_AUTH_LEN, sizeof(int), gf_nfs_mt_int);
    if (autharr == NULL) {
        gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
               "Unable to allocate memory");
        goto err;
    }

    autharr[0] = AUTH_UNIX;

    fh = nfs3_rootfh(req, nfsx, mpath, (char *)expname);

    /* FAILURE: No FH */
    if (fh == NULL) {
        gf_msg(GF_MNT, GF_LOG_ERROR, errno, NFS_MSG_GET_FH_FAIL,
               "Unable to get fh for %s", mpath);
        if (errno)
            stat = mnt3svc_errno_to_mnterr(errno);
        *res = mnt3svc_set_mountres3(stat, NULL /* fh */, autharr,
                                     MNT3UDP_AUTH_LEN);
        return res;
    }

    /* SUCCESS */
    stat = MNT3_OK;
    *res = mnt3svc_set_mountres3(stat, fh, autharr, MNT3UDP_AUTH_LEN);
    (void)mount3udp_add_mountlist(nfsx, mnthost, (char *)expname);
    return res;

err:
    GF_FREE(fh);
    GF_FREE(res);
    GF_FREE(autharr);
    return NULL;
}

mountstat3 *
mountudpproc3_umnt_3_svc(dirpath **dp, struct svc_req *req)
{
    mountstat3 *stat = NULL;
    char *mpath = (char *)*dp;
    xlator_t *nfsx = THIS;

    stat = GF_MALLOC(sizeof(mountstat3), gf_nfs_mt_mountstat3);
    if (stat == NULL) {
        gf_msg(GF_MNT, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
               "Unable to allocate memory");
        return NULL;
    }
    *stat = MNT3_OK;
    (void)mount3udp_delete_mountlist(nfsx, mnthost, mpath);
    return stat;
}

static void
mountudp_program_3(struct svc_req *rqstp, register SVCXPRT *transp)
{
    union {
        dirpath mountudpproc3_mnt_3_arg;
    } argument;
    char *result = NULL;
    xdrproc_t _xdr_argument = NULL, _xdr_result = NULL;
    char *(*local)(char *, struct svc_req *) = NULL;
    mountres3 *res = NULL;
    struct sockaddr_in *sin = NULL;

#if !defined(_TIRPC_SVC_H)
    sin = svc_getcaller(transp);
#else
    sin = (struct sockaddr_in *)svc_getcaller(transp);
    /* TIRPC's svc_getcaller() returns a pointer to a sockaddr_in6, even
     * though it might actually be an IPv4 address. It ought return a
     * struct sockaddr and make the caller upcast it to the proper
     * address family. Sigh.
     */
#endif
    /* And let's make sure that it's actually an IPv4 address. */
    GF_ASSERT(sin->sin_family == AF_INET);

    inet_ntop(AF_INET, &sin->sin_addr, mnthost, INET_ADDRSTRLEN + 1);

    switch (rqstp->rq_proc) {
        case NULLPROC:
            (void)svc_sendreply(transp, (xdrproc_t)xdr_void, (char *)NULL);
            return;

        case MOUNT3_MNT:
            _xdr_argument = (xdrproc_t)xdr_dirpath;
            _xdr_result = (xdrproc_t)xdr_mountres3;
            local = (char *(*)(char *,
                               struct svc_req *))mountudpproc3_mnt_3_svc;
            break;

        case MOUNT3_UMNT:
            _xdr_argument = (xdrproc_t)xdr_dirpath;
            _xdr_result = (xdrproc_t)xdr_mountstat3;
            local = (char *(*)(char *,
                               struct svc_req *))mountudpproc3_umnt_3_svc;
            break;

        default:
            svcerr_noproc(transp);
            return;
    }
    memset((char *)&argument, 0, sizeof(argument));
    if (!svc_getargs(transp, (xdrproc_t)_xdr_argument, (caddr_t)&argument)) {
        svcerr_decode(transp);
        return;
    }
    result = (*local)((char *)&argument, rqstp);
    if (result == NULL) {
        gf_msg_debug(GF_MNT, 0, "PROC returned error");
        svcerr_systemerr(transp);
    }
    if (result != NULL &&
        !svc_sendreply(transp, (xdrproc_t)_xdr_result, result)) {
        gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_SVC_ERROR,
               "svc_sendreply returned error");
        svcerr_systemerr(transp);
    }
    if (!svc_freeargs(transp, (xdrproc_t)_xdr_argument, (caddr_t)&argument)) {
        gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_ARG_FREE_FAIL,
               "Unable to free arguments");
    }
    if (result == NULL)
        return;
    /* free the result */
    switch (rqstp->rq_proc) {
        case MOUNT3_MNT:
            res = (mountres3 *)result;
            GF_FREE(res->mountres3_u.mountinfo.fhandle.fhandle3_val);
            GF_FREE(res->mountres3_u.mountinfo.auth_flavors.auth_flavors_val);
            GF_FREE(res);
            break;

        case MOUNT3_UMNT:
            GF_FREE(result);
            break;
    }
    return;
}

void *
mount3udp_thread(void *argv)
{
    xlator_t *nfsx = argv;
    register SVCXPRT *transp = NULL;

    GF_ASSERT(nfsx);

    THIS = nfsx;

    transp = svcudp_create(RPC_ANYSOCK);
    if (transp == NULL) {
        gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_SVC_ERROR,
               "svcudp_create error");
        return NULL;
    }
    if (!svc_register(transp, MOUNT_PROGRAM, MOUNT_V3, mountudp_program_3,
                      IPPROTO_UDP)) {
        gf_msg(GF_MNT, GF_LOG_ERROR, 0, NFS_MSG_SVC_ERROR,
               "svc_register error");
        return NULL;
    }

    nfs_start_rpc_poller(nfsx->private);

    return NULL;
}
