Centos-kernel-stream-9/fs/vboxsf/utils.c

572 lines
13 KiB
C
Raw Normal View History

// 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->i_atime = ns_to_timespec64(
info->access_time.ns_relative_to_unix_epoch);
inode->i_ctime = ns_to_timespec64(
info->change_time.ns_relative_to_unix_epoch);
inode->i_mtime = 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 prev_mtime;
struct inode *inode;
int err;
if (!dentry || !d_really_is_positive(dentry))
return -EINVAL;
inode = d_inode(dentry);
prev_mtime = inode->i_mtime;
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.
*/
if (timespec64_compare(&inode->i_mtime, &prev_mtime) > 0)
invalidate_inode_pages2(inode->i_mapping);
return 0;
}
fs: port ->getattr() to pass mnt_idmap JIRA: https://issues.redhat.com/browse/RHEL-33888 Status: Linus Conflicts: CentOS Stream has commit 3e0b6f1fa9a1c ("afs: use read_seqbegin() in afs_check_validity() and afs_getattr()"), manually apply hunk #2 to fs/afs/inode.c. CentOS Stream commit 3b06927229296 {"afs: split afs_pagecache_valid() out of afs_validate()") is present which causes a reject in fs/afs/internal.h, manually apply hunk to fs/afs/internal.h. For consistency drop btrfs hunks because it isn't supported in CentOS Stream and other backports also drop such hunks. CentOS Stream commit 48fa94aacd100 ("ceph: fscrypt_auth handling for ceph") alters the definition of _ceph_setattr() causing fuzz. The cifs source has been moved in CentOS Stream so manually apply rejected hunks to fs/smb/client/cifsfs.h and fs/smb/client/inode.c. Upstream commit 2e1d66379ece5 ("staging: erofs: drop the extern prefix for function definitions") caused strange behaviour when applying this patch, there was a conflict in fs/erofs/internal.h but after a refresh the hunk and context looked ok. The hunk had to be manually applied. Upstream commit 2db0487faa211 ("f2fs: move f2fs_force_buffered_io() into file.c") is not present in CentOS Stream which causes fuzz when applying the first hunk to fs/f2fs/file.c. Upstream commit 30abce053f811 ("fat: report creation time in statx") is not present in CentOS Stream which caused a reject so apply change manually. Dropped hunks for ksmbd because the source is not present in the CentOS Stream source tree. Dropped hunks for ntfs3 because the source is not present in the CentOS Stream source tree. There was fuzz with hunk #2 against fs/nfs/inode.c but I was unable to see any difference. CentOS Stream commit 98ba731fc7eae ("ovl: Move xattr support to new xattrs.c file") is present which caused fuzz in fs/overlayfs/overlayfs.h. Upstream commit d919a1e79bac8 ("proc: fix a dentry lock race between release_task and lookup") is not present in CentOS Stream causing fuzz applying hunk #1 against fs/proc/base.c. CentOS Stream commit 20c470188c2eb ("vfs: plumb i_version handling into struct kstat") is present causing fuzz in hunk #2 against fs/stat.c. Upstream commit e0c49bd2b4d3c ("fs: sysv: Fix sysv_nblocks() returns wrong value") is not present in CentOS Stream causing fuzz applying hunk#1 against fs/sysv/itree.c. CentOS Stream commit 892da692fa5bc ("shmem: support idmapped mounts for tmpfs") is present so it's ok to pass idmap to generic_fillattr(). CentOS Stream commit f0f830cd7e01b {"ceph: create symlinks with encrypted and base64-encoded targets") uses the old struct user_namespace and so leaves those changes out, make those getattr() changes here. Allow for CentOS Stream commit 6c3396a0d8f2c ("kernfs: Introduce separate rwsem to protect inode attributes") which is already present. CentOS Stream commit f5219db0c03b6 ("KVM: fix Add KVM_CREATE_GUEST_MEMFD ioctl() for guest-specific backing memory") updated the upstream commit a7800aa80ea4d ("KVM: Add KVM_CREATE_GUEST_MEMFD ioctl() for guest-specific backing memory") to account for missing idmapping commits. Now we have updated the second and final place these changes were made make the final needed adjustment to match the original upstream patch. commit b74d24f7a74ffd2d42ca883d84b7422b8d545901 Author: Christian Brauner <brauner@kernel.org> Date: Fri Jan 13 12:49:12 2023 +0100 fs: port ->getattr() to pass mnt_idmap Convert to struct mnt_idmap. Last cycle we merged the necessary infrastructure in 256c8aed2b42 ("fs: introduce dedicated idmap type for mounts"). This is just the conversion to struct mnt_idmap. Currently we still pass around the plain namespace that was attached to a mount. This is in general pretty convenient but it makes it easy to conflate namespaces that are relevant on the filesystem with namespaces that are relevent on the mount level. Especially for non-vfs developers without detailed knowledge in this area this can be a potential source for bugs. Once the conversion to struct mnt_idmap is done all helpers down to the really low-level helpers will take a struct mnt_idmap argument instead of two namespace arguments. This way it becomes impossible to conflate the two eliminating the possibility of any bugs. All of the vfs and all filesystems only operate on struct mnt_idmap. Acked-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Christian Brauner (Microsoft) <brauner@kernel.org> Signed-off-by: Ian Kent <ikent@redhat.com>
2024-05-21 04:47:00 +00:00
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;
fs: port ->getattr() to pass mnt_idmap JIRA: https://issues.redhat.com/browse/RHEL-33888 Status: Linus Conflicts: CentOS Stream has commit 3e0b6f1fa9a1c ("afs: use read_seqbegin() in afs_check_validity() and afs_getattr()"), manually apply hunk #2 to fs/afs/inode.c. CentOS Stream commit 3b06927229296 {"afs: split afs_pagecache_valid() out of afs_validate()") is present which causes a reject in fs/afs/internal.h, manually apply hunk to fs/afs/internal.h. For consistency drop btrfs hunks because it isn't supported in CentOS Stream and other backports also drop such hunks. CentOS Stream commit 48fa94aacd100 ("ceph: fscrypt_auth handling for ceph") alters the definition of _ceph_setattr() causing fuzz. The cifs source has been moved in CentOS Stream so manually apply rejected hunks to fs/smb/client/cifsfs.h and fs/smb/client/inode.c. Upstream commit 2e1d66379ece5 ("staging: erofs: drop the extern prefix for function definitions") caused strange behaviour when applying this patch, there was a conflict in fs/erofs/internal.h but after a refresh the hunk and context looked ok. The hunk had to be manually applied. Upstream commit 2db0487faa211 ("f2fs: move f2fs_force_buffered_io() into file.c") is not present in CentOS Stream which causes fuzz when applying the first hunk to fs/f2fs/file.c. Upstream commit 30abce053f811 ("fat: report creation time in statx") is not present in CentOS Stream which caused a reject so apply change manually. Dropped hunks for ksmbd because the source is not present in the CentOS Stream source tree. Dropped hunks for ntfs3 because the source is not present in the CentOS Stream source tree. There was fuzz with hunk #2 against fs/nfs/inode.c but I was unable to see any difference. CentOS Stream commit 98ba731fc7eae ("ovl: Move xattr support to new xattrs.c file") is present which caused fuzz in fs/overlayfs/overlayfs.h. Upstream commit d919a1e79bac8 ("proc: fix a dentry lock race between release_task and lookup") is not present in CentOS Stream causing fuzz applying hunk #1 against fs/proc/base.c. CentOS Stream commit 20c470188c2eb ("vfs: plumb i_version handling into struct kstat") is present causing fuzz in hunk #2 against fs/stat.c. Upstream commit e0c49bd2b4d3c ("fs: sysv: Fix sysv_nblocks() returns wrong value") is not present in CentOS Stream causing fuzz applying hunk#1 against fs/sysv/itree.c. CentOS Stream commit 892da692fa5bc ("shmem: support idmapped mounts for tmpfs") is present so it's ok to pass idmap to generic_fillattr(). CentOS Stream commit f0f830cd7e01b {"ceph: create symlinks with encrypted and base64-encoded targets") uses the old struct user_namespace and so leaves those changes out, make those getattr() changes here. Allow for CentOS Stream commit 6c3396a0d8f2c ("kernfs: Introduce separate rwsem to protect inode attributes") which is already present. CentOS Stream commit f5219db0c03b6 ("KVM: fix Add KVM_CREATE_GUEST_MEMFD ioctl() for guest-specific backing memory") updated the upstream commit a7800aa80ea4d ("KVM: Add KVM_CREATE_GUEST_MEMFD ioctl() for guest-specific backing memory") to account for missing idmapping commits. Now we have updated the second and final place these changes were made make the final needed adjustment to match the original upstream patch. commit b74d24f7a74ffd2d42ca883d84b7422b8d545901 Author: Christian Brauner <brauner@kernel.org> Date: Fri Jan 13 12:49:12 2023 +0100 fs: port ->getattr() to pass mnt_idmap Convert to struct mnt_idmap. Last cycle we merged the necessary infrastructure in 256c8aed2b42 ("fs: introduce dedicated idmap type for mounts"). This is just the conversion to struct mnt_idmap. Currently we still pass around the plain namespace that was attached to a mount. This is in general pretty convenient but it makes it easy to conflate namespaces that are relevant on the filesystem with namespaces that are relevent on the mount level. Especially for non-vfs developers without detailed knowledge in this area this can be a potential source for bugs. Once the conversion to struct mnt_idmap is done all helpers down to the really low-level helpers will take a struct mnt_idmap argument instead of two namespace arguments. This way it becomes impossible to conflate the two eliminating the possibility of any bugs. All of the vfs and all filesystems only operate on struct mnt_idmap. Acked-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Christian Brauner (Microsoft) <brauner@kernel.org> Signed-off-by: Ian Kent <ikent@redhat.com>
2024-05-21 04:47:00 +00:00
generic_fillattr(&nop_mnt_idmap, d_inode(dentry), kstat);
return 0;
}
fs: port ->setattr() to pass mnt_idmap JIRA: https://issues.redhat.com/browse/RHEL-33888 Status: Linus Conflicts: CentOS Stream commit 3c29fadfb1ba7 ("afs: split afs_pagecache_valid() out of afs_validate()") is present, manually adjust hunk #1 of fs/afs/internal.h. For consistency drop btrfs hunks because it isn't supported in CentOS Stream and other backports also drop such hunks. CentOS Stream commit 48fa94aacd100 ("ceph: fscrypt_auth handling for ceph") alters the definition of _ceph_setattr(), adjust manually. CentOS Stream commit 34b2a2b5a3b7e {"ceph: add some fscrypt guardrails") introduces a call to fscrypt_prepare_setattr() which causes fuzz when applying. The cifs source has been moved in CentOS Stream so manually apply rejected hunks to fs/smb/client/cifsfs.h and fs/smb/client/inode.c. Upstream commit 5a646fb3a3e2d ("coda: avoid doing bad things on inode type changes during revalidation") is not present which causes fuzz in fs/coda/coda_linux.h. Dropped hunks for ntfs3 because the source is not present in the CentOS Stream source tree. CentOS Stream commit 98ba731fc7eae ("ovl: Move xattr support to new xattrs.c file") is presnt so manually apply hunk. CentOS Stream commit 892da692fa5bc ("shmem: support idmapped mounts for tmpfs") is present so it's ok to pass idmap to setattr_prepare() and setattr_copy(). Update to add incremental changes needed due to CentOS Stream commit 469e1d13f6e5f ("shmem: quota support"). Allow for CentOS Stream commit 6c3396a0d8f2c ("kernfs: Introduce separate rwsem to protect inode attributes") which is already present. CentOS Stream commit f5219db0c03b6 ("KVM: fix Add KVM_CREATE_GUEST_MEMFD ioctl() for guest-specific backing memory") updated the upstream commit a7800aa80ea4d ("KVM: Add KVM_CREATE_GUEST_MEMFD ioctl() for guest-specific backing memory") to account for missing idmapping commits. Now we have updated one of the two places these changes were made make one of the needed adjustments to match the original upstream patch. commit c1632a0f11209338fc300c66252bcc4686e609e8 Author: Christian Brauner <brauner@kernel.org> Date: Fri Jan 13 12:49:11 2023 +0100 fs: port ->setattr() to pass mnt_idmap Convert to struct mnt_idmap. Last cycle we merged the necessary infrastructure in 256c8aed2b42 ("fs: introduce dedicated idmap type for mounts"). This is just the conversion to struct mnt_idmap. Currently we still pass around the plain namespace that was attached to a mount. This is in general pretty convenient but it makes it easy to conflate namespaces that are relevant on the filesystem with namespaces that are relevent on the mount level. Especially for non-vfs developers without detailed knowledge in this area this can be a potential source for bugs. Once the conversion to struct mnt_idmap is done all helpers down to the really low-level helpers will take a struct mnt_idmap argument instead of two namespace arguments. This way it becomes impossible to conflate the two eliminating the possibility of any bugs. All of the vfs and all filesystems only operate on struct mnt_idmap. Acked-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Christian Brauner (Microsoft) <brauner@kernel.org> Signed-off-by: Ian Kent <ikent@redhat.com>
2024-05-21 03:43:39 +00:00
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_len;
size_t out_bound_len;
size_t in_bound_len;
in = utf8_name;
in_bound_len = utf8_len;
out = name;
out_len = 0;
/* 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_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(sizeof(*b), GFP_KERNEL);
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(sizeof(*p), GFP_KERNEL);
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;
}