/* $Id: regops.c $ */
/** @file
 * vboxsf - VBox Linux Shared Folders, Regular file inode and file operations.
 */

/*
 * Copyright (C) 2006-2012 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */

/*
 * Limitations: only COW memory mapping is supported
 */

#include "vfsmod.h"

static void *alloc_bounce_buffer(size_t *tmp_sizep, PRTCCPHYS physp, size_t
                                 xfer_size, const char *caller)
{
    size_t tmp_size;
    void *tmp;

    /* try for big first. */
    tmp_size = RT_ALIGN_Z(xfer_size, PAGE_SIZE);
    if (tmp_size > 16U*_1K)
        tmp_size = 16U*_1K;
    tmp = kmalloc(tmp_size, GFP_KERNEL);
    if (!tmp)
    {
        /* fall back on a page sized buffer. */
        tmp = kmalloc(PAGE_SIZE, GFP_KERNEL);
        if (!tmp)
        {
            LogRel(("%s: could not allocate bounce buffer for xfer_size=%zu %s\n", caller, xfer_size));
            return NULL;
        }
        tmp_size = PAGE_SIZE;
    }

    *tmp_sizep = tmp_size;
    *physp = virt_to_phys(tmp);
    return tmp;
}

static void free_bounce_buffer(void *tmp)
{
    kfree (tmp);
}


/* fops */
static int sf_reg_read_aux(const char *caller, struct sf_glob_info *sf_g,
                           struct sf_reg_info *sf_r, void *buf,
                           uint32_t *nread, uint64_t pos)
{
    /** @todo bird: yes, kmap() and kmalloc() input only. Since the buffer is
     *        contiguous in physical memory (kmalloc or single page), we should
     *        use a physical address here to speed things up. */
    int rc = VbglR0SfRead(&client_handle, &sf_g->map, sf_r->handle,
                          pos, nread, buf, false /* already locked? */);
    if (RT_FAILURE(rc))
    {
        LogFunc(("VbglR0SfRead failed. caller=%s, rc=%Rrc\n", caller, rc));
        return -EPROTO;
    }
    return 0;
}

static int sf_reg_write_aux(const char *caller, struct sf_glob_info *sf_g,
                            struct sf_reg_info *sf_r, void *buf,
                            uint32_t *nwritten, uint64_t pos)
{
    /** @todo bird: yes, kmap() and kmalloc() input only. Since the buffer is
     *        contiguous in physical memory (kmalloc or single page), we should
     *        use a physical address here to speed things up. */
    int rc = VbglR0SfWrite(&client_handle, &sf_g->map, sf_r->handle,
                           pos, nwritten, buf, false /* already locked? */);
    if (RT_FAILURE(rc))
    {
        LogFunc(("VbglR0SfWrite failed. caller=%s, rc=%Rrc\n",
                    caller, rc));
        return -EPROTO;
    }
    return 0;
}

/**
 * Read from a regular file.
 *
 * @param file          the file
 * @param buf           the buffer
 * @param size          length of the buffer
 * @param off           offset within the file
 * @returns the number of read bytes on success, Linux error code otherwise
 */
static ssize_t sf_reg_read(struct file *file, char *buf, size_t size, loff_t *off)
{
    int err;
    void *tmp;
    RTCCPHYS tmp_phys;
    size_t tmp_size;
    size_t left = size;
    ssize_t total_bytes_read = 0;
    struct inode *inode = GET_F_DENTRY(file)->d_inode;
    struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb);
    struct sf_reg_info *sf_r = file->private_data;
    loff_t pos = *off;

    TRACE();
    if (!S_ISREG(inode->i_mode))
    {
        LogFunc(("read from non regular file %d\n", inode->i_mode));
        return -EINVAL;
    }

    /** XXX Check read permission according to inode->i_mode! */

    if (!size)
        return 0;

    tmp = alloc_bounce_buffer(&tmp_size, &tmp_phys, size, __PRETTY_FUNCTION__);
    if (!tmp)
        return -ENOMEM;

    while (left)
    {
        uint32_t to_read, nread;

        to_read = tmp_size;
        if (to_read > left)
            to_read = (uint32_t) left;

        nread = to_read;

        err = sf_reg_read_aux(__func__, sf_g, sf_r, tmp, &nread, pos);
        if (err)
            goto fail;

        if (copy_to_user(buf, tmp, nread))
        {
            err = -EFAULT;
            goto fail;
        }

        pos  += nread;
        left -= nread;
        buf  += nread;
        total_bytes_read += nread;
        if (nread != to_read)
            break;
    }

    *off += total_bytes_read;
    free_bounce_buffer(tmp);
    return total_bytes_read;

fail:
    free_bounce_buffer(tmp);
    return err;
}

/**
 * Write to a regular file.
 *
 * @param file          the file
 * @param buf           the buffer
 * @param size          length of the buffer
 * @param off           offset within the file
 * @returns the number of written bytes on success, Linux error code otherwise
 */
static ssize_t sf_reg_write(struct file *file, const char *buf, size_t size, loff_t *off)
{
    int err;
    void *tmp;
    RTCCPHYS tmp_phys;
    size_t tmp_size;
    size_t left = size;
    ssize_t total_bytes_written = 0;
    struct inode *inode = GET_F_DENTRY(file)->d_inode;
    struct sf_inode_info *sf_i = GET_INODE_INFO(inode);
    struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb);
    struct sf_reg_info *sf_r = file->private_data;
    loff_t pos;

    TRACE();
    BUG_ON(!sf_i);
    BUG_ON(!sf_g);
    BUG_ON(!sf_r);

    if (!S_ISREG(inode->i_mode))
    {
        LogFunc(("write to non regular file %d\n",  inode->i_mode));
        return -EINVAL;
    }

    pos = *off;
    if (file->f_flags & O_APPEND)
    {
        pos = inode->i_size;
        *off = pos;
    }

    /** XXX Check write permission according to inode->i_mode! */

    if (!size)
        return 0;

    tmp = alloc_bounce_buffer(&tmp_size, &tmp_phys, size, __PRETTY_FUNCTION__);
    if (!tmp)
        return -ENOMEM;

    while (left)
    {
        uint32_t to_write, nwritten;

        to_write = tmp_size;
        if (to_write > left)
            to_write = (uint32_t) left;

        nwritten = to_write;

        if (copy_from_user(tmp, buf, to_write))
        {
            err = -EFAULT;
            goto fail;
        }

#if 1
        if (VbglR0CanUsePhysPageList())
        {
            err = VbglR0SfWritePhysCont(&client_handle, &sf_g->map, sf_r->handle,
                                        pos, &nwritten, tmp_phys);
            err = RT_FAILURE(err) ? -EPROTO : 0;
        }
        else
#endif
            err = sf_reg_write_aux(__func__, sf_g, sf_r, tmp, &nwritten, pos);
        if (err)
            goto fail;

        pos  += nwritten;
        left -= nwritten;
        buf  += nwritten;
        total_bytes_written += nwritten;
        if (nwritten != to_write)
            break;
    }

    *off += total_bytes_written;
    if (*off > inode->i_size)
        inode->i_size = *off;

    sf_i->force_restat = 1;
    free_bounce_buffer(tmp);
    return total_bytes_written;

fail:
    free_bounce_buffer(tmp);
    return err;
}

/**
 * Open a regular file.
 *
 * @param inode         the inode
 * @param file          the file
 * @returns 0 on success, Linux error code otherwise
 */
static int sf_reg_open(struct inode *inode, struct file *file)
{
    int rc, rc_linux = 0;
    struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb);
    struct sf_inode_info *sf_i = GET_INODE_INFO(inode);
    struct sf_reg_info *sf_r;
    SHFLCREATEPARMS params;

    TRACE();
    BUG_ON(!sf_g);
    BUG_ON(!sf_i);

    LogFunc(("open %s\n", sf_i->path->String.utf8));

    sf_r = kmalloc(sizeof(*sf_r), GFP_KERNEL);
    if (!sf_r)
    {
        LogRelFunc(("could not allocate reg info\n"));
        return -ENOMEM;
    }

    /* Already open? */
    if (sf_i->handle != SHFL_HANDLE_NIL)
    {
        /*
         * This inode was created with sf_create_aux(). Check the CreateFlags:
         * O_CREAT, O_TRUNC: inherent true (file was just created). Not sure
         * about the access flags (SHFL_CF_ACCESS_*).
         */
        sf_i->force_restat = 1;
        sf_r->handle = sf_i->handle;
        sf_i->handle = SHFL_HANDLE_NIL;
        sf_i->file = file;
        file->private_data = sf_r;
        return 0;
    }

    RT_ZERO(params);
    params.Handle = SHFL_HANDLE_NIL;
    /* We check the value of params.Handle afterwards to find out if
     * the call succeeded or failed, as the API does not seem to cleanly
     * distinguish error and informational messages.
     *
     * Furthermore, we must set params.Handle to SHFL_HANDLE_NIL to
     * make the shared folders host service use our fMode parameter */

    if (file->f_flags & O_CREAT)
    {
        LogFunc(("O_CREAT set\n"));
        params.CreateFlags |= SHFL_CF_ACT_CREATE_IF_NEW;
        /* We ignore O_EXCL, as the Linux kernel seems to call create
           beforehand itself, so O_EXCL should always fail. */
        if (file->f_flags & O_TRUNC)
        {
            LogFunc(("O_TRUNC set\n"));
            params.CreateFlags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS;
        }
        else
            params.CreateFlags |= SHFL_CF_ACT_OPEN_IF_EXISTS;
    }
    else
    {
        params.CreateFlags |= SHFL_CF_ACT_FAIL_IF_NEW;
        if (file->f_flags & O_TRUNC)
        {
            LogFunc(("O_TRUNC set\n"));
            params.CreateFlags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS;
        }
    }

    switch (file->f_flags & O_ACCMODE)
    {
        case O_RDONLY:
            params.CreateFlags |= SHFL_CF_ACCESS_READ;
            break;

        case O_WRONLY:
            params.CreateFlags |= SHFL_CF_ACCESS_WRITE;
            break;

        case O_RDWR:
            params.CreateFlags |= SHFL_CF_ACCESS_READWRITE;
            break;

        default:
            BUG ();
    }

    if (file->f_flags & O_APPEND)
    {
        LogFunc(("O_APPEND set\n"));
        params.CreateFlags |= SHFL_CF_ACCESS_APPEND;
    }

    params.Info.Attr.fMode = inode->i_mode;
    LogFunc(("sf_reg_open: calling VbglR0SfCreate, file %s, flags=%#x, %#x\n",
              sf_i->path->String.utf8 , file->f_flags, params.CreateFlags));
    rc = VbglR0SfCreate(&client_handle, &sf_g->map, sf_i->path, &params);
    if (RT_FAILURE(rc))
    {
        LogFunc(("VbglR0SfCreate failed flags=%d,%#x rc=%Rrc\n",
                  file->f_flags, params.CreateFlags, rc));
        kfree(sf_r);
        return -RTErrConvertToErrno(rc);
    }

    if (SHFL_HANDLE_NIL == params.Handle)
    {
        switch (params.Result)
        {
            case SHFL_PATH_NOT_FOUND:
            case SHFL_FILE_NOT_FOUND:
                rc_linux = -ENOENT;
                break;
            case SHFL_FILE_EXISTS:
                rc_linux = -EEXIST;
                break;
            default:
                break;
        }
    }

    sf_i->force_restat = 1;
    sf_r->handle = params.Handle;
    sf_i->file = file;
    file->private_data = sf_r;
    return rc_linux;
}

/**
 * Close a regular file.
 *
 * @param inode         the inode
 * @param file          the file
 * @returns 0 on success, Linux error code otherwise
 */
static int sf_reg_release(struct inode *inode, struct file *file)
{
    int rc;
    struct sf_reg_info *sf_r;
    struct sf_glob_info *sf_g;
    struct sf_inode_info *sf_i = GET_INODE_INFO(inode);

    TRACE();
    sf_g = GET_GLOB_INFO(inode->i_sb);
    sf_r = file->private_data;

    BUG_ON(!sf_g);
    BUG_ON(!sf_r);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25)
    /* See the smbfs source (file.c). mmap in particular can cause data to be
     * written to the file after it is closed, which we can't cope with.  We
     * copy and paste the body of filemap_write_and_wait() here as it was not
     * defined before 2.6.6 and not exported until quite a bit later. */
    /* filemap_write_and_wait(inode->i_mapping); */
    if (   inode->i_mapping->nrpages
        && filemap_fdatawrite(inode->i_mapping) != -EIO)
        filemap_fdatawait(inode->i_mapping);
#endif
    rc = VbglR0SfClose(&client_handle, &sf_g->map, sf_r->handle);
    if (RT_FAILURE(rc))
        LogFunc(("VbglR0SfClose failed rc=%Rrc\n", rc));

    kfree(sf_r);
    sf_i->file = NULL;
    sf_i->handle = SHFL_HANDLE_NIL;
    file->private_data = NULL;
    return 0;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
static int sf_reg_fault(struct vm_fault *vmf)
#elif LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25)
static int sf_reg_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
static struct page *sf_reg_nopage(struct vm_area_struct *vma, unsigned long vaddr, int *type)
# define SET_TYPE(t) *type = (t)
#else /* LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) */
static struct page *sf_reg_nopage(struct vm_area_struct *vma, unsigned long vaddr, int unused)
# define SET_TYPE(t)
#endif
{
    struct page *page;
    char *buf;
    loff_t off;
    uint32_t nread = PAGE_SIZE;
    int err;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
    struct vm_area_struct *vma = vmf->vma;
#endif
    struct file *file = vma->vm_file;
    struct inode *inode = GET_F_DENTRY(file)->d_inode;
    struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb);
    struct sf_reg_info *sf_r = file->private_data;

    TRACE();
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25)
    if (vmf->pgoff > vma->vm_end)
        return VM_FAULT_SIGBUS;
#else
    if (vaddr > vma->vm_end)
    {
        SET_TYPE(VM_FAULT_SIGBUS);
        return NOPAGE_SIGBUS;
    }
#endif

    /* Don't use GFP_HIGHUSER as long as sf_reg_read_aux() calls VbglR0SfRead()
     * which works on virtual addresses. On Linux cannot reliably determine the
     * physical address for high memory, see rtR0MemObjNativeLockKernel(). */
    page = alloc_page(GFP_USER);
    if (!page) {
        LogRelFunc(("failed to allocate page\n"));
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25)
        return VM_FAULT_OOM;
#else
        SET_TYPE(VM_FAULT_OOM);
        return NOPAGE_OOM;
#endif
    }

    buf = kmap(page);
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25)
    off = (vmf->pgoff << PAGE_SHIFT);
#else
    off = (vaddr - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT);
#endif
    err = sf_reg_read_aux(__func__, sf_g, sf_r, buf, &nread, off);
    if (err)
    {
        kunmap(page);
        put_page(page);
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25)
        return VM_FAULT_SIGBUS;
#else
        SET_TYPE(VM_FAULT_SIGBUS);
        return NOPAGE_SIGBUS;
#endif
    }

    BUG_ON (nread > PAGE_SIZE);
    if (!nread)
    {
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25)
        clear_user_page(page_address(page), vmf->pgoff, page);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
        clear_user_page(page_address(page), vaddr, page);
#else
        clear_user_page(page_address(page), vaddr);
#endif
    }
    else
        memset(buf + nread, 0, PAGE_SIZE - nread);

    flush_dcache_page(page);
    kunmap(page);
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25)
    vmf->page = page;
    return 0;
#else
    SET_TYPE(VM_FAULT_MAJOR);
    return page;
#endif
}

static struct vm_operations_struct sf_vma_ops =
{
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25)
    .fault = sf_reg_fault
#else
    .nopage = sf_reg_nopage
#endif
};

static int sf_reg_mmap(struct file *file, struct vm_area_struct *vma)
{
    TRACE();
    if (vma->vm_flags & VM_SHARED)
    {
        LogFunc(("shared mmapping not available\n"));
        return -EINVAL;
    }

    vma->vm_ops = &sf_vma_ops;
    return 0;
}

struct file_operations sf_reg_fops =
{
    .read        = sf_reg_read,
    .open        = sf_reg_open,
    .write       = sf_reg_write,
    .release     = sf_reg_release,
    .mmap        = sf_reg_mmap,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23)
    .splice_read = generic_file_splice_read,
# else
    .sendfile    = generic_file_sendfile,
# endif
# if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
    .read_iter   = generic_file_read_iter,
    .write_iter  = generic_file_write_iter,
# else
    .aio_read    = generic_file_aio_read,
    .aio_write   = generic_file_aio_write,
# endif
# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35)
    .fsync       = noop_fsync,
# else
    .fsync       = simple_sync_file,
# endif
    .llseek      = generic_file_llseek,
#endif
};


struct inode_operations sf_reg_iops =
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)
    .revalidate = sf_inode_revalidate
#else
    .getattr    = sf_getattr,
    .setattr    = sf_setattr
#endif
};


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
static int sf_readpage(struct file *file, struct page *page)
{
    struct inode *inode = GET_F_DENTRY(file)->d_inode;
    struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb);
    struct sf_reg_info *sf_r = file->private_data;
    uint32_t nread = PAGE_SIZE;
    char *buf;
    loff_t off = ((loff_t)page->index) << PAGE_SHIFT;
    int ret;

    TRACE();

    buf = kmap(page);
    ret = sf_reg_read_aux(__func__, sf_g, sf_r, buf, &nread, off);
    if (ret)
    {
        kunmap(page);
        if (PageLocked(page))
            unlock_page(page);
        return ret;
    }
    BUG_ON(nread > PAGE_SIZE);
    memset(&buf[nread], 0, PAGE_SIZE - nread);
    flush_dcache_page(page);
    kunmap(page);
    SetPageUptodate(page);
    unlock_page(page);
    return 0;
}

static int
sf_writepage(struct page *page, struct writeback_control *wbc)
{
    struct address_space *mapping = page->mapping;
    struct inode *inode = mapping->host;
    struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb);
    struct sf_inode_info *sf_i = GET_INODE_INFO(inode);
    struct file *file = sf_i->file;
    struct sf_reg_info *sf_r = file->private_data;
    char *buf;
    uint32_t nwritten = PAGE_SIZE;
    int end_index = inode->i_size >> PAGE_SHIFT;
    loff_t off = ((loff_t) page->index) << PAGE_SHIFT;
    int err;

    TRACE();

    if (page->index >= end_index)
        nwritten = inode->i_size & (PAGE_SIZE-1);

    buf = kmap(page);

    err = sf_reg_write_aux(__func__, sf_g, sf_r, buf, &nwritten, off);
    if (err < 0)
    {
        ClearPageUptodate(page);
        goto out;
    }

    if (off > inode->i_size)
        inode->i_size = off;

    if (PageError(page))
        ClearPageError(page);
    err = 0;

out:
    kunmap(page);

    unlock_page(page);
    return err;
}

# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24)
int sf_write_begin(struct file *file, struct address_space *mapping, loff_t pos,
                   unsigned len, unsigned flags, struct page **pagep, void **fsdata)
{
    TRACE();

    return simple_write_begin(file, mapping, pos, len, flags, pagep, fsdata);
}

int sf_write_end(struct file *file, struct address_space *mapping, loff_t pos,
                 unsigned len, unsigned copied, struct page *page, void *fsdata)
{
    struct inode *inode = mapping->host;
    struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb);
    struct sf_reg_info *sf_r = file->private_data;
    void *buf;
    unsigned from = pos & (PAGE_SIZE - 1);
    uint32_t nwritten = len;
    int err;

    TRACE();

    buf = kmap(page);
    err = sf_reg_write_aux(__func__, sf_g, sf_r, buf+from, &nwritten, pos);
    kunmap(page);

    if (!PageUptodate(page) && err == PAGE_SIZE)
        SetPageUptodate(page);

    if (err >= 0) {
        pos += nwritten;
        if (pos > inode->i_size)
            inode->i_size = pos;
    }

    unlock_page(page);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0)
    put_page(page);
#else
    page_cache_release(page);
#endif

    return nwritten;
}

# endif /* KERNEL_VERSION >= 2.6.24 */

struct address_space_operations sf_reg_aops =
{
    .readpage      = sf_readpage,
    .writepage     = sf_writepage,
# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24)
    .write_begin   = sf_write_begin,
    .write_end     = sf_write_end,
# else
    .prepare_write = simple_prepare_write,
    .commit_write  = simple_commit_write,
# endif
};
#endif
