root/fs/vboxsf/utils.c
// SPDX-License-Identifier: MIT
/*
 * VirtualBox Guest Shared Folders support: Utility functions.
 * Mainly conversion from/to VirtualBox/Linux data structures.
 *
 * Copyright (C) 2006-2018 Oracle Corporation
 */

#include <linux/namei.h>
#include <linux/nls.h>
#include <linux/sizes.h>
#include <linux/pagemap.h>
#include <linux/vfs.h>
#include "vfsmod.h"

struct inode *vboxsf_new_inode(struct super_block *sb)
{
        struct vboxsf_sbi *sbi = VBOXSF_SBI(sb);
        struct inode *inode;
        unsigned long flags;
        int cursor, ret;
        u32 gen;

        inode = new_inode(sb);
        if (!inode)
                return ERR_PTR(-ENOMEM);

        idr_preload(GFP_KERNEL);
        spin_lock_irqsave(&sbi->ino_idr_lock, flags);
        cursor = idr_get_cursor(&sbi->ino_idr);
        ret = idr_alloc_cyclic(&sbi->ino_idr, inode, 1, 0, GFP_ATOMIC);
        if (ret >= 0 && ret < cursor)
                sbi->next_generation++;
        gen = sbi->next_generation;
        spin_unlock_irqrestore(&sbi->ino_idr_lock, flags);
        idr_preload_end();

        if (ret < 0) {
                iput(inode);
                return ERR_PTR(ret);
        }

        inode->i_ino = ret;
        inode->i_generation = gen;
        return inode;
}

/* set [inode] attributes based on [info], uid/gid based on [sbi] */
int vboxsf_init_inode(struct vboxsf_sbi *sbi, struct inode *inode,
                       const struct shfl_fsobjinfo *info, bool reinit)
{
        const struct shfl_fsobjattr *attr;
        s64 allocated;
        umode_t mode;

        attr = &info->attr;

#define mode_set(r) ((attr->mode & (SHFL_UNIX_##r)) ? (S_##r) : 0)

        mode = mode_set(IRUSR);
        mode |= mode_set(IWUSR);
        mode |= mode_set(IXUSR);

        mode |= mode_set(IRGRP);
        mode |= mode_set(IWGRP);
        mode |= mode_set(IXGRP);

        mode |= mode_set(IROTH);
        mode |= mode_set(IWOTH);
        mode |= mode_set(IXOTH);

#undef mode_set

        /* We use the host-side values for these */
        inode->i_flags |= S_NOATIME | S_NOCMTIME;
        inode->i_mapping->a_ops = &vboxsf_reg_aops;

        if (SHFL_IS_DIRECTORY(attr->mode)) {
                if (sbi->o.dmode_set)
                        mode = sbi->o.dmode;
                mode &= ~sbi->o.dmask;
                mode |= S_IFDIR;
                if (!reinit) {
                        inode->i_op = &vboxsf_dir_iops;
                        inode->i_fop = &vboxsf_dir_fops;
                        /*
                         * XXX: this probably should be set to the number of entries
                         * in the directory plus two (. ..)
                         */
                        set_nlink(inode, 1);
                } else if (!S_ISDIR(inode->i_mode))
                        return -ESTALE;
                inode->i_mode = mode;
        } else if (SHFL_IS_SYMLINK(attr->mode)) {
                if (sbi->o.fmode_set)
                        mode = sbi->o.fmode;
                mode &= ~sbi->o.fmask;
                mode |= S_IFLNK;
                if (!reinit) {
                        inode->i_op = &vboxsf_lnk_iops;
                        set_nlink(inode, 1);
                } else if (!S_ISLNK(inode->i_mode))
                        return -ESTALE;
                inode->i_mode = mode;
        } else {
                if (sbi->o.fmode_set)
                        mode = sbi->o.fmode;
                mode &= ~sbi->o.fmask;
                mode |= S_IFREG;
                if (!reinit) {
                        inode->i_op = &vboxsf_reg_iops;
                        inode->i_fop = &vboxsf_reg_fops;
                        set_nlink(inode, 1);
                } else if (!S_ISREG(inode->i_mode))
                        return -ESTALE;
                inode->i_mode = mode;
        }

        inode->i_uid = sbi->o.uid;
        inode->i_gid = sbi->o.gid;

        inode->i_size = info->size;
        inode->i_blkbits = 12;
        /* i_blocks always in units of 512 bytes! */
        allocated = info->allocated + 511;
        do_div(allocated, 512);
        inode->i_blocks = allocated;

        inode_set_atime_to_ts(inode,
                              ns_to_timespec64(info->access_time.ns_relative_to_unix_epoch));
        inode_set_ctime_to_ts(inode,
                              ns_to_timespec64(info->change_time.ns_relative_to_unix_epoch));
        inode_set_mtime_to_ts(inode,
                              ns_to_timespec64(info->modification_time.ns_relative_to_unix_epoch));
        return 0;
}

int vboxsf_create_at_dentry(struct dentry *dentry,
                            struct shfl_createparms *params)
{
        struct vboxsf_sbi *sbi = VBOXSF_SBI(dentry->d_sb);
        struct shfl_string *path;
        int err;

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

        err = vboxsf_create(sbi->root, path, params);
        __putname(path);

        return err;
}

int vboxsf_stat(struct vboxsf_sbi *sbi, struct shfl_string *path,
                struct shfl_fsobjinfo *info)
{
        struct shfl_createparms params = {};
        int err;

        params.handle = SHFL_HANDLE_NIL;
        params.create_flags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW;

        err = vboxsf_create(sbi->root, path, &params);
        if (err)
                return err;

        if (params.result != SHFL_FILE_EXISTS)
                return -ENOENT;

        if (info)
                *info = params.info;

        return 0;
}

int vboxsf_stat_dentry(struct dentry *dentry, struct shfl_fsobjinfo *info)
{
        struct vboxsf_sbi *sbi = VBOXSF_SBI(dentry->d_sb);
        struct shfl_string *path;
        int err;

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

        err = vboxsf_stat(sbi, path, info);
        __putname(path);
        return err;
}

int vboxsf_inode_revalidate(struct dentry *dentry)
{
        struct vboxsf_sbi *sbi;
        struct vboxsf_inode *sf_i;
        struct shfl_fsobjinfo info;
        struct timespec64 mtime, prev_mtime;
        struct inode *inode;
        int err;

        if (!dentry || !d_really_is_positive(dentry))
                return -EINVAL;

        inode = d_inode(dentry);
        prev_mtime = inode_get_mtime(inode);
        sf_i = VBOXSF_I(inode);
        sbi = VBOXSF_SBI(dentry->d_sb);
        if (!sf_i->force_restat) {
                if (time_before(jiffies, dentry->d_time + sbi->o.ttl))
                        return 0;
        }

        err = vboxsf_stat_dentry(dentry, &info);
        if (err)
                return err;

        dentry->d_time = jiffies;
        sf_i->force_restat = 0;
        err = vboxsf_init_inode(sbi, inode, &info, true);
        if (err)
                return err;

        /*
         * If the file was changed on the host side we need to invalidate the
         * page-cache for it.  Note this also gets triggered by our own writes,
         * this is unavoidable.
         */
        mtime = inode_get_mtime(inode);
        if (timespec64_compare(&mtime, &prev_mtime) > 0)
                invalidate_inode_pages2(inode->i_mapping);

        return 0;
}

int vboxsf_getattr(struct mnt_idmap *idmap, const struct path *path,
                   struct kstat *kstat, u32 request_mask, unsigned int flags)
{
        int err;
        struct dentry *dentry = path->dentry;
        struct inode *inode = d_inode(dentry);
        struct vboxsf_inode *sf_i = VBOXSF_I(inode);

        switch (flags & AT_STATX_SYNC_TYPE) {
        case AT_STATX_DONT_SYNC:
                err = 0;
                break;
        case AT_STATX_FORCE_SYNC:
                sf_i->force_restat = 1;
                fallthrough;
        default:
                err = vboxsf_inode_revalidate(dentry);
        }
        if (err)
                return err;

        generic_fillattr(&nop_mnt_idmap, request_mask, d_inode(dentry), kstat);
        return 0;
}

int vboxsf_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
                   struct iattr *iattr)
{
        struct vboxsf_inode *sf_i = VBOXSF_I(d_inode(dentry));
        struct vboxsf_sbi *sbi = VBOXSF_SBI(dentry->d_sb);
        struct shfl_createparms params = {};
        struct shfl_fsobjinfo info = {};
        u32 buf_len;
        int err;

        params.handle = SHFL_HANDLE_NIL;
        params.create_flags = SHFL_CF_ACT_OPEN_IF_EXISTS |
                              SHFL_CF_ACT_FAIL_IF_NEW |
                              SHFL_CF_ACCESS_ATTR_WRITE;

        /* this is at least required for Posix hosts */
        if (iattr->ia_valid & ATTR_SIZE)
                params.create_flags |= SHFL_CF_ACCESS_WRITE;

        err = vboxsf_create_at_dentry(dentry, &params);
        if (err || params.result != SHFL_FILE_EXISTS)
                return err ? err : -ENOENT;

#define mode_set(r) ((iattr->ia_mode & (S_##r)) ? SHFL_UNIX_##r : 0)

        /*
         * Setting the file size and setting the other attributes has to
         * be handled separately.
         */
        if (iattr->ia_valid & (ATTR_MODE | ATTR_ATIME | ATTR_MTIME)) {
                if (iattr->ia_valid & ATTR_MODE) {
                        info.attr.mode = mode_set(IRUSR);
                        info.attr.mode |= mode_set(IWUSR);
                        info.attr.mode |= mode_set(IXUSR);
                        info.attr.mode |= mode_set(IRGRP);
                        info.attr.mode |= mode_set(IWGRP);
                        info.attr.mode |= mode_set(IXGRP);
                        info.attr.mode |= mode_set(IROTH);
                        info.attr.mode |= mode_set(IWOTH);
                        info.attr.mode |= mode_set(IXOTH);

                        if (iattr->ia_mode & S_IFDIR)
                                info.attr.mode |= SHFL_TYPE_DIRECTORY;
                        else
                                info.attr.mode |= SHFL_TYPE_FILE;
                }

                if (iattr->ia_valid & ATTR_ATIME)
                        info.access_time.ns_relative_to_unix_epoch =
                                            timespec64_to_ns(&iattr->ia_atime);

                if (iattr->ia_valid & ATTR_MTIME)
                        info.modification_time.ns_relative_to_unix_epoch =
                                            timespec64_to_ns(&iattr->ia_mtime);

                /*
                 * Ignore ctime (inode change time) as it can't be set
                 * from userland anyway.
                 */

                buf_len = sizeof(info);
                err = vboxsf_fsinfo(sbi->root, params.handle,
                                    SHFL_INFO_SET | SHFL_INFO_FILE, &buf_len,
                                    &info);
                if (err) {
                        vboxsf_close(sbi->root, params.handle);
                        return err;
                }

                /* the host may have given us different attr then requested */
                sf_i->force_restat = 1;
        }

#undef mode_set

        if (iattr->ia_valid & ATTR_SIZE) {
                memset(&info, 0, sizeof(info));
                info.size = iattr->ia_size;
                buf_len = sizeof(info);
                err = vboxsf_fsinfo(sbi->root, params.handle,
                                    SHFL_INFO_SET | SHFL_INFO_SIZE, &buf_len,
                                    &info);
                if (err) {
                        vboxsf_close(sbi->root, params.handle);
                        return err;
                }

                /* the host may have given us different attr then requested */
                sf_i->force_restat = 1;
        }

        vboxsf_close(sbi->root, params.handle);

        /* Update the inode with what the host has actually given us. */
        if (sf_i->force_restat)
                vboxsf_inode_revalidate(dentry);

        return 0;
}

/*
 * [dentry] contains string encoded in coding system that corresponds
 * to [sbi]->nls, we must convert it to UTF8 here.
 * Returns a shfl_string allocated through __getname (must be freed using
 * __putname), or an ERR_PTR on error.
 */
struct shfl_string *vboxsf_path_from_dentry(struct vboxsf_sbi *sbi,
                                            struct dentry *dentry)
{
        struct shfl_string *shfl_path;
        int path_len, out_len, nb;
        char *buf, *path;
        wchar_t uni;
        u8 *out;

        buf = __getname();
        if (!buf)
                return ERR_PTR(-ENOMEM);

        path = dentry_path_raw(dentry, buf, PATH_MAX);
        if (IS_ERR(path)) {
                __putname(buf);
                return ERR_CAST(path);
        }
        path_len = strlen(path);

        if (sbi->nls) {
                shfl_path = __getname();
                if (!shfl_path) {
                        __putname(buf);
                        return ERR_PTR(-ENOMEM);
                }

                out = shfl_path->string.utf8;
                out_len = PATH_MAX - SHFLSTRING_HEADER_SIZE - 1;

                while (path_len) {
                        nb = sbi->nls->char2uni(path, path_len, &uni);
                        if (nb < 0) {
                                __putname(shfl_path);
                                __putname(buf);
                                return ERR_PTR(-EINVAL);
                        }
                        path += nb;
                        path_len -= nb;

                        nb = utf32_to_utf8(uni, out, out_len);
                        if (nb < 0) {
                                __putname(shfl_path);
                                __putname(buf);
                                return ERR_PTR(-ENAMETOOLONG);
                        }
                        out += nb;
                        out_len -= nb;
                }
                *out = 0;
                shfl_path->length = out - shfl_path->string.utf8;
                shfl_path->size = shfl_path->length + 1;
                __putname(buf);
        } else {
                if ((SHFLSTRING_HEADER_SIZE + path_len + 1) > PATH_MAX) {
                        __putname(buf);
                        return ERR_PTR(-ENAMETOOLONG);
                }
                /*
                 * dentry_path stores the name at the end of buf, but the
                 * shfl_string string we return must be properly aligned.
                 */
                shfl_path = (struct shfl_string *)buf;
                memmove(shfl_path->string.utf8, path, path_len);
                shfl_path->string.utf8[path_len] = 0;
                shfl_path->length = path_len;
                shfl_path->size = path_len + 1;
        }

        return shfl_path;
}

int vboxsf_nlscpy(struct vboxsf_sbi *sbi, char *name, size_t name_bound_len,
                  const unsigned char *utf8_name, size_t utf8_len)
{
        const char *in;
        char *out;
        size_t out_bound_len;
        size_t in_bound_len;

        in = utf8_name;
        in_bound_len = utf8_len;

        out = name;
        /* Reserve space for terminating 0 */
        out_bound_len = name_bound_len - 1;

        while (in_bound_len) {
                int nb;
                unicode_t uni;

                nb = utf8_to_utf32(in, in_bound_len, &uni);
                if (nb < 0)
                        return -EINVAL;

                in += nb;
                in_bound_len -= nb;

                nb = sbi->nls->uni2char(uni, out, out_bound_len);
                if (nb < 0)
                        return nb;

                out += nb;
                out_bound_len -= nb;
        }

        *out = 0;

        return 0;
}

static struct vboxsf_dir_buf *vboxsf_dir_buf_alloc(struct list_head *list)
{
        struct vboxsf_dir_buf *b;

        b = kmalloc_obj(*b);
        if (!b)
                return NULL;

        b->buf = kmalloc(DIR_BUFFER_SIZE, GFP_KERNEL);
        if (!b->buf) {
                kfree(b);
                return NULL;
        }

        b->entries = 0;
        b->used = 0;
        b->free = DIR_BUFFER_SIZE;
        list_add(&b->head, list);

        return b;
}

static void vboxsf_dir_buf_free(struct vboxsf_dir_buf *b)
{
        list_del(&b->head);
        kfree(b->buf);
        kfree(b);
}

struct vboxsf_dir_info *vboxsf_dir_info_alloc(void)
{
        struct vboxsf_dir_info *p;

        p = kmalloc_obj(*p);
        if (!p)
                return NULL;

        INIT_LIST_HEAD(&p->info_list);
        return p;
}

void vboxsf_dir_info_free(struct vboxsf_dir_info *p)
{
        struct list_head *list, *pos, *tmp;

        list = &p->info_list;
        list_for_each_safe(pos, tmp, list) {
                struct vboxsf_dir_buf *b;

                b = list_entry(pos, struct vboxsf_dir_buf, head);
                vboxsf_dir_buf_free(b);
        }
        kfree(p);
}

int vboxsf_dir_read_all(struct vboxsf_sbi *sbi, struct vboxsf_dir_info *sf_d,
                        u64 handle)
{
        struct vboxsf_dir_buf *b;
        u32 entries, size;
        int err = 0;
        void *buf;

        /* vboxsf_dirinfo returns 1 on end of dir */
        while (err == 0) {
                b = vboxsf_dir_buf_alloc(&sf_d->info_list);
                if (!b) {
                        err = -ENOMEM;
                        break;
                }

                buf = b->buf;
                size = b->free;

                err = vboxsf_dirinfo(sbi->root, handle, NULL, 0, 0,
                                     &size, buf, &entries);
                if (err < 0)
                        break;

                b->entries += entries;
                b->free -= size;
                b->used += size;
        }

        if (b && b->used == 0)
                vboxsf_dir_buf_free(b);

        /* -EILSEQ means the host could not translate a filename, ignore */
        if (err > 0 || err == -EILSEQ)
                err = 0;

        return err;
}