/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 *
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

#include <afsconfig.h>
#include "afs/param.h"


#include "afs/sysincludes.h"	/* Standard vendor system headers */
#include "afsincludes.h"	/* Afs-based standard headers */
#include "afs/afs_stats.h"
#include "rx/rx_globals.h"
#include "rx/rxstat.h"
#if !defined(UKERNEL) && !defined(AFS_LINUX_ENV)
#include "net/if.h"
#include "netinet/in_var.h"
#endif /* !defined(UKERNEL) */
#include "rmtsys.h"
#include "pagcb.h"


afs_int32 afs_termState = 0;
afs_int32 afs_gcpags = AFS_GCPAGS;
enum afs_shutdown_state afs_shuttingdown = AFS_RUNNING;
int afs_cold_shutdown = 0;
int afs_resourceinit_flag = 0;
afs_int32 afs_nfs_server_addr;
struct interfaceAddr afs_cb_interface;
struct afs_osi_WaitHandle AFS_WaitHandler;
static struct rx_securityClass *srv_secobj;
static struct rx_securityClass *clt_secobj;
static struct rx_service *stats_svc;
static struct rx_service *pagcb_svc;
static struct rx_connection *rmtsys_conn;
char *afs_sysname = 0;
char *afs_sysnamelist[MAXNUMSYSNAMES];
int afs_sysnamecount = 0;
int afs_sysnamegen = 0;
afs_int32 afs_showflags = GAGUSER | GAGCONSOLE; /* show all messages */


void
afs_Daemon(void)
{
    afs_int32 now, last10MinCheck, last60MinCheck;

    last10MinCheck = 0;
    last60MinCheck = 0;
    while (1) {
	rx_CheckPackets();
	now = osi_Time();

	if (last10MinCheck + 600 < now) {
	    afs_GCUserData();
	}

	if (last60MinCheck + 3600 < now) {
	    afs_int32 didany;
	    afs_GCPAGs(&didany);
	}

	now = 20000 - (osi_Time() - now);
	afs_osi_Wait(now, &AFS_WaitHandler, 0);

	if (afs_termState == AFSOP_STOP_AFS) {
#if defined(RXK_LISTENER_ENV)
	    afs_termState = AFSOP_STOP_RXEVENT;
#else
	    afs_termState = AFSOP_STOP_COMPLETE;
#endif
	    afs_osi_Wakeup(&afs_termState);
	    return;
	}
    }
}


void
afspag_Init(afs_int32 nfs_server_addr)
{
    struct clientcred ccred;
    struct rmtbulk idata, odata;
    afs_int32 code, err, addr, obuf;
    int i;

    afs_uuid_create(&afs_cb_interface.uuid);

    AFS_GLOCK();

    afs_InitStats();
    rx_Init(htons(7001));

    AFS_STATCNT(afs_ResourceInit);
    AFS_RWLOCK_INIT(&afs_xuser, "afs_xuser");
    AFS_RWLOCK_INIT(&afs_xpagcell, "afs_xpagcell");
    AFS_RWLOCK_INIT(&afs_xpagsys, "afs_xpagsys");
    AFS_RWLOCK_INIT(&afs_icl_lock, "afs_icl_lock");

    afs_resourceinit_flag = 1;
    afs_nfs_server_addr = nfs_server_addr;
    for (i = 0; i < MAXNUMSYSNAMES; i++) {
	afs_sysnamelist[i] = afs_osi_Alloc(MAXSYSNAME);
        osi_Assert(afs_sysnamelist[i] != NULL);
    }
    afs_sysname = afs_sysnamelist[0];
    osi_Assert(strlcpy(afs_sysname, SYS_NAME, MAXSYSNAME) < MAXSYSNAME);
    afs_sysnamecount = 1;
    afs_sysnamegen++;

    srv_secobj = rxnull_NewServerSecurityObject();
    stats_svc = rx_NewService(0, RX_STATS_SERVICE_ID, "rpcstats", &srv_secobj,
			      1, RXSTATS_ExecuteRequest);
    pagcb_svc = rx_NewService(0, PAGCB_SERVICEID, "pagcb", &srv_secobj,
			      1, PAGCB_ExecuteRequest);
    rx_StartServer(0);

    clt_secobj = rxnull_NewClientSecurityObject();
    rmtsys_conn = rx_NewConnection(nfs_server_addr, htons(7009),
				   RMTSYS_SERVICEID, clt_secobj, 0);

#ifdef RXK_LISTENER_ENV
    afs_start_thread(rxk_Listener,       "Rx Listener");
#endif
    afs_start_thread((void *)(void *)rx_ServerProc,      "Rx Server Thread");
    afs_start_thread(afs_rxevent_daemon, "Rx Event Daemon");
    afs_start_thread(afs_Daemon,         "AFS PAG Daemon");

    afs_icl_InitLogs();

    AFS_GUNLOCK();

    /* If it's reachable, tell the translator to nuke our creds.
     * We should be more agressive about making sure this gets done,
     * even if the translator is unreachable when we boot.
     */
    addr = obuf = err = 0;
    idata.rmtbulk_len = sizeof(addr);
    idata.rmtbulk_val = (char *)&addr;
    odata.rmtbulk_len = sizeof(obuf);
    odata.rmtbulk_val = (char *)&obuf;
    memset(&ccred, 0, sizeof(ccred));
    code = RMTSYS_Pioctl(rmtsys_conn, &ccred, NIL_PATHP, 0x4F01, 0,
                         &idata, &odata, &err);
}				/*afs_ResourceInit */


/* called with the GLOCK held */
void
afspag_Shutdown(void)
{
    if (afs_shuttingdown != AFS_RUNNING)
	return;
    afs_shuttingdown = AFS_SHUTDOWN;
    afs_termState = AFSOP_STOP_RXCALLBACK;
    rx_WakeupServerProcs();
    while (afs_termState == AFSOP_STOP_RXCALLBACK)
	afs_osi_Sleep(&afs_termState);
    /* rx_ServerProc sets AFS_STOP_AFS */

    while (afs_termState == AFSOP_STOP_AFS) {
	afs_osi_CancelWait(&AFS_WaitHandler);
	afs_osi_Sleep(&afs_termState);
    }
    /* afs_Daemon sets AFS_STOP_RXEVENT */

#if defined(RXK_LISTENER_ENV)
    while (afs_termState == AFSOP_STOP_RXEVENT)
	afs_osi_Sleep(&afs_termState);
    /* afs_rxevent_daemon sets AFSOP_STOP_RXK_LISTENER */

    afs_osi_UnmaskRxkSignals();
    osi_StopListener();
    while (afs_termState == AFSOP_STOP_RXK_LISTENER)
	afs_osi_Sleep(&afs_termState);
    /* rxk_Listener sets AFSOP_STOP_COMPLETE */
#endif
}

static void
token_conversion(char *buffer, int buf_size, int in)
{
    struct ClearToken *ticket;
    afs_int32 *lptr, n;

    /* secret ticket */
    if (buf_size < 4) return;
    lptr = (afs_int32 *)buffer;
    buffer += 4; buf_size -= 4;
    if (in) {
	*lptr = ntohl(*lptr);
	n = *lptr;
    } else {
	n = *lptr;
	*lptr = htonl(*lptr);
    }
    if (n < 0 || buf_size < n) return;
    buffer += n; buf_size -= n;

    /* clear token */
    if (buf_size < 4) return;
    lptr = (afs_int32 *)buffer;
    buffer += 4; buf_size -= 4;
    if (in) {
	*lptr = ntohl(*lptr);
	n = *lptr;
    } else {
	n = *lptr;
	*lptr = htonl(*lptr);
    }
    if (n < 0 || buf_size < n) return;
    if (n >= sizeof(struct ClearToken)) {
	ticket = (struct ClearToken *)buffer;
	if (in) {
	    ticket->AuthHandle     = ntohl(ticket->AuthHandle);
	    ticket->ViceId         = ntohl(ticket->ViceId);
	    ticket->BeginTimestamp = ntohl(ticket->BeginTimestamp);
	    ticket->EndTimestamp   = ntohl(ticket->EndTimestamp);
	} else {
	    ticket->AuthHandle     = htonl(ticket->AuthHandle);
	    ticket->ViceId         = htonl(ticket->ViceId);
	    ticket->BeginTimestamp = htonl(ticket->BeginTimestamp);
	    ticket->EndTimestamp   = htonl(ticket->EndTimestamp);
	}
    }
    buffer += n; buf_size -= n;

    /* primary flag */
    if (buf_size < 4) return;
    lptr = (afs_int32 *)buffer;
    if (in) {
	*lptr = ntohl(*lptr);
    } else {
	*lptr = htonl((*lptr) & ~0x8000);
    }
    return;
}

static void
FetchVolumeStatus_conversion(char *buffer, int buf_size, int in)
{
    AFSFetchVolumeStatus *status = (AFSFetchVolumeStatus *)buffer;

    if (buf_size < sizeof(AFSFetchVolumeStatus))
	return;
    if (in) {
	status->Vid              = ntohl(status->Vid);
	status->ParentId         = ntohl(status->ParentId);
	status->Type             = ntohl(status->Type);
	status->MinQuota         = ntohl(status->MinQuota);
	status->MaxQuota         = ntohl(status->MaxQuota);
	status->BlocksInUse      = ntohl(status->BlocksInUse);
	status->PartBlocksAvail  = ntohl(status->PartBlocksAvail);
	status->PartMaxBlocks    = ntohl(status->PartMaxBlocks);
    } else {
	status->Vid              = htonl(status->Vid);
	status->ParentId         = htonl(status->ParentId);
	status->Type             = htonl(status->Type);
	status->MinQuota         = htonl(status->MinQuota);
	status->MaxQuota         = htonl(status->MaxQuota);
	status->BlocksInUse      = htonl(status->BlocksInUse);
	status->PartBlocksAvail  = htonl(status->PartBlocksAvail);
	status->PartMaxBlocks    = htonl(status->PartMaxBlocks);
    }
}

static void
inparam_conversion(int cmd, char *buffer, int buf_size, int in)
{
    afs_int32 *lptr = (afs_int32 *)buffer;

    switch (cmd & 0xffff) {
	case (0x5600 |  3): /* VIOCSETTOK */
	    token_conversion(buffer, buf_size, in);
	    return;

	case (0x5600 |  5): /* VIOCSETVOLSTAT */
	    FetchVolumeStatus_conversion(buffer, buf_size, in);
	    return;

	case (0x5600 |  8): /* VIOCGETTOK */
	case (0x5600 | 10): /* VIOCCKSERV */
	case (0x5600 | 20): /* VIOCACCESS */
	case (0x5600 | 24): /* VIOCSETCACHESIZE */
	case (0x5600 | 27): /* VIOCGETCELL */
	case (0x5600 | 32): /* VIOC_AFS_MARINER_HOST */
	case (0x5600 | 34): /* VIOC_VENUSLOG */
	case (0x5600 | 38): /* VIOC_AFS_SYSNAME */
	case (0x5600 | 39): /* VIOC_EXPORTAFS */
	    /* one 32-bit integer */
	    if (buf_size >= 4) {
		if (in) lptr[0] = ntohl(lptr[0]);
		else    lptr[0] = htonl(lptr[0]);
	    }
	    return;

	case (0x5600 | 36): /* VIOCSETCELLSTATUS */
	    /* two 32-bit integers */
	    if (buf_size >= 4) {
		if (in) lptr[0] = ntohl(lptr[0]);
		else    lptr[0] = htonl(lptr[0]);
	    }
	    if (buf_size >= 8) {
		if (in) lptr[1] = ntohl(lptr[1]);
		else    lptr[1] = htonl(lptr[1]);
	    }
	    return;
    }
}

static void
outparam_conversion(int cmd, char *buffer, int buf_size, int in)
{
    afs_int32 *lptr = (afs_int32 *)buffer;
    int i;

    switch (cmd & 0xffff) {
	case (0x5600 |  4): /* VIOCGETVOLSTAT */
	case (0x5600 |  5): /* VIOCSETVOLSTAT */
	    FetchVolumeStatus_conversion(buffer, buf_size, in);
	    return;

	case (0x5600 |  8): /* VIOCGETTOK */
	    token_conversion(buffer, buf_size, in);
	    return;

	case (0x5600 | 12): /* VIOCCKCONN */
	case (0x5600 | 32): /* VIOC_AFS_MARINER_HOST */
	case (0x5600 | 34): /* VIOC_VENUSLOG */
	case (0x5600 | 35): /* VIOC_GETCELLSTATUS */
	case (0x5600 | 38): /* VIOC_AFS_SYSNAME */
	case (0x5600 | 39): /* VIOC_EXPORTAFS */
	    /* one 32-bit integer */
	    if (buf_size >= 4) {
		if (in) lptr[0] = ntohl(lptr[0]);
		else    lptr[0] = htonl(lptr[0]);
	    }
	    return;

	case (0x5600 | 40): /* VIOCGETCACHEPARMS */
	    /* sixteen 32-bit integers */
	    for (i = 0; i < 16 && buf_size >= 4; i++) {
		if (in) lptr[i] = ntohl(lptr[i]);
		else    lptr[i] = htonl(lptr[i]);
		buf_size -= 4;
	    }
	    return;
    }
}


/* called with the GLOCK held */
int
afs_syscall_pioctl(char *path, unsigned int com, caddr_t cmarg, int follow)
{
    cred_t *credp = crref();	/* don't free until done! */
    struct afs_ioctl data;
    struct clientcred ccred;
    struct rmtbulk idata, odata;
    short in_size, out_size;
    afs_int32 code = 0, pag, err;
    gid_t g0, g1;
    char *abspath, *pathbuf = 0;

    AFS_STATCNT(afs_syscall_pioctl);
    if (follow)
	follow = 1;		/* compat. with old venus */
    code = copyin_afs_ioctl(cmarg, &data);
    if (code) goto out;

    if ((com & 0xff) == 90) {
	/* PSetClientContext, in any space */
	code = EINVAL;
	goto out;
    }

    /* Special handling for a few pioctls */
    switch (com & 0xffff) {
	case (0x5600 |  3): /* VIOCSETTOK */
	    code = afspag_PSetTokens(data.in, data.in_size, &credp);
	    if (code) goto out;
	    break;

	case (0x5600 |  9): /* VIOCUNLOG */
	case (0x5600 | 21): /* VIOCUNPAG */
	    code = afspag_PUnlog(data.in, data.in_size, &credp);
	    if (code) goto out;
	    break;

	case (0x5600 | 38): /* VIOC_AFS_SYSNAME */
	    code = afspag_PSetSysName(data.in, data.in_size, &credp);
	    if (code) goto out;
	    break;
    }

    /* Set up credentials */
    memset(&ccred, 0, sizeof(ccred));
    pag = PagInCred(credp);
    ccred.uid = afs_cr_uid(credp);
    if (pag != NOPAG) {
	 afs_get_groups_from_pag(pag, &g0, &g1);
	 ccred.group0 = g0;
	 ccred.group1 = g1;
    }

    /*
     * Copy the path and convert to absolute, if one was given.
     * NB: We can only use osI_AllocLargeSpace here as long as
     * RMTSYS_MAXPATHLEN is less than AFS_LRALLOCSIZ.
     */
    if (path) {
	pathbuf = osi_AllocLargeSpace(RMTSYS_MAXPATHLEN);
	if (!pathbuf) {
	    code = ENOMEM;
	    goto out;
	}
	code = osi_abspath(path, pathbuf, RMTSYS_MAXPATHLEN, 0, &abspath);
	if (code)
	    goto out_path;
    } else {
	abspath = NIL_PATHP;
    }

    /* Allocate, copy, and convert incoming data */
    idata.rmtbulk_len = in_size = data.in_size;
    if (in_size  < 0 || in_size  > MAXBUFFERLEN) {
	code = EINVAL;
	goto out_path;
    }
    if (in_size > AFS_LRALLOCSIZ)
	 idata.rmtbulk_val = osi_Alloc(in_size);
    else
	 idata.rmtbulk_val = osi_AllocLargeSpace(AFS_LRALLOCSIZ);
    if (!idata.rmtbulk_val) {
	code = ENOMEM;
	goto out_path;
    }
    if (in_size) {
	AFS_COPYIN(data.in, idata.rmtbulk_val, in_size, code);
	if (code)
	    goto out_idata;
	inparam_conversion(com, idata.rmtbulk_val, in_size, 0);
    }

    /* Allocate space for outgoing data */
    odata.rmtbulk_len = out_size = data.out_size;
    if (out_size < 0 || out_size > MAXBUFFERLEN) {
	code = EINVAL;
	goto out_idata;
    }
    if (out_size > AFS_LRALLOCSIZ)
	 odata.rmtbulk_val = osi_Alloc(out_size);
    else
	 odata.rmtbulk_val = osi_AllocLargeSpace(AFS_LRALLOCSIZ);
    if (!odata.rmtbulk_val) {
	code = ENOMEM;
	goto out_idata;
    }

    memset(odata.rmtbulk_val, 0, out_size);

    AFS_GUNLOCK();
    code = RMTSYS_Pioctl(rmtsys_conn, &ccred, abspath, com, follow,
			 &idata, &odata, &err);
    AFS_GLOCK();
    if (code)
	goto out_odata;

    /* Convert and copy out the result */
    if (odata.rmtbulk_len > out_size) {
	code = E2BIG;
	goto out_odata;
    }
    if (odata.rmtbulk_len) {
	outparam_conversion(com, odata.rmtbulk_val, odata.rmtbulk_len, 1);
	AFS_COPYOUT(odata.rmtbulk_val, data.out, odata.rmtbulk_len, code);
    }
    if (!code)
	code = err;

out_odata:
    if (out_size > AFS_LRALLOCSIZ)
	osi_Free(odata.rmtbulk_val, out_size);
    else
	osi_FreeLargeSpace(odata.rmtbulk_val);

out_idata:
    if (in_size > AFS_LRALLOCSIZ)
	osi_Free(idata.rmtbulk_val, in_size);
    else
	osi_FreeLargeSpace(idata.rmtbulk_val);

out_path:
    if (path)
	osi_FreeLargeSpace(pathbuf);

out:
    crfree(credp);
#if defined(KERNEL_HAVE_UERROR)
    if (!getuerror())
	setuerror(code);
    return (getuerror());
#else
    return (code);
#endif
}


int
afs_syscall_call(long parm, long parm2, long parm3,
		 long parm4, long parm5, long parm6)
{
    /* Superusers may shut us down, such as with afsd -shutdown. */
    if (parm == AFSOP_SHUTDOWN && afs_suser(NULL))
    {
	AFS_GLOCK();
	afspag_Shutdown();
	AFS_GUNLOCK();
	return 0;
    }

    /* otherwise, we don't support afs_syscall_call, period */
#if defined(KERNEL_HAVE_UERROR)
    setuerror(EPERM);
#endif
    return EPERM;
}
