// 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 #include #include #include #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] */ void vboxsf_init_inode(struct vboxsf_sbi *sbi, struct inode *inode, const struct shfl_fsobjinfo *info) { const struct shfl_fsobjattr *attr; s64 allocated; int 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)) { inode->i_mode = sbi->o.dmode_set ? sbi->o.dmode : mode; inode->i_mode &= ~sbi->o.dmask; inode->i_mode |= S_IFDIR; 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 (SHFL_IS_SYMLINK(attr->mode)) { inode->i_mode = sbi->o.fmode_set ? sbi->o.fmode : mode; inode->i_mode &= ~sbi->o.fmask; inode->i_mode |= S_IFLNK; inode->i_op = &vboxsf_lnk_iops; set_nlink(inode, 1); } else { inode->i_mode = sbi->o.fmode_set ? sbi->o.fmode : mode; inode->i_mode &= ~sbi->o.fmask; inode->i_mode |= S_IFREG; inode->i_op = &vboxsf_reg_iops; inode->i_fop = &vboxsf_reg_fops; set_nlink(inode, 1); } 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); } 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, ¶ms); 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; vboxsf_init_inode(sbi, inode, &info); /* * 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; } int vboxsf_getattr(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; /* fall-through */ default: err = vboxsf_inode_revalidate(dentry); } if (err) return err; generic_fillattr(d_inode(dentry), kstat); return 0; } int vboxsf_setattr(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, ¶ms); 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 (struct shfl_string *)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; }