/** @file
 *
 * vboxsf -- VirtualBox Guest Additions for Linux:
 * Directory inode and file operations
 */

/*
 * Copyright (C) 2006-2007 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.
 */

#include "vfsmod.h"

static int
sf_dir_open(struct inode *inode, struct file *file)
{
        int rc;
        int err;
        struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb);
        struct sf_dir_info *sf_d;
        struct sf_inode_info *sf_i = GET_INODE_INFO(inode);
        SHFLCREATEPARMS params;

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

        if (file->private_data) {
                LogFunc(("dir_open called on already opened directory %s\n",
                         sf_i->path->String.utf8));
                return 0;
        }

        RT_ZERO(params);

        sf_d = sf_dir_info_alloc();

        if (!sf_d) {
                LogRelFunc(("could not allocate directory info for %s\n",
                            sf_i->path->String.utf8));
                return -ENOMEM;
        }

        params.CreateFlags = 0
                | SHFL_CF_DIRECTORY
                | SHFL_CF_ACT_OPEN_IF_EXISTS
                | SHFL_CF_ACT_FAIL_IF_NEW
                | SHFL_CF_ACCESS_READ
                ;

        LogFunc(("sf_dir_open: calling vboxCallCreate, folder %s, flags %#x\n",
                 sf_i->path->String.utf8, params.CreateFlags));
        rc = vboxCallCreate(&client_handle, &sf_g->map, sf_i->path, &params);
        if (RT_FAILURE(rc)) {
                LogFunc(("vboxCallCreate(%s) failed rc=%Rrc\n",
                         sf_i->path->String.utf8, rc));
                sf_dir_info_free(sf_d);
                return -EPERM;
        }

        if (params.Result != SHFL_FILE_EXISTS) {
                LogFunc(("directory %s does not exist\n", sf_i->path->String.utf8));
                sf_dir_info_free(sf_d);
                return -ENOENT;
        }

        err = sf_dir_read_all(sf_g, sf_i, sf_d, params.Handle);
        if (err) {
                rc = vboxCallClose(&client_handle, &sf_g->map, params.Handle);
                if (RT_FAILURE(rc)) {
                        LogFunc(("vboxCallClose(%s) after err=%d failed rc=%Rrc\n",
                                 sf_i->path->String.utf8, err, rc));
                }
                sf_dir_info_free(sf_d);
                return err;
        }


        rc = vboxCallClose(&client_handle, &sf_g->map, params.Handle);
        if (RT_FAILURE(rc)) {
                LogFunc(("vboxCallClose(%s) failed rc=%Rrc\n",
                          sf_i->path->String.utf8, rc));
        }

        file->private_data = sf_d;
        return 0;
}

/* This is called when reference count of [file] goes to zero. Notify
   the host that it can free whatever is associated with this
   directory and deallocate our own internal buffers */
static int
sf_dir_release(struct inode *inode, struct file *file)
{
        struct sf_inode_info *sf_i = GET_INODE_INFO(inode);

        TRACE();
        BUG_ON(!sf_i);

        if (file->private_data) {
                sf_dir_info_free(file->private_data);
        }
        return 0;
}

/* Extract element ([dir]->f_pos) from the directory [dir] into
   [d_name], return:
   0       all ok
   1       end reached
   -errno  some form of error*/
static int
sf_getdent(struct file *dir, char d_name[NAME_MAX])
{
        loff_t cur;
        struct sf_glob_info *sf_g;
        struct sf_dir_info *sf_d;
        struct sf_inode_info *sf_i;
        struct inode *inode;
        struct list_head *pos, *list;

        TRACE();

        sf_g = GET_GLOB_INFO(dir->f_dentry->d_inode->i_sb);
        sf_d = dir->private_data;

        BUG_ON(!sf_g);
        BUG_ON(!sf_d);

        inode = dir->f_dentry->d_inode;
        sf_i = GET_INODE_INFO(inode);

        BUG_ON(!sf_i);

        if (sf_i->force_reread)
        {
                int rc;
                int err;
                SHFLCREATEPARMS params;
 
                RT_ZERO(params);
 
                params.CreateFlags = 0
                                   | SHFL_CF_DIRECTORY
                                   | SHFL_CF_ACT_OPEN_IF_EXISTS
                                   | SHFL_CF_ACT_FAIL_IF_NEW
                                   | SHFL_CF_ACCESS_READ
                                   ;
 
                LogFunc(("sf_getdent: calling vboxCallCreate, folder %s, flags %#x\n",
                            sf_i->path->String.utf8, params.CreateFlags));
                rc = vboxCallCreate(&client_handle, &sf_g->map, sf_i->path, &params);
                if (RT_FAILURE(rc)) {
                    LogRel(("open failed %d\n", rc));
                    LogFunc(("vboxCallCreate(%s) failed rc=%Rrc\n",
                                sf_i->path->String.utf8, rc));
                    return -EPERM;
                }
 
                if (params.Result != SHFL_FILE_EXISTS) {
                    LogRel(("not exist %x\n", params.Result));
                    LogFunc(("directory %s does not exist\n", sf_i->path->String.utf8));
                    sf_dir_info_free(sf_d);
                    return -ENOENT;
                }
 
                sf_dir_info_empty(sf_d);
                err = sf_dir_read_all(sf_g, sf_i, sf_d, params.Handle);
                rc = vboxCallClose(&client_handle, &sf_g->map, params.Handle);
                if (RT_FAILURE(rc)) {
                    LogFunc(("vboxCallClose(%s) failed rc=%Rrc\n",
                             sf_i->path->String.utf8, rc));
                }
                if (err)
                    return err;
                sf_i->force_reread = 0;
        }
 
        cur = 0;
        list = &sf_d->info_list;
        list_for_each(pos, list) {
                struct sf_dir_buf *b;
                SHFLDIRINFO *info;
                loff_t i;
                size_t name_len;
                char *name_ptr;

                b = list_entry(pos, struct sf_dir_buf, head);
                if (dir->f_pos >= cur + b->nb_entries) {
                        cur += b->nb_entries;
                        continue;
                }

                for (i = 0, info = b->buf; i < dir->f_pos - cur; ++i) {
                        size_t size;

                        size = offsetof(SHFLDIRINFO, name.String) + info->name.u16Size;
                        info = (SHFLDIRINFO *) ((uintptr_t) info + size);
                }

                name_ptr = info->name.String.utf8;
                name_len = info->name.u16Length;

                return sf_nlscpy(sf_g, d_name, NAME_MAX, name_ptr, name_len);
        }
        return 1;
}

/* This is called when vfs wants to populate internal buffers with
   directory [dir]s contents. [opaque] is an argument to the
   [filldir]. [filldir] magically modifies it's argument - [opaque]
   and takes following additional arguments (which i in turn get from
   the host via sf_getdent):

   name : name of the entry (i must also supply it's length huh?)
   type : type of the entry (FILE | DIR | etc) (i ellect to use DT_UNKNOWN)
   pos : position/index of the entry
   ino : inode number of the entry (i fake those)

   [dir] contains:
   f_pos : cursor into the directory listing
   private_data : mean of communcation with the host side

   Extract elements from the directory listing (incrementing f_pos
   along the way) and feed them to [filldir] until:

   a. there are no more entries (i.e. sf_getdent set done to 1)
   b. failure to compute fake inode number
   c. filldir returns an error (see comment on that) */
static int
sf_dir_read (struct file *dir, void *opaque, filldir_t filldir)
{
        TRACE();
        for (;;) {
                int err;
                ino_t fake_ino;
                loff_t sanity;
                char d_name[NAME_MAX];

                err = sf_getdent(dir, d_name);
                switch (err) {
                        case 1:
                                return 0;

                        case 0:
                                break;

                        case -1:
                        default:
                                /* skip erroneous entry and proceed */
                                LogFunc(("sf_getdent error %d\n", err));
                                dir->f_pos += 1;
                                continue;
                }

                /* d_name now contains valid entry name */

                sanity = dir->f_pos + 0xbeef;
                fake_ino = sanity;
                if (sanity - fake_ino) {
                        LogRelFunc(("can not compute ino\n"));
                        return -EINVAL;
                }

                err = filldir(opaque, d_name, strlen(d_name),
                               dir->f_pos, fake_ino, DT_UNKNOWN);
                if (err) {
                        LogFunc(("filldir returned error %d\n", err));
                        /* Rely on the fact that filldir returns error
                           only when it runs out of space in opaque */
                        return 0;
                }

                dir->f_pos += 1;
        }

        BUG();
}

struct file_operations sf_dir_fops = {
        .open    = sf_dir_open,
        .readdir = sf_dir_read,
        .release = sf_dir_release,
        .read    = generic_read_dir
};


/* iops */

/* This is called when vfs failed to locate dentry in the cache. The
   job of this function is to allocate inode and link it to dentry.
   [dentry] contains the name to be looked in the [parent] directory.
   Failure to locate the name is not a "hard" error, in this case NULL
   inode is added to [dentry] and vfs should proceed trying to create
   the entry via other means. NULL(or "positive" pointer) ought to be
   returned in case of succes and "negative" pointer on error */
static struct dentry *
sf_lookup(struct inode *parent, struct dentry *dentry
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
           , struct nameidata *nd
#endif
        )
{
        int err;
        struct sf_inode_info *sf_i, *sf_new_i;
        struct sf_glob_info *sf_g;
        SHFLSTRING *path;
        struct inode *inode;
        ino_t ino;
        RTFSOBJINFO fsinfo;

        TRACE();
        sf_g = GET_GLOB_INFO(parent->i_sb);
        sf_i = GET_INODE_INFO(parent);

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

        err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path);
        if (err) {
                goto fail0;
        }

        err = sf_stat(__func__, sf_g, path, &fsinfo, 1);
        if (err) {
                if (err == -ENOENT) {
                        /* -ENOENT add NULL inode to dentry so it later can be
                           created via call to create/mkdir/open */
                        inode = NULL;
                }
                else goto fail1;
        }
        else {
                sf_new_i = kmalloc(sizeof(*sf_new_i), GFP_KERNEL);
                if (!sf_new_i) {
                        LogRelFunc(("could not allocate memory for new inode info\n"));
                        err = -ENOMEM;
                        goto fail1;
                }
                sf_new_i->handle = SHFL_HANDLE_NIL;

                ino = iunique(parent->i_sb, 1);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25)
                inode = iget_locked(parent->i_sb, ino);
#else
                inode = iget(parent->i_sb, ino);
#endif
                if (!inode) {
                        LogFunc(("iget failed\n"));
                        err = -ENOMEM;          /* XXX: ??? */
                        goto fail2;
                }

                SET_INODE_INFO(inode, sf_new_i);
                sf_init_inode(sf_g, inode, &fsinfo);
                sf_new_i->path = path;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25)
                unlock_new_inode(inode);
#endif
        }

        sf_i->force_restat = 0;
        dentry->d_time = jiffies;
        dentry->d_op = &sf_dentry_ops;
        d_add(dentry, inode);
        return NULL;

 fail2:
        kfree(sf_new_i);
 fail1:
        kfree(path);
 fail0:
        return ERR_PTR(err);
}

/* This should allocate memory for sf_inode_info, compute unique inode
   number, get an inode from vfs, initialize inode info, instantiate
   dentry */
static int
sf_instantiate(const char *caller, struct inode *parent,
                struct dentry *dentry, SHFLSTRING *path,
                RTFSOBJINFO *info, SHFLHANDLE handle)
{
        int err;
        ino_t ino;
        struct inode *inode;
        struct sf_inode_info *sf_new_i;
        struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb);

        TRACE();
        BUG_ON(!sf_g);

        sf_new_i = kmalloc(sizeof(*sf_new_i), GFP_KERNEL);
        if (!sf_new_i) {
                LogRelFunc(("could not allocate inode info.  caller=%s\n", caller));
                err = -ENOMEM;
                goto fail0;
        }

        ino = iunique(parent->i_sb, 1);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25)
        inode = iget_locked(parent->i_sb, ino);
#else
        inode = iget(parent->i_sb, ino);
#endif
        if (!inode) {
                LogFunc(("iget failed.  caller=%s\n", caller));
                err = -ENOMEM;
                goto fail1;
        }

        sf_init_inode(sf_g, inode, info);
        sf_new_i->path = path;
        SET_INODE_INFO(inode, sf_new_i);

        dentry->d_time = jiffies;
        dentry->d_op = &sf_dentry_ops;
        sf_new_i->force_restat = 1;

        d_instantiate(dentry, inode);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25)
        unlock_new_inode(inode);
#endif

        /* Store this handle if we leave the handle open. */
        sf_new_i->handle = handle;
        return 0;

 fail1:
        kfree(sf_new_i);
 fail0:
        return err;

}

static int
sf_create_aux(struct inode *parent, struct dentry *dentry, int mode, int fDirectory)
{
        int rc, err;
        SHFLCREATEPARMS params;
        SHFLSTRING *path;
        struct sf_inode_info *sf_i = GET_INODE_INFO(parent);
        struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb);

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

#if 0
        printk("create_aux %s/%s\n", sf_i->path->String.utf8,
                dentry->d_name.name);
#endif

        RT_ZERO(params);
        /* Ensure that the shared folders host service is using our fMode
         * paramter */
        params.Handle = SHFL_HANDLE_NIL;

        params.CreateFlags = 0
                           | SHFL_CF_ACT_CREATE_IF_NEW
                           | SHFL_CF_ACT_FAIL_IF_EXISTS
                           | SHFL_CF_ACCESS_READWRITE
                           | (fDirectory ? SHFL_CF_DIRECTORY : 0)
                           ;

        params.Info.Attr.fMode = 0
                               | (fDirectory ? RTFS_TYPE_DIRECTORY : RTFS_TYPE_FILE)
                               | (mode & S_IRWXUGO)
                               ;

        params.Info.Attr.enmAdditional = RTFSOBJATTRADD_NOTHING;

        err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path);
        if (err) {
                goto fail0;
        }

        LogFunc(("sf_create_aux: calling vboxCallCreate, folder %s, flags %#x\n",
                 path->String.utf8, params.CreateFlags));
        rc = vboxCallCreate(&client_handle, &sf_g->map, path, &params);
        if (RT_FAILURE(rc)) {
                if (rc == VERR_WRITE_PROTECT) {
                        err = -EROFS;
                        goto fail0;
                }
                err = -EPROTO;
                LogFunc(("(%d): vboxCallCreate(%s) failed rc=%Rrc\n",
                         fDirectory, sf_i->path->String.utf8, rc));
                goto fail0;
        }

        if (params.Result != SHFL_FILE_CREATED) {
                err = -EPERM;
                LogFunc(("(%d): could not create file %s result=%d\n",
                         fDirectory, sf_i->path->String.utf8, params.Result));
                goto fail0;
        }

        err = sf_instantiate(__func__, parent, dentry, path, &params.Info,
                             fDirectory ? SHFL_HANDLE_NIL : params.Handle);
        if (err) {
                LogFunc(("(%d): could not instantiate dentry for %s err=%d\n",
                         fDirectory, sf_i->path->String.utf8, err));
                goto fail1;
        }

        /*
         * Don't close this handle right now. We assume that the same file is
         * opened with sf_reg_open() and later closed with sf_reg_close(). Save
         * the handle in between. Does not apply to directories. True?
         */
        if (fDirectory)
        {
                rc = vboxCallClose(&client_handle, &sf_g->map, params.Handle);
                if (RT_FAILURE(rc)) {
                        LogFunc(("(%d): vboxCallClose failed rc=%Rrc\n", fDirectory, rc));
                }
        }

        sf_i->force_restat = 1;
        return 0;

 fail1:
        rc = vboxCallClose(&client_handle, &sf_g->map, params.Handle);
        if (RT_FAILURE(rc)) {
                LogFunc(("(%d): vboxCallClose failed rc=%Rrc\n", fDirectory, rc));
        }

 fail0:
        kfree(path);
        return err;
}

static int
sf_create(struct inode *parent, struct dentry *dentry, int mode
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
           , struct nameidata *nd
#endif
        )
{
        TRACE();
        return sf_create_aux(parent, dentry, mode, 0);
}

static int
sf_mkdir(struct inode *parent, struct dentry *dentry, int mode)
{
        TRACE();
        return sf_create_aux(parent, dentry, mode, 1);
}

static int
sf_unlink_aux(struct inode *parent, struct dentry *dentry, int fDirectory)
{
        int rc, err;
        struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb);
        struct sf_inode_info *sf_i = GET_INODE_INFO(parent);
        SHFLSTRING *path;

        TRACE();
        BUG_ON(!sf_g);

        err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path);
        if (err) {
                goto fail0;
        }

        rc = vboxCallRemove(&client_handle, &sf_g->map, path,
                             fDirectory ? SHFL_REMOVE_DIR : SHFL_REMOVE_FILE);
        if (RT_FAILURE(rc)) {
                LogFunc(("(%d): vboxCallRemove(%s) failed rc=%Rrc\n", fDirectory,
                         path->String.utf8, rc));
                         err = -RTErrConvertToErrno(rc);
                goto fail1;
        }

        /* directory access/change time changed */
        sf_i->force_restat = 1;
        /* directory content changed */
        sf_i->force_reread = 1;
        kfree(path);
        return 0;

 fail1:
        kfree(path);
 fail0:
        return err;
}

static int
sf_unlink(struct inode *parent, struct dentry *dentry)
{
        TRACE();
        return sf_unlink_aux(parent, dentry, 0);
}

static int
sf_rmdir(struct inode *parent, struct dentry *dentry)
{
        TRACE();
        return sf_unlink_aux(parent, dentry, 1);
}

static int
sf_rename(struct inode *old_parent, struct dentry *old_dentry,
           struct inode *new_parent, struct dentry *new_dentry)
{
        int err = 0, rc = VINF_SUCCESS;
        struct sf_glob_info *sf_g = GET_GLOB_INFO(old_parent->i_sb);

        TRACE();

        if (sf_g != GET_GLOB_INFO(new_parent->i_sb)) {
                LogFunc(("rename with different roots\n"));
                err = -EINVAL;
        } else {
                struct sf_inode_info *sf_old_i = GET_INODE_INFO(old_parent);
                struct sf_inode_info *sf_new_i = GET_INODE_INFO(new_parent);
                /* As we save the relative path inside the inode structure, we need to change
                  this if the rename is successful. */
                struct sf_inode_info *sf_file_i = GET_INODE_INFO(old_dentry->d_inode);
                SHFLSTRING *old_path;
                SHFLSTRING *new_path;

                BUG_ON(!sf_old_i);
                BUG_ON(!sf_new_i);
                BUG_ON(!sf_file_i);

                old_path = sf_file_i->path;
                err = sf_path_from_dentry(__func__, sf_g, sf_new_i,
                                          new_dentry, &new_path);
                if (err) {
                        LogFunc(("failed to create new path\n"));
                } else {
                        int is_dir = ((old_dentry->d_inode->i_mode & S_IFDIR) != 0);

                        rc = vboxCallRename(&client_handle, &sf_g->map, old_path,
                                             new_path, is_dir ? 0 : SHFL_RENAME_FILE | SHFL_RENAME_REPLACE_IF_EXISTS);
                        if (RT_SUCCESS(rc)) {
                                kfree(old_path);
                                sf_new_i->force_restat = 1;
                                sf_old_i->force_restat = 1; /* XXX: needed? */
                                /* Set the new relative path in the inode. */
                                sf_file_i->path = new_path;
                        } else {
                                LogFunc(("vboxCallRename failed rc=%Rrc\n", rc));
                                err = -RTErrConvertToErrno(rc);
                        }
                        if (0 != err)
                                kfree(new_path);
                }
        }
        return err;
}

struct inode_operations sf_dir_iops = {
        .lookup     = sf_lookup,
        .create     = sf_create,
        .mkdir      = sf_mkdir,
        .rmdir      = sf_rmdir,
        .unlink     = sf_unlink,
        .rename     = sf_rename,
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)
        .revalidate = sf_inode_revalidate
#else
        .getattr    = sf_getattr,
        .setattr    = sf_setattr
#endif
};
