/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
 * vim:expandtab:shiftwidth=8:tabstop=8:
 *
 * Copyright (C) 2001, 2002 Cluster File Systems, Inc. <braam@clusterfs.com>
 * Copyright (C) 2001 Tacit Networks, Inc. <phil@off.net>
 *
 *   This file is part of InterMezzo, http://www.inter-mezzo.org.
 *
 *   InterMezzo is free software; you can redistribute it and/or
 *   modify it under the terms of version 2 of the GNU General Public
 *   License as published by the Free Software Foundation.
 *
 *   InterMezzo is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with InterMezzo; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Mostly platform independent upcall operations to a cache manager:
 *  -- upcalls
 *  -- upcall routines
 *
 */

#include <asm/system.h>
#include <asm/segment.h>
#include <asm/signal.h>
#include <linux/signal.h>

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/stat.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <asm/uaccess.h>

#include "intermezzo_lib.h"
#include "intermezzo_fs.h"
#include "intermezzo_psdev.h"

#include "intermezzo_idl.h"

/*
  At present:
  -- Asynchronous calls:
   - kml:            give a "more" kml indication to userland
   - kml_truncate:   initiate KML truncation
   - release_permit: kernel is done with permit
  -- Synchronous
   - open:           fetch file
   - permit:         get a permit

  Errors returned by user level code are positive

 */

static struct izo_upcall_hdr *upc_pack(__u32 opcode, int pathlen, char *path,
                                       char *fsetname, int reclen, char *rec,
                                       int *size)
{
        struct izo_upcall_hdr *hdr;
        char *ptr;
        ENTRY;

        *size = sizeof(struct izo_upcall_hdr);
        if ( fsetname ) {
                *size += round_strlen(fsetname);
        }
        if ( path ) { 
                *size += round_strlen(path);
        }
        if ( rec ) { 
                *size += size_round(reclen);
        }
        PRESTO_ALLOC(hdr, *size);
        if (!hdr) { 
                CERROR("intermezzo upcall: out of memory (opc %d)\n", opcode);
                EXIT;
                return NULL;
        }
        memset(hdr, 0, *size);

        ptr = (char *)hdr + sizeof(*hdr);

        /* XXX do we need fsuid ? */
        hdr->u_len = *size;
        hdr->u_version = IZO_UPC_VERSION;
        hdr->u_opc = opcode;
        hdr->u_pid = current->pid;
        hdr->u_uid = current->fsuid;

        if (path) { 
                /*XXX Robert: please review what len to pass in for 
                  NUL terminated strings */
                hdr->u_pathlen = strlen(path);
                LOGL0(path, hdr->u_pathlen, ptr);
        }
        if (fsetname) { 
                hdr->u_fsetlen = strlen(fsetname);
                LOGL0(fsetname, strlen(fsetname), ptr);
        }
        if (rec) { 
                hdr->u_reclen = reclen;
                LOGL(rec, reclen, ptr);
        }
        
        EXIT;
        return hdr;
}

/* the upcalls */
int izo_upc_kml(int minor, __u64 offset, __u32 first_recno, __u64 length, __u32 last_recno, char *fsetname)
{
        int size;
        int error;
        struct izo_upcall_hdr *hdr;

        ENTRY;
        if (!presto_lento_up(minor)) {
                EXIT;
                return 0;
        }

        hdr = upc_pack(IZO_UPC_KML, 0, NULL, fsetname, 0, NULL, &size);
        if (!hdr || IS_ERR(hdr)) {
                EXIT;
                return -PTR_ERR(hdr);
        }

        hdr->u_offset = offset;
        hdr->u_first_recno = first_recno;
        hdr->u_length = length;
        hdr->u_last_recno = last_recno;

        CDEBUG(D_UPCALL, "KML: fileset %s, offset %Lu, length %Lu, "
               "first %u, last %d; minor %d\n",
               fsetname,
               (unsigned long long) hdr->u_offset,
               (unsigned long long) hdr->u_length,
               hdr->u_first_recno,
               hdr->u_last_recno, minor);

        error = izo_upc_upcall(minor, &size, hdr, ASYNCHRONOUS);

        EXIT;
        return -error;
}

int izo_upc_kml_truncate(int minor, __u64 length, __u32 last_recno, char *fsetname)
{
        int size;
        int error;
        struct izo_upcall_hdr *hdr;

        ENTRY;
        if (!presto_lento_up(minor)) {
                EXIT;
                return 0;
        }

        hdr = upc_pack(IZO_UPC_KML_TRUNC, 0, NULL, fsetname, 0, NULL, &size);
        if (!hdr || IS_ERR(hdr)) {
                EXIT;
                return -PTR_ERR(hdr);
        }

        hdr->u_length = length;
        hdr->u_last_recno = last_recno;

        CDEBUG(D_UPCALL, "KML TRUNCATE: fileset %s, length %Lu, "
               "last recno %d, minor %d\n",
               fsetname,
               (unsigned long long) hdr->u_length,
               hdr->u_last_recno, minor);

        error = izo_upc_upcall(minor, &size, hdr, ASYNCHRONOUS);

        EXIT;
        return error;
}

int izo_upc_open(int minor, __u32 pathlen, char *path, char *fsetname, struct lento_vfs_context *info)
{
        int size;
        int error;
        struct izo_upcall_hdr *hdr;
        ENTRY;

        if (!presto_lento_up(minor)) {
                EXIT;
                return -EIO;
        }

        hdr = upc_pack(IZO_UPC_OPEN, pathlen, path, fsetname, 
                       sizeof(*info), (char*)info, &size);
        if (!hdr || IS_ERR(hdr)) {
                EXIT;
                return -PTR_ERR(hdr);
        }

        CDEBUG(D_UPCALL, "path %s\n", path);

        error = izo_upc_upcall(minor, &size, hdr, SYNCHRONOUS);
        if (error)
                CERROR("InterMezzo: %s: error %d\n", __FUNCTION__, error);

        EXIT;
        return -error;
}

int izo_upc_get_fileid(int minor, __u32 reclen, char *rec, 
                       __u32 pathlen, char *path, char *fsetname)
{
        int size;
        int error;
        struct izo_upcall_hdr *hdr;
        ENTRY;

        if (!presto_lento_up(minor)) {
                EXIT;
                return -EIO;
        }

        hdr = upc_pack(IZO_UPC_GET_FILEID, pathlen, path, fsetname, reclen, rec, &size);
        if (!hdr || IS_ERR(hdr)) {
                EXIT;
                return -PTR_ERR(hdr);
        }

        CDEBUG(D_UPCALL, "path %s\n", path);

        error = izo_upc_upcall(minor, &size, hdr, SYNCHRONOUS);
        if (error)
                CERROR("InterMezzo: %s: error %d\n", __FUNCTION__, error);

        EXIT;
        return -error;
}

int izo_upc_backfetch(int minor, char *path, char *fsetname, struct lento_vfs_context *info)
{
        int size;
        int error;
        struct izo_upcall_hdr *hdr;
        ENTRY;

        if (!presto_lento_up(minor)) {
                EXIT;
                return -EIO;
        }

        hdr = upc_pack(IZO_UPC_BACKFETCH, strlen(path), path, fsetname, 
                       sizeof(*info), (char *)info, &size);
        if (!hdr || IS_ERR(hdr)) {
                EXIT;
                return -PTR_ERR(hdr);
        }

        /* This is currently synchronous, kml_reint_record blocks */
        error = izo_upc_upcall(minor, &size, hdr, SYNCHRONOUS);
        if (error)
                CERROR("InterMezzo: %s: error %d\n", __FUNCTION__, error);

        EXIT;
        return -error;
}

int izo_upc_permit(int minor, struct dentry *dentry, __u32 pathlen, char *path,
                   char *fsetname)
{
        int size;
        int error;
        struct izo_upcall_hdr *hdr;

        ENTRY;

        hdr = upc_pack(IZO_UPC_PERMIT, pathlen, path, fsetname, 0, NULL, &size);
        if (!hdr || IS_ERR(hdr)) {
                EXIT;
                return -PTR_ERR(hdr);
        }

        CDEBUG(D_UPCALL, "Permit minor %d path %s\n", minor, path);

        error = izo_upc_upcall(minor, &size, hdr, SYNCHRONOUS);

        if (error == -EROFS) {
                int err;
                CERROR("InterMezzo: ERROR - requested permit for read-only "
                       "fileset.\n   Setting \"%s\" read-only!\n", path);
                err = izo_mark_cache(dentry, 0xFFFFFFFF, CACHE_CLIENT_RO, NULL);
                if (err)
                        CERROR("InterMezzo ERROR: mark_cache %d\n", err);
        } else if (error) {
                CERROR("InterMezzo: %s: error %d\n", __FUNCTION__, error);
        }

        EXIT;
        return error;
}

/* This is a ping-pong upcall handled on the server when a client (uuid)
 * requests the permit for itself. */
int izo_upc_revoke_permit(int minor, char *fsetname, __u8 uuid[16])
{
        int size;
        int error;
        struct izo_upcall_hdr *hdr;

        ENTRY;

        hdr = upc_pack(IZO_UPC_REVOKE_PERMIT, 0, NULL, fsetname, 0, NULL, &size);
        if (!hdr || IS_ERR(hdr)) {
                EXIT;
                return -PTR_ERR(hdr);
        }

        memcpy(hdr->u_uuid, uuid, sizeof(hdr->u_uuid));

        error = izo_upc_upcall(minor, &size, hdr, SYNCHRONOUS);

        if (error)
                CERROR("InterMezzo: %s: error %d\n", __FUNCTION__, error);

        EXIT;
        return -error;
}

int izo_upc_go_fetch_kml(int minor, char *fsetname, __u8 uuid[16],
                         __u64 kmlsize)
{
        int size;
        int error;
        struct izo_upcall_hdr *hdr;
        ENTRY;

        if (!presto_lento_up(minor)) {
                EXIT;
                return -EIO;
        }

        hdr = upc_pack(IZO_UPC_GO_FETCH_KML, 0, NULL, fsetname, 0, NULL, &size);
        if (!hdr || IS_ERR(hdr)) {
                EXIT;
                return -PTR_ERR(hdr);
        }

        hdr->u_offset = kmlsize;
        memcpy(hdr->u_uuid, uuid, sizeof(hdr->u_uuid));

        error = izo_upc_upcall(minor, &size, hdr, ASYNCHRONOUS);
        if (error)
                CERROR("%s: error %d\n", __FUNCTION__, error);

        EXIT;
        return -error;
}

int izo_upc_connect(int minor, __u64 ip_address, __u64 port, __u8 uuid[16],
                    int client_flag)
{
        int size;
        int error;
        struct izo_upcall_hdr *hdr;
        ENTRY;

        if (!presto_lento_up(minor)) {
                EXIT;
                return -EIO;
        }

        hdr = upc_pack(IZO_UPC_CONNECT, 0, NULL, NULL, 0, NULL, &size);
        if (!hdr || IS_ERR(hdr)) {
                EXIT;
                return -PTR_ERR(hdr);
        }

        hdr->u_offset = ip_address;
        hdr->u_length = port;
        memcpy(hdr->u_uuid, uuid, sizeof(hdr->u_uuid));
        hdr->u_first_recno = client_flag;

        error = izo_upc_upcall(minor, &size, hdr, SYNCHRONOUS);
        if (error) {
                CERROR("%s: error %d\n", __FUNCTION__, error);
        }

        EXIT;
        return -error;
}

int izo_upc_set_kmlsize(int minor, char *fsetname, __u8 uuid[16], __u64 kmlsize)
{
        int size;
        int error;
        struct izo_upcall_hdr *hdr;
        ENTRY;

        if (!presto_lento_up(minor)) {
                EXIT;
                return -EIO;
        }

        hdr = upc_pack(IZO_UPC_SET_KMLSIZE, 0, NULL, fsetname, 0, NULL, &size);
        if (!hdr || IS_ERR(hdr)) {
                EXIT;
                return -PTR_ERR(hdr);
        }

        memcpy(hdr->u_uuid, uuid, sizeof(hdr->u_uuid));
        hdr->u_length = kmlsize;

        error = izo_upc_upcall(minor, &size, hdr, SYNCHRONOUS);
        if (error)
                CERROR("%s: error %d\n", __FUNCTION__, error);

        EXIT;
        return -error;
}

int izo_upc_repstatus(int minor,  char * fsetname, struct izo_rcvd_rec *lr_server)
{
        int size;
        int error;
        struct izo_upcall_hdr *hdr;
        ENTRY;

        if (!presto_lento_up(minor)) {
                EXIT;
                return -EIO;
        }

        hdr = upc_pack(IZO_UPC_REPSTATUS, 0, NULL, fsetname, 
                       sizeof(*lr_server), (char*)lr_server, 
                       &size);
        if (!hdr || IS_ERR(hdr)) {
                EXIT;
                return -PTR_ERR(hdr);
        }

        error = izo_upc_upcall(minor, &size, hdr, SYNCHRONOUS);
        if (error)
                CERROR("%s: error %d\n", __FUNCTION__, error);

        EXIT;
        return -error;
}


#if 0
int izo_upc_client_make_branch(int minor, char *fsetname, char *tagname,
                               char *branchname)
{
        int size, error;
        struct izo_upcall_hdr *hdr;
        int pathlen;
        char *path;
        ENTRY;

        hdr = upc_pack(IZO_UPC_CLIENT_MAKE_BRANCH, strlen(tagname), tagname,
                       fsetname, strlen(branchname) + 1, branchname, &size);
        if (!hdr || IS_ERR(hdr)) {
                error = -PTR_ERR(hdr);
                goto error;
        }

        error = izo_upc_upcall(minor, &size, hdr, SYNCHRONOUS);
        if (error)
                CERROR("InterMezzo: error %d\n", error);

 error:
        PRESTO_FREE(path, pathlen);
        EXIT;
        return error;
}
#endif

int izo_upc_server_make_branch(int minor, char *fsetname)
{
        int size, error;
        struct izo_upcall_hdr *hdr;
        ENTRY;

        hdr = upc_pack(IZO_UPC_SERVER_MAKE_BRANCH, 0, NULL, fsetname, 0, NULL, &size);
        if (!hdr || IS_ERR(hdr)) {
                error = -PTR_ERR(hdr);
                goto error;
        }

        error = izo_upc_upcall(minor, &size, hdr, SYNCHRONOUS);
        if (error)
                CERROR("InterMezzo: error %d\n", error);

 error:
        EXIT;
        return -error;
}

int izo_upc_branch_undo(int minor, char *fsetname, char *branchname)
{
        int size;
        int error;
        struct izo_upcall_hdr *hdr;
        ENTRY;

        if (!presto_lento_up(minor)) {
                EXIT;
                return -EIO;
        }

        hdr = upc_pack(IZO_UPC_BRANCH_UNDO, strlen(branchname), branchname,
                       fsetname, 0, NULL, &size);
        if (!hdr || IS_ERR(hdr)) {
                EXIT;
                return -PTR_ERR(hdr);
        }

        error = izo_upc_upcall(minor, &size, hdr, SYNCHRONOUS);
        if (error)
                CERROR("InterMezzo: %s: error %d\n", __FUNCTION__, error);

        EXIT;
        return -error;
}

int izo_upc_branch_redo(int minor, char *fsetname, char *branchname)
{
        int size;
        int error;
        struct izo_upcall_hdr *hdr;
        ENTRY;

        if (!presto_lento_up(minor)) {
                EXIT;
                return -EIO;
        }

        hdr = upc_pack(IZO_UPC_BRANCH_REDO, strlen(branchname) + 1, branchname,
                       fsetname, 0, NULL, &size);
        if (!hdr || IS_ERR(hdr)) {
                EXIT;
                return -PTR_ERR(hdr);
        }

        error = izo_upc_upcall(minor, &size, hdr, SYNCHRONOUS);
        if (error)
                CERROR("InterMezzo: %s: error %d\n", __FUNCTION__, error);

        EXIT;
        return -error;
}
