root/fs/vboxsf/dir.c
// SPDX-License-Identifier: MIT
/*
 * VirtualBox Guest Shared Folders support: Directory inode and file operations
 *
 * Copyright (C) 2006-2018 Oracle Corporation
 */

#include <linux/namei.h>
#include <linux/vbox_utils.h>
#include "vfsmod.h"

static int vboxsf_dir_open(struct inode *inode, struct file *file)
{
        struct vboxsf_sbi *sbi = VBOXSF_SBI(inode->i_sb);
        struct shfl_createparms params = {};
        struct vboxsf_dir_info *sf_d;
        int err;

        sf_d = vboxsf_dir_info_alloc();
        if (!sf_d)
                return -ENOMEM;

        params.handle = SHFL_HANDLE_NIL;
        params.create_flags = SHFL_CF_DIRECTORY | SHFL_CF_ACT_OPEN_IF_EXISTS |
                              SHFL_CF_ACT_FAIL_IF_NEW | SHFL_CF_ACCESS_READ;

        err = vboxsf_create_at_dentry(file_dentry(file), &params);
        if (err)
                goto err_free_dir_info;

        if (params.result != SHFL_FILE_EXISTS) {
                err = -ENOENT;
                goto err_close;
        }

        err = vboxsf_dir_read_all(sbi, sf_d, params.handle);
        if (err)
                goto err_close;

        vboxsf_close(sbi->root, params.handle);
        file->private_data = sf_d;
        return 0;

err_close:
        vboxsf_close(sbi->root, params.handle);
err_free_dir_info:
        vboxsf_dir_info_free(sf_d);
        return err;
}

static int vboxsf_dir_release(struct inode *inode, struct file *file)
{
        if (file->private_data)
                vboxsf_dir_info_free(file->private_data);

        return 0;
}

static unsigned int vboxsf_get_d_type(u32 mode)
{
        unsigned int d_type;

        switch (mode & SHFL_TYPE_MASK) {
        case SHFL_TYPE_FIFO:
                d_type = DT_FIFO;
                break;
        case SHFL_TYPE_DEV_CHAR:
                d_type = DT_CHR;
                break;
        case SHFL_TYPE_DIRECTORY:
                d_type = DT_DIR;
                break;
        case SHFL_TYPE_DEV_BLOCK:
                d_type = DT_BLK;
                break;
        case SHFL_TYPE_FILE:
                d_type = DT_REG;
                break;
        case SHFL_TYPE_SYMLINK:
                d_type = DT_LNK;
                break;
        case SHFL_TYPE_SOCKET:
                d_type = DT_SOCK;
                break;
        case SHFL_TYPE_WHITEOUT:
                d_type = DT_WHT;
                break;
        default:
                d_type = DT_UNKNOWN;
                break;
        }
        return d_type;
}

static bool vboxsf_dir_emit(struct file *dir, struct dir_context *ctx)
{
        struct vboxsf_sbi *sbi = VBOXSF_SBI(file_inode(dir)->i_sb);
        struct vboxsf_dir_info *sf_d = dir->private_data;
        struct shfl_dirinfo *info;
        struct vboxsf_dir_buf *b;
        unsigned int d_type;
        loff_t i, cur = 0;
        ino_t fake_ino;
        void *end;
        int err;

        list_for_each_entry(b, &sf_d->info_list, head) {
try_next_entry:
                if (ctx->pos >= cur + b->entries) {
                        cur += b->entries;
                        continue;
                }

                /*
                 * Note the vboxsf_dir_info objects we are iterating over here
                 * are variable sized, so the info pointer may end up being
                 * unaligned. This is how we get the data from the host.
                 * Since vboxsf is only supported on x86 machines this is not
                 * a problem.
                 */
                for (i = 0, info = b->buf; i < ctx->pos - cur; i++) {
                        end = &info->name.string.utf8[info->name.size];
                        /* Only happens if the host gives us corrupt data */
                        if (WARN_ON(end > (b->buf + b->used)))
                                return false;
                        info = end;
                }

                end = &info->name.string.utf8[info->name.size];
                if (WARN_ON(end > (b->buf + b->used)))
                        return false;

                /* Info now points to the right entry, emit it. */
                d_type = vboxsf_get_d_type(info->info.attr.mode);

                /*
                 * On 32-bit systems pos is 64-bit signed, while ino is 32-bit
                 * unsigned so fake_ino may overflow, check for this.
                 */
                if ((ino_t)(ctx->pos + 1) != (u64)(ctx->pos + 1)) {
                        vbg_err("vboxsf: fake ino overflow, truncating dir\n");
                        return false;
                }
                fake_ino = ctx->pos + 1;

                if (sbi->nls) {
                        char d_name[NAME_MAX];

                        err = vboxsf_nlscpy(sbi, d_name, NAME_MAX,
                                            info->name.string.utf8,
                                            info->name.length);
                        if (err) {
                                /* skip erroneous entry and proceed */
                                ctx->pos += 1;
                                goto try_next_entry;
                        }

                        return dir_emit(ctx, d_name, strlen(d_name),
                                        fake_ino, d_type);
                }

                return dir_emit(ctx, info->name.string.utf8, info->name.length,
                                fake_ino, d_type);
        }

        return false;
}

static int vboxsf_dir_iterate(struct file *dir, struct dir_context *ctx)
{
        bool emitted;

        do {
                emitted = vboxsf_dir_emit(dir, ctx);
                if (emitted)
                        ctx->pos += 1;
        } while (emitted);

        return 0;
}

WRAP_DIR_ITER(vboxsf_dir_iterate) // FIXME!
const struct file_operations vboxsf_dir_fops = {
        .open = vboxsf_dir_open,
        .iterate_shared = shared_vboxsf_dir_iterate,
        .release = vboxsf_dir_release,
        .read = generic_read_dir,
        .llseek = generic_file_llseek,
};

/*
 * This is called during name resolution/lookup to check if the @dentry in
 * the cache is still valid. the job is handled by vboxsf_inode_revalidate.
 */
static int vboxsf_dentry_revalidate(struct inode *dir, const struct qstr *name,
                                    struct dentry *dentry, unsigned int flags)
{
        if (flags & LOOKUP_RCU)
                return -ECHILD;

        if (d_really_is_positive(dentry))
                return vboxsf_inode_revalidate(dentry) == 0;
        else
                return vboxsf_stat_dentry(dentry, NULL) == -ENOENT;
}

const struct dentry_operations vboxsf_dentry_ops = {
        .d_revalidate = vboxsf_dentry_revalidate
};

/* iops */

static struct dentry *vboxsf_dir_lookup(struct inode *parent,
                                        struct dentry *dentry,
                                        unsigned int flags)
{
        struct vboxsf_sbi *sbi = VBOXSF_SBI(parent->i_sb);
        struct shfl_fsobjinfo fsinfo;
        struct inode *inode;
        int err;

        dentry->d_time = jiffies;

        err = vboxsf_stat_dentry(dentry, &fsinfo);
        if (err) {
                inode = (err == -ENOENT) ? NULL : ERR_PTR(err);
        } else {
                inode = vboxsf_new_inode(parent->i_sb);
                if (!IS_ERR(inode))
                        vboxsf_init_inode(sbi, inode, &fsinfo, false);
        }

        return d_splice_alias(inode, dentry);
}

static int vboxsf_dir_instantiate(struct inode *parent, struct dentry *dentry,
                                  struct shfl_fsobjinfo *info)
{
        struct vboxsf_sbi *sbi = VBOXSF_SBI(parent->i_sb);
        struct vboxsf_inode *sf_i;
        struct inode *inode;

        inode = vboxsf_new_inode(parent->i_sb);
        if (IS_ERR(inode))
                return PTR_ERR(inode);

        sf_i = VBOXSF_I(inode);
        /* The host may have given us different attr then requested */
        sf_i->force_restat = 1;
        vboxsf_init_inode(sbi, inode, info, false);

        d_instantiate(dentry, inode);

        return 0;
}

static int vboxsf_dir_create(struct inode *parent, struct dentry *dentry,
                             umode_t mode, bool is_dir, bool excl, u64 *handle_ret)
{
        struct vboxsf_inode *sf_parent_i = VBOXSF_I(parent);
        struct vboxsf_sbi *sbi = VBOXSF_SBI(parent->i_sb);
        struct shfl_createparms params = {};
        int err;

        params.handle = SHFL_HANDLE_NIL;
        params.create_flags = SHFL_CF_ACT_CREATE_IF_NEW | SHFL_CF_ACCESS_READWRITE;
        if (is_dir)
                params.create_flags |= SHFL_CF_DIRECTORY;
        if (excl)
                params.create_flags |= SHFL_CF_ACT_FAIL_IF_EXISTS;

        params.info.attr.mode = (mode & 0777) |
                                (is_dir ? SHFL_TYPE_DIRECTORY : SHFL_TYPE_FILE);
        params.info.attr.additional = SHFLFSOBJATTRADD_NOTHING;

        err = vboxsf_create_at_dentry(dentry, &params);
        if (err)
                return err;

        if (params.result != SHFL_FILE_CREATED)
                return -EPERM;

        err = vboxsf_dir_instantiate(parent, dentry, &params.info);
        if (err)
                goto out;

        /* parent directory access/change time changed */
        sf_parent_i->force_restat = 1;

out:
        if (err == 0 && handle_ret)
                *handle_ret = params.handle;
        else
                vboxsf_close(sbi->root, params.handle);

        return err;
}

static int vboxsf_dir_mkfile(struct mnt_idmap *idmap,
                             struct inode *parent, struct dentry *dentry,
                             umode_t mode, bool excl)
{
        return vboxsf_dir_create(parent, dentry, mode, false, excl, NULL);
}

static struct dentry *vboxsf_dir_mkdir(struct mnt_idmap *idmap,
                                       struct inode *parent, struct dentry *dentry,
                                       umode_t mode)
{
        return ERR_PTR(vboxsf_dir_create(parent, dentry, mode, true, true, NULL));
}

static int vboxsf_dir_atomic_open(struct inode *parent, struct dentry *dentry,
                                  struct file *file, unsigned int flags, umode_t mode)
{
        struct vboxsf_sbi *sbi = VBOXSF_SBI(parent->i_sb);
        struct vboxsf_handle *sf_handle;
        u64 handle;
        int err;

        if (d_in_lookup(dentry)) {
                struct dentry *res = vboxsf_dir_lookup(parent, dentry, 0);
                if (res || d_really_is_positive(dentry))
                        return finish_no_open(file, res);
        }

        /* Only creates */
        if (!(flags & O_CREAT))
                return finish_no_open(file, NULL);

        err = vboxsf_dir_create(parent, dentry, mode, false, flags & O_EXCL, &handle);
        if (err)
                return err;

        sf_handle = vboxsf_create_sf_handle(d_inode(dentry), handle, SHFL_CF_ACCESS_READWRITE);
        if (IS_ERR(sf_handle)) {
                vboxsf_close(sbi->root, handle);
                return PTR_ERR(sf_handle);
        }

        err = finish_open(file, dentry, generic_file_open);
        if (err) {
                /* This also closes the handle passed to vboxsf_create_sf_handle() */
                vboxsf_release_sf_handle(d_inode(dentry), sf_handle);
                return err;
        }

        file->private_data = sf_handle;
        file->f_mode |= FMODE_CREATED;
        return 0;
}

static int vboxsf_dir_unlink(struct inode *parent, struct dentry *dentry)
{
        struct vboxsf_sbi *sbi = VBOXSF_SBI(parent->i_sb);
        struct vboxsf_inode *sf_parent_i = VBOXSF_I(parent);
        struct inode *inode = d_inode(dentry);
        struct shfl_string *path;
        u32 flags;
        int err;

        if (S_ISDIR(inode->i_mode))
                flags = SHFL_REMOVE_DIR;
        else
                flags = SHFL_REMOVE_FILE;

        if (S_ISLNK(inode->i_mode))
                flags |= SHFL_REMOVE_SYMLINK;

        path = vboxsf_path_from_dentry(sbi, dentry);
        if (IS_ERR(path))
                return PTR_ERR(path);

        err = vboxsf_remove(sbi->root, path, flags);
        __putname(path);
        if (err)
                return err;

        /* parent directory access/change time changed */
        sf_parent_i->force_restat = 1;

        return 0;
}

static int vboxsf_dir_rename(struct mnt_idmap *idmap,
                             struct inode *old_parent,
                             struct dentry *old_dentry,
                             struct inode *new_parent,
                             struct dentry *new_dentry,
                             unsigned int flags)
{
        struct vboxsf_sbi *sbi = VBOXSF_SBI(old_parent->i_sb);
        struct vboxsf_inode *sf_old_parent_i = VBOXSF_I(old_parent);
        struct vboxsf_inode *sf_new_parent_i = VBOXSF_I(new_parent);
        u32 shfl_flags = SHFL_RENAME_FILE | SHFL_RENAME_REPLACE_IF_EXISTS;
        struct shfl_string *old_path, *new_path;
        int err;

        if (flags)
                return -EINVAL;

        old_path = vboxsf_path_from_dentry(sbi, old_dentry);
        if (IS_ERR(old_path))
                return PTR_ERR(old_path);

        new_path = vboxsf_path_from_dentry(sbi, new_dentry);
        if (IS_ERR(new_path)) {
                err = PTR_ERR(new_path);
                goto err_put_old_path;
        }

        if (d_inode(old_dentry)->i_mode & S_IFDIR)
                shfl_flags = 0;

        err = vboxsf_rename(sbi->root, old_path, new_path, shfl_flags);
        if (err == 0) {
                /* parent directories access/change time changed */
                sf_new_parent_i->force_restat = 1;
                sf_old_parent_i->force_restat = 1;
        }

        __putname(new_path);
err_put_old_path:
        __putname(old_path);
        return err;
}

static int vboxsf_dir_symlink(struct mnt_idmap *idmap,
                              struct inode *parent, struct dentry *dentry,
                              const char *symname)
{
        struct vboxsf_inode *sf_parent_i = VBOXSF_I(parent);
        struct vboxsf_sbi *sbi = VBOXSF_SBI(parent->i_sb);
        int symname_size = strlen(symname) + 1;
        struct shfl_string *path, *ssymname;
        struct shfl_fsobjinfo info;
        int err;

        path = vboxsf_path_from_dentry(sbi, dentry);
        if (IS_ERR(path))
                return PTR_ERR(path);

        ssymname = kmalloc(SHFLSTRING_HEADER_SIZE + symname_size, GFP_KERNEL);
        if (!ssymname) {
                __putname(path);
                return -ENOMEM;
        }
        ssymname->length = symname_size - 1;
        ssymname->size = symname_size;
        memcpy(ssymname->string.utf8, symname, symname_size);

        err = vboxsf_symlink(sbi->root, path, ssymname, &info);
        kfree(ssymname);
        __putname(path);
        if (err) {
                /* -EROFS means symlinks are note support -> -EPERM */
                return (err == -EROFS) ? -EPERM : err;
        }

        err = vboxsf_dir_instantiate(parent, dentry, &info);
        if (err)
                return err;

        /* parent directory access/change time changed */
        sf_parent_i->force_restat = 1;
        return 0;
}

const struct inode_operations vboxsf_dir_iops = {
        .lookup  = vboxsf_dir_lookup,
        .create  = vboxsf_dir_mkfile,
        .mkdir   = vboxsf_dir_mkdir,
        .atomic_open = vboxsf_dir_atomic_open,
        .rmdir   = vboxsf_dir_unlink,
        .unlink  = vboxsf_dir_unlink,
        .rename  = vboxsf_dir_rename,
        .symlink = vboxsf_dir_symlink,
        .getattr = vboxsf_getattr,
        .setattr = vboxsf_setattr,
};