/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
 * vim:expandtab:shiftwidth=8:tabstop=8:
 *
 *  Copyright (C) 2001 Cluster File Systems, Inc. <braam@clusterfs.com>
 *
 *   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.
 *
 * Reintegration of KML records
 *
 */

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <asm/uaccess.h>
#include <asm/pgtable.h>
#include <asm/mmu_context.h>

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

static void kmlreint_pre_secure(struct kml_rec *rec, struct file *dir,
                                struct run_ctxt *saved)
{
        struct run_ctxt ctxt; 
        struct presto_dentry_data *dd = presto_d2d(dir->f_dentry);
        int i;

        ctxt.fsuid = rec->prefix.hdr->fsuid;
        ctxt.fsgid = rec->prefix.hdr->fsgid;
        ctxt.fs = KERNEL_DS; 
        ctxt.pwd = dd->dd_fset->fset_dentry;
        ctxt.pwdmnt = dd->dd_fset->fset_mnt;

        ctxt.root = ctxt.pwd;
        ctxt.rootmnt = ctxt.pwdmnt;
        if (rec->prefix.hdr->ngroups > 0) {
                ctxt.ngroups = rec->prefix.hdr->ngroups;
                for (i = 0; i< ctxt.ngroups; i++) 
                        ctxt.groups[i] = rec->prefix.groups[i];
        } else
                ctxt.ngroups = 0;

        push_ctxt(saved, &ctxt);
}


/* Append two strings in a less-retarded fashion. */
static char * path_join(char *p1, int p1len, char *p2, int p2len)
{
        int size = p1len + p2len + 2; /* possibly one extra /, one NULL */
        char *path;

        path = kmalloc(size, GFP_KERNEL);
        if (path == NULL)
                return NULL;

        memcpy(path, p1, p1len);
        if (path[p1len - 1] != '/') {
                path[p1len] = '/';
                p1len++;
        }
        memcpy(path + p1len, p2, p2len);
        path[p1len + p2len] = '\0';

        return path;
}

static inline int kml_recno_equal(struct kml_rec *rec,
                                  struct presto_file_set *fset)
{
        return (rec->suffix->recno == fset->fset_lento_recno + 1);
}

static inline int version_equal(struct presto_version *a, struct inode *inode)
{
        if (a == NULL)
                return 1;

        if (inode == NULL) {
                CERROR("InterMezzo: NULL inode in version_equal()\n");
                return 0;
        }

        if (inode->i_mtime.tv_sec == a->pv_mtime_sec &&
            inode->i_mtime.tv_nsec == a->pv_mtime_nsec &&
            (S_ISDIR(inode->i_mode) || inode->i_size == a->pv_size))
                return 1;

        return 0;
}

static int reint_close(struct kml_rec *rec, struct file *file,
                       struct lento_vfs_context *given_info)
{
        struct run_ctxt saved_ctxt;
        int error;
        struct presto_file_set *fset;
        struct lento_vfs_context info; 
        ENTRY;

        memcpy(&info, given_info, sizeof(*given_info));


        CDEBUG (D_KML, "=====REINT_CLOSE::%s\n", rec->path);

        fset = presto_fset(file->f_dentry);
        if (fset->fset_flags & FSET_DATA_ON_DEMAND) {
                struct iattr iattr;

                iattr.ia_valid = ATTR_CTIME | ATTR_MTIME | ATTR_SIZE;
                iattr.ia_mtime.tv_sec = (time_t)rec->new_objectv->pv_mtime_sec;
                iattr.ia_mtime.tv_nsec = (time_t)rec->new_objectv->pv_mtime_nsec;
                iattr.ia_ctime.tv_sec = (time_t)rec->new_objectv->pv_ctime_sec;
                iattr.ia_ctime.tv_nsec = (time_t)rec->new_objectv->pv_ctime_nsec;
                iattr.ia_size = (time_t)rec->new_objectv->pv_size;

                /* no kml record, but update last rcvd */
                /* save fileid in dentry for later backfetch */
                info.flags |= LENTO_FL_EXPECT | LENTO_FL_SET_DDFILEID;
                info.remote_ino = rec->ino;
                info.remote_generation = rec->generation;
                info.flags &= ~LENTO_FL_KML;
                kmlreint_pre_secure(rec, file, &saved_ctxt);
                error = lento_setattr(rec->path, &iattr, &info);
                pop_ctxt(&saved_ctxt);

                presto_d2d(file->f_dentry)->dd_flags &= ~PRESTO_DATA;
        } else {
                int minor = presto_f2m(fset);

                info.updated_time.tv_sec = rec->new_objectv->pv_mtime_sec;
                info.updated_time.tv_nsec = rec->new_objectv->pv_mtime_nsec;
                memcpy(&info.remote_version, rec->old_objectv, 
                       sizeof(*rec->old_objectv));
                info.remote_ino = rec->ino;
                info.remote_generation = rec->generation;
                error = izo_upc_backfetch(minor, rec->path, fset->fset_name,
                                          &info);
                if (error) {
                        CERROR("backfetch error %d\n", error);
                        /* if file doesn't exist anymore,  then ignore the CLOSE
                         * and just update the last_rcvd.
                         */
                        if (error == ENOENT) {
                                CDEBUG(D_KML, "manually updating remote offset uuid %s"
                                       "recno %d offset %Lu\n", info.uuid, info.recno, info.kml_offset);
                                error = izo_rcvd_upd_remote(fset, info.uuid, info.recno, info.kml_offset);
                                if(error)
                                        CERROR("izo_rcvd_upd_remote error %d\n", error);

                        } 
                }
                        
                /* propagate error to avoid further reint */
        }

        EXIT;
        return error;
}

static int reint_create(struct kml_rec *rec, struct file *dir,
                        struct lento_vfs_context *info)
{
        struct run_ctxt saved_ctxt;
        int     error;        ENTRY;

        CDEBUG (D_KML, "=====REINT_CREATE::%s\n", rec->path);
        info->updated_time.tv_sec = rec->new_objectv->pv_ctime_sec;
        info->updated_time.tv_nsec = rec->new_objectv->pv_ctime_nsec;
        kmlreint_pre_secure(rec, dir, &saved_ctxt);
        error = lento_create(rec->path, rec->mode, info);
        pop_ctxt(&saved_ctxt); 

        EXIT;
        return error;
}

static int reint_link(struct kml_rec *rec, struct file *dir,
                      struct lento_vfs_context *info)
{
        struct run_ctxt saved_ctxt;
        int     error;

        ENTRY;

        CDEBUG (D_KML, "=====REINT_LINK::%s -> %s\n", rec->path, rec->target);
        info->updated_time.tv_sec = rec->new_objectv->pv_mtime_sec;
        info->updated_time.tv_nsec = rec->new_objectv->pv_mtime_nsec;
        kmlreint_pre_secure(rec, dir, &saved_ctxt);
        error = lento_link(rec->path, rec->target, info);
        pop_ctxt(&saved_ctxt); 

        EXIT;
        return error;
}

static int reint_mkdir(struct kml_rec *rec, struct file *dir,
                       struct lento_vfs_context *info)
{
        struct run_ctxt saved_ctxt;
        int     error;

        ENTRY;

        CDEBUG (D_KML, "=====REINT_MKDIR::%s\n", rec->path);
        info->updated_time.tv_sec = rec->new_objectv->pv_ctime_sec;
        info->updated_time.tv_nsec = rec->new_objectv->pv_ctime_nsec;
        kmlreint_pre_secure(rec, dir, &saved_ctxt);
        error = lento_mkdir(rec->path, rec->mode, info);
        pop_ctxt(&saved_ctxt); 

        EXIT;
        return error;
}

static int reint_mknod(struct kml_rec *rec, struct file *dir,
                       struct lento_vfs_context *info)
{
        struct run_ctxt saved_ctxt;
        int     error;
	dev_t dev;

        ENTRY;

        CDEBUG (D_KML, "=====REINT_MKNOD::%s\n", rec->path);
        info->updated_time.tv_sec = rec->new_objectv->pv_ctime_sec;
        info->updated_time.tv_nsec = rec->new_objectv->pv_ctime_nsec;
        kmlreint_pre_secure(rec, dir, &saved_ctxt);

        dev = rec->rdev ? old_decode_dev(rec->rdev) : MKDEV(rec->major, rec->minor);

        error = lento_mknod(rec->path, rec->mode, dev, info);
        pop_ctxt(&saved_ctxt); 

        EXIT;
        return error;
}


static int reint_noop(struct kml_rec *rec, struct file *dir,
                      struct lento_vfs_context *info)
{
        return 0;
}

static int reint_rename(struct kml_rec *rec, struct file *dir,
                        struct lento_vfs_context *info)
{
        struct run_ctxt saved_ctxt;
        int     error;

        ENTRY;

        CDEBUG (D_KML, "=====REINT_RENAME::%s -> %s\n", rec->path, rec->target);
        info->updated_time.tv_sec = rec->new_objectv->pv_mtime_sec;
        info->updated_time.tv_nsec = rec->new_objectv->pv_mtime_nsec;
        kmlreint_pre_secure(rec, dir, &saved_ctxt);
        error = lento_rename(rec->path, rec->target, info);
        pop_ctxt(&saved_ctxt); 

        EXIT;
        return error;
}

static int reint_rmdir(struct kml_rec *rec, struct file *dir,
                       struct lento_vfs_context *info)
{
        struct run_ctxt saved_ctxt;
        int     error;
        char *path;

        ENTRY;

        path = path_join(rec->path, rec->pathlen - 1, rec->target, rec->targetlen);
        if (path == NULL) {
                EXIT;
                return -ENOMEM;
        }

        CDEBUG (D_KML, "=====REINT_RMDIR::%s\n", path);
        info->updated_time.tv_sec = rec->new_parentv->pv_mtime_sec;
        info->updated_time.tv_nsec = rec->new_parentv->pv_mtime_nsec;
        kmlreint_pre_secure(rec, dir, &saved_ctxt);
        error = lento_rmdir(path, info);
        pop_ctxt(&saved_ctxt); 

        kfree(path);
        EXIT;
        return error;
}

static int reint_setattr(struct kml_rec *rec, struct file *dir,
                         struct lento_vfs_context *info)
{
        struct run_ctxt saved_ctxt;
        struct iattr iattr;
        int     error;

        ENTRY;

        iattr.ia_valid = rec->valid;
        iattr.ia_mode  = (umode_t)rec->mode;
        iattr.ia_uid   = (uid_t)rec->uid;
        iattr.ia_gid   = (gid_t)rec->gid;
        iattr.ia_size  = (off_t)rec->size;
        iattr.ia_ctime.tv_sec = rec->ctime_sec;
        iattr.ia_ctime.tv_nsec = rec->ctime_nsec;
        iattr.ia_mtime.tv_sec = rec->mtime_sec;
        iattr.ia_mtime.tv_nsec = rec->mtime_nsec;
        iattr.ia_atime = iattr.ia_mtime; /* We don't track atimes. */
        iattr.ia_attr_flags = rec->flags;

        CDEBUG (D_KML, "=====REINT_SETATTR::%s (%d)\n", rec->path, rec->valid);
        kmlreint_pre_secure(rec, dir, &saved_ctxt);
        error = lento_setattr(rec->path, &iattr, info);
        pop_ctxt(&saved_ctxt); 

        EXIT;
        return error;
}

static int reint_symlink(struct kml_rec *rec, struct file *dir,
                         struct lento_vfs_context *info)
{
        struct run_ctxt saved_ctxt;
        int     error;

        ENTRY;

        CDEBUG (D_KML, "=====REINT_SYMLINK::%s -> %s\n", rec->path, rec->target);
        info->updated_time.tv_sec = rec->new_objectv->pv_ctime_sec;
        info->updated_time.tv_nsec = rec->new_objectv->pv_ctime_nsec;
        kmlreint_pre_secure(rec, dir, &saved_ctxt);
        error = lento_symlink(rec->target, rec->path, info);
        pop_ctxt(&saved_ctxt); 

        EXIT;
        return error;
}

static int reint_unlink(struct kml_rec *rec, struct file *dir,
                        struct lento_vfs_context *info)
{
        struct run_ctxt saved_ctxt;
        int     error;
        char *path;

        ENTRY;

        path = path_join(rec->path, rec->pathlen - 1, rec->target, rec->targetlen);
        if (path == NULL) {
                EXIT;
                return -ENOMEM;
        }

        CDEBUG (D_KML, "=====REINT_UNLINK::%s\n", path);
        info->updated_time.tv_sec = rec->new_parentv->pv_mtime_sec;
        info->updated_time.tv_nsec = rec->new_parentv->pv_mtime_nsec;
        kmlreint_pre_secure(rec, dir, &saved_ctxt);
        error = lento_unlink(path, info);
        pop_ctxt(&saved_ctxt); 

        kfree(path);
        EXIT;
        return error;
}

static int branch_reint_rename(struct presto_file_set *fset, struct kml_rec *rec, 
                   struct file *dir, struct lento_vfs_context *info,
                   char * kml_data, __u64 kml_size)
{
        int     error;

        ENTRY;

        error = reint_rename(rec, dir, info);
        if (error == -ENOENT) {
                /* normal reint failed because path was not found */
                struct rec_info rec;
                
                CDEBUG(D_KML, "saving branch rename kml\n");
                rec.is_kml = 1;
                rec.size = kml_size;
                error = presto_log(fset, &rec, kml_data, kml_size,
                           NULL, 0, NULL, 0,  NULL, 0);
                if (error == 0)
                        error = presto_write_last_rcvd(&rec, fset, info);
        }

        EXIT;
        return error;
}

int branch_reinter(struct presto_file_set *fset, struct kml_rec *rec, 
                   struct file *dir, struct lento_vfs_context *info,
                   char * kml_data, __u64 kml_size)
{
        int error = 0;
        int op = rec->prefix.hdr->opcode;

        if (op == KML_OPCODE_CLOSE) {
                /* regular close and backfetch */
                error = reint_close(rec, dir, info);
        } else if  (op == KML_OPCODE_RENAME) {
                /* rename only if name already exists  */
                error = branch_reint_rename(fset, rec, dir, info,
                                            kml_data, kml_size);
        } else {
                /* just rewrite kml into branch/kml and update last_rcvd */
                struct rec_info rec;
                
                CDEBUG(D_KML, "Saving branch kml\n");
                rec.is_kml = 1;
                rec.size = kml_size;
                error = presto_log(fset, &rec, kml_data, kml_size,
                           NULL, 0, NULL, 0,  NULL, 0);
                if (error == 0)
                        error = presto_write_last_rcvd(&rec, fset, info);
        }
                
        return error;
}

typedef int (*reinter_t)(struct kml_rec *rec, struct file *basedir,
                         struct lento_vfs_context *info);

static reinter_t presto_reinters[KML_OPCODE_NUM] =
{
        [KML_OPCODE_CLOSE] = reint_close,
        [KML_OPCODE_CREATE] = reint_create,
        [KML_OPCODE_LINK] = reint_link,
        [KML_OPCODE_MKDIR] = reint_mkdir,
        [KML_OPCODE_MKNOD] = reint_mknod,
        [KML_OPCODE_NOOP] = reint_noop,
        [KML_OPCODE_RENAME] = reint_rename,
        [KML_OPCODE_RMDIR] = reint_rmdir,
        [KML_OPCODE_SETATTR] = reint_setattr,
        [KML_OPCODE_SYMLINK] = reint_symlink,
        [KML_OPCODE_UNLINK] = reint_unlink,
};

static inline reinter_t get_reinter(int op)
{
        if (op < 0 || op >= sizeof(presto_reinters) / sizeof(reinter_t)) 
                return NULL; 
        else 
                return  presto_reinters[op];
}

int kml_reint_rec(struct file *dir, struct izo_ioctl_data *data)
{
        char *ptr;
        char *end;
        struct kml_rec rec;
        int error = 0;
        struct lento_vfs_context info;
        struct presto_cache *cache;
        struct presto_file_set *fset;
        struct presto_dentry_data *dd = presto_d2d(dir->f_dentry);
        int op;
        reinter_t reinter;

        struct izo_rcvd_rec lr_rec;
        int off;

        ENTRY;

        error = presto_prep(dir->f_dentry, &cache, &fset);
        if ( error  ) {
                CERROR("intermezzo: Reintegration on invalid file\n");
                return error;
        }

        if (!dd || !dd->dd_fset || dd->dd_fset->fset_dentry != dir->f_dentry) { 
                CERROR("intermezzo: reintegration on non-fset root (ino %ld)\n",
                       dir->f_dentry->d_inode->i_ino);
                    
                return -EINVAL;
        }

        if (data->ioc_plen1 > 64 * 1024) {
                EXIT;
                return -ENOSPC;
        }

        ptr = fset->fset_reint_buf;
        end = ptr + data->ioc_plen1;

        if (copy_from_user(ptr, data->ioc_pbuf1, data->ioc_plen1)) { 
                EXIT;
                error = -EFAULT;
                goto out;
        }

        error = kml_unpack(&rec, &ptr, end);
        if (error) { 
                EXIT;
                error = -EFAULT;
                goto out;
        }

        off = izo_rcvd_get(&lr_rec, fset, data->ioc_uuid);
        if (off < 0) {
                CERROR("No last_rcvd record, setting to 0\n");
                memset(&lr_rec, 0, sizeof(lr_rec));
        }
 
        data->ioc_kmlsize = ptr - fset->fset_reint_buf;

        if (rec.suffix->recno != lr_rec.lr_remote_recno + 1) {
                CERROR("KML record number %Lu expected, not %d\n",
                       lr_rec.lr_remote_recno + 1,
                       rec.suffix->recno);

#if 0
                if (!version_check(&rec, dd->dd_fset, &info)) {
                        /* FIXME: do an upcall to resolve conflicts */
                        CERROR("intermezzo: would be a conflict!\n");
                        error = -EINVAL;
                        EXIT;
                        goto out;
                }
#endif
        }

        op = rec.prefix.hdr->opcode;

        reinter = get_reinter(op);
        if (!reinter) { 
                CERROR("%s: Unrecognized KML opcode %d\n", __FUNCTION__, op);
                error = -EINVAL;
                EXIT;
                goto out;
        }

        info.kml_offset = data->ioc_offset + data->ioc_kmlsize;
        info.recno = rec.suffix->recno;
        info.flags = LENTO_FL_EXPECT;
        if (data->ioc_flags)
                info.flags |= LENTO_FL_KML;

        memcpy(info.uuid, data->ioc_uuid, sizeof(info.uuid));

        if (fset->fset_flags & FSET_IS_BRANCH && data->ioc_flags)
                error = branch_reinter(fset, &rec, dir, &info, fset->fset_reint_buf,
                                       data->ioc_kmlsize);
        else 
                error = reinter(&rec, dir, &info);
 out: 
        EXIT;
        return error;
}

int izo_get_fileid(struct file *dir, struct izo_ioctl_data *data)
{
        char *buf = NULL; 
        char *ptr;
        char *end;
        struct kml_rec rec;
        struct file *file;
        struct presto_cache *cache;
        struct presto_file_set *fset;
        struct presto_dentry_data *dd = presto_d2d(dir->f_dentry);
        struct run_ctxt saved_ctxt;
        int     error;

        ENTRY;

        error = presto_prep(dir->f_dentry, &cache, &fset);
        if ( error  ) {
                CERROR("intermezzo: Reintegration on invalid file\n");
                return error;
        }

        if (!dd || !dd->dd_fset || dd->dd_fset->fset_dentry != dir->f_dentry) { 
                CERROR("intermezzo: reintegration on non-fset root (ino %ld)\n",
                       dir->f_dentry->d_inode->i_ino);
                    
                return -EINVAL;
        }


        PRESTO_ALLOC(buf, data->ioc_plen1);
        if (!buf) { 
                EXIT;
                return -ENOMEM;
        }
        ptr = buf;
        end = buf + data->ioc_plen1;

        if (copy_from_user(buf, data->ioc_pbuf1, data->ioc_plen1)) { 
                EXIT;
                PRESTO_FREE(buf, data->ioc_plen1);
                return -EFAULT;
        }

        error = kml_unpack(&rec, &ptr, end);
        if (error) { 
                EXIT;
                PRESTO_FREE(buf, data->ioc_plen1);
                return -EFAULT;
        }

        kmlreint_pre_secure(&rec, dir, &saved_ctxt);

        file = filp_open(rec.path, O_RDONLY, 0);
        if (!file || IS_ERR(file)) { 
                error = PTR_ERR(file);
                goto out;
        }
        data->ioc_ino = file->f_dentry->d_inode->i_ino;
        data->ioc_generation = file->f_dentry->d_inode->i_generation; 
        filp_close(file, 0); 

        CDEBUG(D_FILE, "%s ino %Lx, gen %Lx\n", rec.path, 
               data->ioc_ino, data->ioc_generation);

 out:
        if (buf) 
                PRESTO_FREE(buf, data->ioc_plen1);
        pop_ctxt(&saved_ctxt); 
        EXIT;
        return error;
}


