diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Additions/solaris/SharedFolders | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Additions/solaris/SharedFolders')
-rw-r--r-- | src/VBox/Additions/solaris/SharedFolders/Makefile.kmk | 113 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/SharedFolders/deps.asm | 49 | ||||
-rwxr-xr-x | src/VBox/Additions/solaris/SharedFolders/loadfs.sh | 105 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/SharedFolders/vboxfs.h | 106 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/SharedFolders/vboxfs_mount.c | 179 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.c | 1070 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.h | 200 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.c | 641 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.h | 90 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.c | 2500 | ||||
-rw-r--r-- | src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.h | 99 |
11 files changed, 5152 insertions, 0 deletions
diff --git a/src/VBox/Additions/solaris/SharedFolders/Makefile.kmk b/src/VBox/Additions/solaris/SharedFolders/Makefile.kmk new file mode 100644 index 00000000..9568ad75 --- /dev/null +++ b/src/VBox/Additions/solaris/SharedFolders/Makefile.kmk @@ -0,0 +1,113 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Solaris Shared folder kernel module. +# + +# +# Copyright (C) 2008-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +#ifneq ($(KBUILD_HOST),solaris) +#$(error "The Solaris guest additions can only be built on Solaris!") +#endif + +# +# vboxfs - The Shared Folder Driver +# +SYSMODS.solaris += vboxfs +vboxfs_TEMPLATE = VBoxGuestR0Drv +vboxfs_DEFS = VBOX_WITH_HGCM VBOX_SVN_REV=$(VBOX_SVN_REV) +vboxfs_DEPS += $(VBOX_SVN_REV_KMK) +vboxfs_INCS := \ + . +vboxfs_SOURCES = \ + vboxfs_vfs.c \ + vboxfs_vnode.c \ + vboxfs_prov.c +vboxfs_LIBS = \ + $(VBOX_LIB_VBGL_R0) +ifeq ($(KBUILD_HOST),solaris) + vboxfs_LDFLAGS.solaris += -N drv/vboxguest -N misc/ctf +else + vboxfs_SOURCES += deps.asm + vboxfs_deps.asm_ASFLAGS = -f bin -g null +endif +if ($(VBOX_SOLARIS_11_UPDATE_VERSION) > 1 \ + || ($(VBOX_SOLARIS_11_UPDATE_VERSION) == 1 && $(VBOX_SOLARIS_11_BUILD_VERSION) >= 10)) + vboxfs_DEFS += VBOX_VFS_EXTENDED_POLICY +endif + + +ifndef VBOX_OSE + # + # vboxfs_s10 - The Shared Folder Driver for Solaris 10 + # + SYSMODS.solaris += vboxfs_s10 + vboxfs_s10_TEMPLATE = VBoxGuestR0Drv + vboxfs_s10_DEFS = VBOX_WITH_HGCM VBOX_VFS_SOLARIS_10U6 VBOX_SVN_REV=$(VBOX_SVN_REV) + vboxfs_s10_DEPS += $(VBOX_SVN_REV_KMK) + vboxfs_s10_INCS := solaris10/ + vboxfs_s10_SOURCES = \ + vboxfs_vfs.c \ + vboxfs_vnode.c \ + vboxfs_prov.c + vboxfs_s10_LIBS = \ + $(VBOX_LIB_VBGL_R0) \ + $(VBOX_LIB_IPRT_GUEST_R0) + ifeq ($(KBUILD_HOST),solaris) + vboxfs_s10_LDFLAGS += -N drv/vboxguest -N misc/ctf + else + vboxfs_s10_SOURCES += deps.asm + vboxfs_s10_deps.asm_ASFLAGS = -f bin -g null + endif +endif # VBOX_OSE + + +# +# mount - Userland mount wrapper for vboxfs +# +PROGRAMS += vboxfsmount +vboxfsmount_TEMPLATE = VBoxGuestR3Exe +vboxfsmount_SOURCES = vboxfs_mount.c + + +# +# Load script. +# +INSTALLS += vboxfsload +vboxfsload_TEMPLATE = VBoxGuestR0Drv +vboxfsload_EXEC_SOURCES = loadfs.sh + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/solaris/SharedFolders/deps.asm b/src/VBox/Additions/solaris/SharedFolders/deps.asm new file mode 100644 index 00000000..d01557ed --- /dev/null +++ b/src/VBox/Additions/solaris/SharedFolders/deps.asm @@ -0,0 +1,49 @@ +; $Id: deps.asm $ +;; @file +; Solaris kernel module dependency +; + +; +; Copyright (C) 2012-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +%include "iprt/solaris/kmoddeps.mac" + +kmoddeps_header ; ELF header, section table and shared string table + +kmoddeps_dynstr_start ; ELF .dynstr section +kmoddeps_dynstr_string str_misc_ctf, "misc/ctf" +kmoddeps_dynstr_string str_drv_vboxguest, "drv/vboxguest" +kmoddeps_dynstr_end + +kmoddeps_dynamic_start ; ELF .dynamic section +kmoddeps_dynamic_needed str_misc_ctf +kmoddeps_dynamic_needed str_drv_vboxguest +kmoddeps_dynamic_end diff --git a/src/VBox/Additions/solaris/SharedFolders/loadfs.sh b/src/VBox/Additions/solaris/SharedFolders/loadfs.sh new file mode 100755 index 00000000..7a5c1006 --- /dev/null +++ b/src/VBox/Additions/solaris/SharedFolders/loadfs.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# $Id: loadfs.sh $ +## @file +# For GA development. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +DRVNAME="vboxfs" +MOUNTHLP="vboxfsmount" + +DRVFILE=`dirname "$0"` +DRVFILE=`cd "$DRVFILE" && pwd` +MOUNTHLPFILE="$DRVFILE/$MOUNTHLP" +DRVFILE="$DRVFILE/$DRVNAME" +if [ ! -f "$DRVFILE" ]; then + echo "loadfs.sh: Cannot find $DRVFILE or it's not a file..." + exit 1; +fi +if [ ! -f "$MOUNTHLPFILE" ]; then + echo "load.sh: Cannot find $MOUNTHLPFILE or it's not a file..." + exit 1; +fi + +SUDO=sudo +#set -x + +# Unload the driver if loaded. +for drv in $DRVNAME; +do + LOADED=`modinfo | grep -w "$drv"` + if test -n "$LOADED"; then + MODID=`echo "$LOADED" | cut -d ' ' -f 1` + $SUDO modunload -i $MODID; + LOADED=`modinfo | grep -w "$drv"`; + if test -n "$LOADED"; then + echo "load.sh: failed to unload $drv"; + dmesg | tail + exit 1; + fi + fi +done + +# +# Remove old stuff. +# +MY_RC=1 +set -e +$SUDO rm -f \ + "/usr/kernel/fs/${DRVNAME}" \ + "/usr/kernel/fs/amd64/${DRVNAME}" \ + "/etc/fs/vboxfs/mount" +sync +set +e + +# +# Install the mount program. +# +if [ ! -d /etc/fs/vboxfs ]; then + $SUDO mkdir -p /etc/fs/vboxfs +fi +$SUDO ln -sf "$MOUNTHLPFILE" /etc/fs/vboxfs/mount + +# +# Load the module. We can load it without copying it to /usr/kernel/fs/. +# +if $SUDO modload "$DRVFILE"; then + sync + MY_RC=0 +else + dmesg | tail + echo "load.sh: add_drv failed." +fi + +exit $MY_RC; + diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs.h b/src/VBox/Additions/solaris/SharedFolders/vboxfs.h new file mode 100644 index 00000000..db72f7a7 --- /dev/null +++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs.h @@ -0,0 +1,106 @@ +/* $Id: vboxfs.h $ */ +/** @file + * VirtualBox File System Driver for Solaris Guests, Internal Header. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_h +#define GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_HOST_NAME 256 +#define MAX_NLS_NAME 32 +/** Default stat cache ttl (in ms) */ +#define DEF_STAT_TTL_MS 200 + +/** The module name. */ +#define DEVICE_NAME "vboxfs" + +#ifdef _KERNEL + +#include <VBox/VBoxGuestLibSharedFolders.h> +#include <sys/vfs.h> + +/** VNode for VBoxVFS */ +typedef struct vboxvfs_vnode +{ + vnode_t *pVNode; + vattr_t Attr; + SHFLSTRING *pPath; + kmutex_t MtxContents; +} vboxvfs_vnode_t; + + +/** Per-file system mount instance data. */ +typedef struct vboxvfs_globinfo +{ + VBGLSFMAP Map; + int Ttl; + int Uid; + int Gid; + vfs_t *pVFS; + vboxvfs_vnode_t *pVNodeRoot; + kmutex_t MtxFS; +} vboxvfs_globinfo_t; + +extern struct vnodeops *g_pVBoxVFS_vnodeops; +extern const fs_operation_def_t g_VBoxVFS_vnodeops_template[]; +extern VBGLSFCLIENT g_VBoxVFSClient; + +/** Helper functions */ +extern int vboxvfs_Stat(const char *pszCaller, vboxvfs_globinfo_t *pVBoxVFSGlobalInfo, SHFLSTRING *pPath, + PSHFLFSOBJINFO pResult, boolean_t fAllowFailure); +extern void vboxvfs_InitVNode(vboxvfs_globinfo_t *pVBoxVFSGlobalInfo, vboxvfs_vnode_t *pVBoxVNode, + PSHFLFSOBJINFO pFSInfo); + + +/** Helper macros */ +#define VFS_TO_VBOXVFS(vfs) ((vboxvfs_globinfo_t *)((vfs)->vfs_data)) +#define VBOXVFS_TO_VFS(vboxvfs) ((vboxvfs)->pVFS) +#define VN_TO_VBOXVN(vnode) ((vboxvfs_vnode_t *)((vnode)->v_data)) +#define VBOXVN_TO_VN(vboxvnode) ((vboxvnode)->pVNode) + +#endif /* _KERNEL */ + +#ifdef __cplusplus +} +#endif + +#endif /* !GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_h */ + diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_mount.c b/src/VBox/Additions/solaris/SharedFolders/vboxfs_mount.c new file mode 100644 index 00000000..08505afb --- /dev/null +++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_mount.c @@ -0,0 +1,179 @@ +/* $Id: vboxfs_mount.c $ */ +/** @file + * VirtualBox File System Mount Helper, Solaris host. + * Userspace mount wrapper that parses mount (or user-specified) options + * and passes it to mount(2) syscall + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <sys/vfs.h> +#include <sys/mount.h> + +#include "vboxfs.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static char g_achOptBuf[MAX_MNTOPT_STR] = { '\0', }; +static const int g_RetErr = 33; +static const int g_RetMagic = 2; +static const int g_RetOK = 0; + +static void Usage(char *pszName) +{ + fprintf(stderr, "Usage: %s [OPTIONS] NAME MOUNTPOINT\n" + "Mount the VirtualBox shared folder NAME from the host system to MOUNTPOINT.\n" + "\n" + " -w mount the shared folder writable (the default)\n" + " -r mount the shared folder read-only\n" + " -o OPTION[,OPTION...] use the mount options specified\n" + "\n", pszName); + fprintf(stderr, "Available mount options are:\n" + "\n" + " rw mount writable (the default)\n" + " ro mount read only\n" + " uid=UID set the default file owner user id to UID\n" + " gid=GID set the default file owner group id to GID\n"); + fprintf(stderr, + " dmode=MODE override the mode for all directories (octal) to MODE\n" + " fmode=MODE override the mode for all regular files (octal) to MODE\n" + " umask=UMASK set the umask (bitmask of permissions not present) in (octal) UMASK\n" + " dmask=UMASK set the umask applied to directories only in (octal) UMASK\n" + " fmask=UMASK set the umask applied to regular files only in (octal) UMASK\n" + " stat_ttl=TTL set the \"time to live\" (in ms) for the stat caches (default %d)\n", DEF_STAT_TTL_MS); + fprintf(stderr, + " fsync honor fsync calls instead of ignoring them\n" + " ttl=TTL set the \"time to live\" to TID for the dentry\n" + " iocharset CHARSET use the character set CHARSET for i/o operations (default utf8)\n" + " convertcp CHARSET convert the shared folder name from the character set CHARSET to utf8\n\n" + "Less common used options:\n" + " noexec,exec,nodev,dev,nosuid,suid\n"); + exit(1); +} + +int main(int argc, char **argv) +{ + char *pszName = NULL; + char *pszSpecial = NULL; + char *pszMount = NULL; + char achType[MAXFIDSZ]; + int c = '?'; + int rc = -1; + int parseError = 0; + int mntFlags = 0; + int quietFlag = 0; + + pszName = strrchr(argv[0], '/'); + pszName = pszName ? pszName + 1 : argv[0]; + snprintf(achType, sizeof(achType), "%s_%s", DEVICE_NAME, pszName); + + while ((c = getopt(argc, argv, "o:rmoQ")) != EOF) + { + switch (c) + { + case '?': + { + parseError = 1; + break; + } + + case 'q': + { + quietFlag = 1; + break; + } + + case 'r': + { + mntFlags |= MS_RDONLY; + break; + } + + case 'O': + { + mntFlags |= MS_OVERLAY; + break; + } + + case 'm': + { + mntFlags |= MS_NOMNTTAB; + break; + } + + case 'o': + { + if (strlcpy(g_achOptBuf, optarg, sizeof(g_achOptBuf)) >= sizeof(g_achOptBuf)) + { + fprintf(stderr, "%s: invalid argument: %s\n", pszName, optarg); + return g_RetMagic; + } + break; + } + + default: + { + Usage(pszName); + break; + } + } + } + + if ( argc - optind != 2 + || parseError) + { + Usage(pszName); + } + + pszSpecial = argv[argc - 2]; + pszMount = argv[argc - 1]; + + rc = mount(pszSpecial, pszMount, mntFlags | MS_OPTIONSTR, DEVICE_NAME, NULL, 0, g_achOptBuf, sizeof(g_achOptBuf)); + if (rc) + { + fprintf(stderr, "mount:"); + perror(pszSpecial); + return g_RetErr; + } + + return g_RetOK; +} + diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.c b/src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.c new file mode 100644 index 00000000..94251d63 --- /dev/null +++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.c @@ -0,0 +1,1070 @@ +/* $Id: vboxfs_prov.c $ */ +/** @file + * VirtualBox File System for Solaris Guests, provider implementation. + * Portions contributed by: Ronald. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/* + * Provider interfaces for shared folder file system. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mntent.h> +#include <sys/param.h> +#include <sys/modctl.h> +#include <sys/mount.h> +#include <sys/policy.h> +#include <sys/atomic.h> +#include <sys/sysmacros.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/dirent.h> +#include <sys/file.h> +#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */ + +#include "vboxfs_prov.h" +#include <iprt/err.h> + +#define SFPROV_VERSION 1 + +static VBGLSFCLIENT vbox_client; + +static int sfprov_vbox2errno(int rc) +{ + if (rc == VERR_ACCESS_DENIED) + return (EACCES); + if (rc == VERR_INVALID_NAME) + return (ENOENT); + return (RTErrConvertToErrno(rc)); +} + +/* + * utility to create strings + */ +static SHFLSTRING * +sfprov_string(char *path, int *sz) +{ + SHFLSTRING *str; + int len = strlen(path); + + *sz = len + 1 + sizeof (*str) - sizeof (str->String); + str = kmem_zalloc(*sz, KM_SLEEP); + str->u16Size = len + 1; + str->u16Length = len; + strcpy(str->String.utf8, path); + return (str); +} + +sfp_connection_t * +sfprov_connect(int version) +{ + /* + * only one version for now, so must match + */ + int rc = -1; + if (version != SFPROV_VERSION) + { + cmn_err(CE_WARN, "sfprov_connect: wrong version. version=%d expected=%d\n", version, SFPROV_VERSION); + return NULL; + } + rc = VbglR0SfInit(); + if (RT_SUCCESS(rc)) + { + rc = VbglR0SfConnect(&vbox_client); + if (RT_SUCCESS(rc)) + { + rc = VbglR0SfSetUtf8(&vbox_client); + if (RT_SUCCESS(rc)) + { + return ((sfp_connection_t *)&vbox_client); + } + else + cmn_err(CE_WARN, "sfprov_connect: VbglR0SfSetUtf8() failed\n"); + + VbglR0SfDisconnect(&vbox_client); + } + else + cmn_err(CE_WARN, "sfprov_connect: VbglR0SfConnect() failed rc=%d\n", rc); + VbglR0SfTerm(); + } + else + cmn_err(CE_WARN, "sfprov_connect: VbglR0SfInit() failed rc=%d\n", rc); + return (NULL); +} + +void +sfprov_disconnect(sfp_connection_t *conn) +{ + if (conn != (sfp_connection_t *)&vbox_client) + cmn_err(CE_WARN, "sfprov_disconnect: bad argument\n"); + VbglR0SfDisconnect(&vbox_client); + VbglR0SfTerm(); +} + + +int +sfprov_mount(sfp_connection_t *conn, char *path, sfp_mount_t **mnt) +{ + sfp_mount_t *m; + SHFLSTRING *str; + int size; + int rc; + + m = kmem_zalloc(sizeof (*m), KM_SLEEP); + str = sfprov_string(path, &size); + rc = VbglR0SfMapFolder(&vbox_client, str, &m->map); + if (RT_FAILURE(rc)) { + cmn_err(CE_WARN, "sfprov_mount: VbglR0SfMapFolder() failed. path=%s rc=%d\n", path, rc); + kmem_free(m, sizeof (*m)); + *mnt = NULL; + rc = EINVAL; + } else { + *mnt = m; + rc = 0; + } + kmem_free(str, size); + return (rc); +} + +int +sfprov_unmount(sfp_mount_t *mnt) +{ + int rc; + + rc = VbglR0SfUnmapFolder(&vbox_client, &mnt->map); + if (RT_FAILURE(rc)) { + cmn_err(CE_WARN, "sfprov_mount: VbglR0SfUnmapFolder() failed rc=%d\n", rc); + rc = EINVAL; + } else { + rc = 0; + } + kmem_free(mnt, sizeof (*mnt)); + return (rc); +} + +/* + * query information about a mounted file system + */ +int +sfprov_get_fsinfo(sfp_mount_t *mnt, sffs_fsinfo_t *fsinfo) +{ + int rc; + SHFLVOLINFO info; + uint32_t bytes = sizeof(SHFLVOLINFO); + + rc = VbglR0SfFsInfo(&vbox_client, &mnt->map, 0, SHFL_INFO_GET | SHFL_INFO_VOLUME, + &bytes, (SHFLDIRINFO *)&info); + if (RT_FAILURE(rc)) + return (EINVAL); + + fsinfo->blksize = info.ulBytesPerAllocationUnit; + fsinfo->blksused = (info.ullTotalAllocationBytes - info.ullAvailableAllocationBytes) / info.ulBytesPerAllocationUnit; + fsinfo->blksavail = info.ullAvailableAllocationBytes / info.ulBytesPerAllocationUnit; + fsinfo->maxnamesize = info.fsProperties.cbMaxComponent; + fsinfo->readonly = info.fsProperties.fReadOnly; + return (0); +} + +/* + * file/directory information conversions. + */ +static void +sfprov_fmode_from_mode(RTFMODE *fMode, mode_t mode) +{ + RTFMODE m = 0; + +#define mode_set(r) ((mode) & (S_##r)) ? RTFS_UNIX_##r : 0 + m = mode_set (ISUID); + m |= mode_set (ISGID); + m |= (mode & S_ISVTX) ? RTFS_UNIX_ISTXT : 0; + + m |= mode_set (IRUSR); + m |= mode_set (IWUSR); + m |= mode_set (IXUSR); + + m |= mode_set (IRGRP); + m |= mode_set (IWGRP); + m |= mode_set (IXGRP); + + m |= mode_set (IROTH); + m |= mode_set (IWOTH); + m |= mode_set (IXOTH); +#undef mode_set + + if (S_ISDIR(mode)) + m |= RTFS_TYPE_DIRECTORY; + else if (S_ISREG(mode)) + m |= RTFS_TYPE_FILE; + else if (S_ISFIFO(mode)) + m |= RTFS_TYPE_FIFO; + else if (S_ISCHR(mode)) + m |= RTFS_TYPE_DEV_CHAR; + else if (S_ISBLK(mode)) + m |= RTFS_TYPE_DEV_BLOCK; + else if (S_ISLNK(mode)) + m |= RTFS_TYPE_SYMLINK; + else if (S_ISSOCK(mode)) + m |= RTFS_TYPE_SOCKET; + else + m |= RTFS_TYPE_FILE; + + *fMode = m; +} + +static void +sfprov_mode_from_fmode(sfp_mount_t *mnt, mode_t *mode, RTFMODE fMode) +{ + mode_t m = 0; + +#define mode_set_from_rt(r) ((fMode) & (RTFS_UNIX_##r)) ? (S_##r) : 0; + m = mode_set_from_rt(ISUID); + m |= mode_set_from_rt(ISGID); + m |= (fMode & RTFS_UNIX_ISTXT) ? S_ISVTX : 0; + + m |= mode_set_from_rt(IRUSR); + m |= mode_set_from_rt(IWUSR); + m |= mode_set_from_rt(IXUSR); + + m |= mode_set_from_rt(IRGRP); + m |= mode_set_from_rt(IWGRP); + m |= mode_set_from_rt(IXGRP); + + m |= mode_set_from_rt(IROTH); + m |= mode_set_from_rt(IWOTH); + m |= mode_set_from_rt(IXOTH); +#undef mode_set_from_rt + + if (RTFS_IS_DIRECTORY(fMode)) + { + m = mnt->sf_dmode != ~0U ? (mnt->sf_dmode & PERMMASK) : m; + m &= ~mnt->sf_dmask; + m |= S_IFDIR; + } + else + { + m = mnt->sf_fmode != ~0U ? (mnt->sf_fmode & PERMMASK) : m; + m &= ~mnt->sf_fmask; + + if (RTFS_IS_FILE(fMode)) + m |= S_IFREG; + else if (RTFS_IS_SYMLINK(fMode)) + m |= S_IFLNK; + else if (RTFS_IS_FIFO(fMode)) + m |= S_IFIFO; + else if (RTFS_IS_DEV_CHAR(fMode)) + m |= S_IFCHR; + else if (RTFS_IS_DEV_BLOCK(fMode)) + m |= S_IFBLK; + else if (RTFS_IS_SOCKET(fMode)) + m |= S_IFSOCK; + } + + *mode = m; +} + +static void +sfprov_ftime_from_timespec(timestruc_t *time, RTTIMESPEC *ts) +{ + uint64_t nanosec = RTTimeSpecGetNano(ts); + time->tv_sec = nanosec / UINT64_C(1000000000); + time->tv_nsec = nanosec % UINT64_C(1000000000); +} + +static void +sfprov_stat_from_info(sfp_mount_t *mnt, sffs_stat_t *stat, SHFLFSOBJINFO *info) +{ + sfprov_mode_from_fmode(mnt, &stat->sf_mode, info->Attr.fMode); + stat->sf_size = info->cbObject; + stat->sf_alloc = info->cbAllocated; + sfprov_ftime_from_timespec(&stat->sf_atime, &info->AccessTime); + sfprov_ftime_from_timespec(&stat->sf_mtime, &info->ModificationTime); + sfprov_ftime_from_timespec(&stat->sf_ctime, &info->ChangeTime); +} + +/* + * File operations: open/close/read/write/etc. + * + * open/create can return any relevant errno, however ENOENT + * generally means that the host file didn't exist. + */ +struct sfp_file { + SHFLHANDLE handle; + VBGLSFMAP map; /**< need this again for the close operation */ +}; + +int +sfprov_create( + sfp_mount_t *mnt, + char *path, + mode_t mode, + sfp_file_t **fp, + sffs_stat_t *stat) +{ + + int rc; + SHFLCREATEPARMS parms; + SHFLSTRING *str; + int size; + sfp_file_t *newfp; + + str = sfprov_string(path, &size); + parms.Handle = SHFL_HANDLE_NIL; + parms.Info.cbObject = 0; + sfprov_fmode_from_mode(&parms.Info.Attr.fMode, mode); + parms.CreateFlags = SHFL_CF_ACT_CREATE_IF_NEW | + SHFL_CF_ACT_REPLACE_IF_EXISTS | SHFL_CF_ACCESS_READWRITE; + rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms); + kmem_free(str, size); + + if (RT_FAILURE(rc)) + { + if (rc != VERR_ACCESS_DENIED && rc != VERR_WRITE_PROTECT) + cmn_err(CE_WARN, "sfprov_create: VbglR0SfCreate failed! path=%s rc=%d\n", path, rc); + return (sfprov_vbox2errno(rc)); + } + if (parms.Handle == SHFL_HANDLE_NIL) { + if (parms.Result == SHFL_FILE_EXISTS) + return (EEXIST); + return (ENOENT); + } + newfp = kmem_alloc(sizeof(sfp_file_t), KM_SLEEP); + newfp->handle = parms.Handle; + newfp->map = mnt->map; + *fp = newfp; + sfprov_stat_from_info(mnt, stat, &parms.Info); + return (0); +} + +int +sfprov_diropen(sfp_mount_t *mnt, char *path, sfp_file_t **fp) +{ + int rc; + SHFLCREATEPARMS parms; + SHFLSTRING *str; + int size; + sfp_file_t *newfp; + + bzero(&parms, sizeof(parms)); + str = sfprov_string(path, &size); + parms.Handle = SHFL_HANDLE_NIL; + parms.Info.cbObject = 0; + parms.CreateFlags = SHFL_CF_DIRECTORY + | SHFL_CF_ACCESS_READ + | SHFL_CF_ACT_OPEN_IF_EXISTS + | SHFL_CF_ACT_FAIL_IF_NEW; + + /* + * Open the host directory. + */ + rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms); + + /* + * Our VBoxFS interface here isn't very clear regarding failure and informational status. + * Check the file-handle as well as the return code to make sure the operation succeeded. + */ + if (RT_FAILURE(rc)) { + kmem_free(str, size); + return (sfprov_vbox2errno(rc)); + } + + if (parms.Handle == SHFL_HANDLE_NIL) { + kmem_free(str, size); + return (ENOENT); + } + + newfp = kmem_alloc(sizeof(sfp_file_t), KM_SLEEP); + newfp->handle = parms.Handle; + newfp->map = mnt->map; + *fp = newfp; + return (0); +} + +int +sfprov_open(sfp_mount_t *mnt, char *path, sfp_file_t **fp, int flag) +{ + int rc; + SHFLCREATEPARMS parms; + SHFLSTRING *str; + int size; + sfp_file_t *newfp; + + bzero(&parms, sizeof(parms)); + str = sfprov_string(path, &size); + parms.Handle = SHFL_HANDLE_NIL; + parms.Info.cbObject = 0; + + /* + * Translate file modes. + */ + if (flag & FCREAT) { + parms.CreateFlags |= SHFL_CF_ACT_CREATE_IF_NEW; + if (!(flag & FTRUNC)) + parms.CreateFlags |= SHFL_CF_ACT_OPEN_IF_EXISTS; + } + else + parms.CreateFlags |= SHFL_CF_ACT_FAIL_IF_NEW; + + if (flag & FTRUNC) + parms.CreateFlags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS | SHFL_CF_ACCESS_WRITE; + if (flag & FWRITE) + parms.CreateFlags |= SHFL_CF_ACCESS_WRITE; + if (flag & FREAD) + parms.CreateFlags |= SHFL_CF_ACCESS_READ; + if (flag & FAPPEND) + parms.CreateFlags |= SHFL_CF_ACCESS_APPEND; + + /* + * Open/create the host file. + */ + rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms); + + /* + * Our VBoxFS interface here isn't very clear regarding failure and informational status. + * Check the file-handle as well as the return code to make sure the operation succeeded. + */ + if (RT_FAILURE(rc)) { + kmem_free(str, size); + return (sfprov_vbox2errno(rc)); + } + + if (parms.Handle == SHFL_HANDLE_NIL) { + kmem_free(str, size); + return (ENOENT); + } + + newfp = kmem_alloc(sizeof(sfp_file_t), KM_SLEEP); + newfp->handle = parms.Handle; + newfp->map = mnt->map; + *fp = newfp; + return (0); +} + +int +sfprov_close(sfp_file_t *fp) +{ + int rc; + + rc = VbglR0SfClose(&vbox_client, &fp->map, fp->handle); + kmem_free(fp, sizeof(sfp_file_t)); + return (0); +} + +int +sfprov_read(sfp_file_t *fp, char *buffer, uint64_t offset, uint32_t *numbytes) +{ + int rc; + + rc = VbglR0SfRead(&vbox_client, &fp->map, fp->handle, offset, + numbytes, (uint8_t *)buffer, 0 /*fLocked*/); + if (RT_FAILURE(rc)) + return (EINVAL); + return (0); +} + +int +sfprov_write(sfp_file_t *fp, char *buffer, uint64_t offset, uint32_t *numbytes) +{ + int rc; + + rc = VbglR0SfWrite(&vbox_client, &fp->map, fp->handle, offset, + numbytes, (uint8_t *)buffer, 0 /*fLocked*/); + if (RT_FAILURE(rc)) + return (EINVAL); + return (0); +} + +int +sfprov_fsync(sfp_file_t *fp) +{ + int rc; + + rc = VbglR0SfFlush(&vbox_client, &fp->map, fp->handle); + if (RT_FAILURE(rc)) + return (EIO); + return (0); +} + + +static int +sfprov_getinfo(sfp_mount_t *mnt, char *path, PSHFLFSOBJINFO info) +{ + int rc; + SHFLCREATEPARMS parms; + SHFLSTRING *str; + int size; + + str = sfprov_string(path, &size); + parms.Handle = 0; + parms.Info.cbObject = 0; + parms.CreateFlags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW; + rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms); + kmem_free(str, size); + + if (RT_FAILURE(rc)) + return (EINVAL); + if (parms.Result != SHFL_FILE_EXISTS) + return (ENOENT); + *info = parms.Info; + return (0); +} + +/* + * get information about a file (or directory) + */ +int +sfprov_get_mode(sfp_mount_t *mnt, char *path, mode_t *mode) +{ + int rc; + SHFLFSOBJINFO info; + + rc = sfprov_getinfo(mnt, path, &info); + if (rc) + return (rc); + sfprov_mode_from_fmode(mnt, mode, info.Attr.fMode); + return (0); +} + +int +sfprov_get_size(sfp_mount_t *mnt, char *path, uint64_t *size) +{ + int rc; + SHFLFSOBJINFO info; + + rc = sfprov_getinfo(mnt, path, &info); + if (rc) + return (rc); + *size = info.cbObject; + return (0); +} + + +int +sfprov_get_atime(sfp_mount_t *mnt, char *path, timestruc_t *time) +{ + int rc; + SHFLFSOBJINFO info; + + rc = sfprov_getinfo(mnt, path, &info); + if (rc) + return (rc); + sfprov_ftime_from_timespec(time, &info.AccessTime); + return (0); +} + +int +sfprov_get_mtime(sfp_mount_t *mnt, char *path, timestruc_t *time) +{ + int rc; + SHFLFSOBJINFO info; + + rc = sfprov_getinfo(mnt, path, &info); + if (rc) + return (rc); + sfprov_ftime_from_timespec(time, &info.ModificationTime); + return (0); +} + +int +sfprov_get_ctime(sfp_mount_t *mnt, char *path, timestruc_t *time) +{ + int rc; + SHFLFSOBJINFO info; + + rc = sfprov_getinfo(mnt, path, &info); + if (rc) + return (rc); + sfprov_ftime_from_timespec(time, &info.ChangeTime); + return (0); +} + +int +sfprov_get_attr(sfp_mount_t *mnt, char *path, sffs_stat_t *attr) +{ + int rc; + SHFLFSOBJINFO info; + + rc = sfprov_getinfo(mnt, path, &info); + if (rc) + return (rc); + sfprov_stat_from_info(mnt, attr, &info); + return (0); +} + +static void +sfprov_timespec_from_ftime(RTTIMESPEC *ts, timestruc_t time) +{ + uint64_t nanosec = UINT64_C(1000000000) * time.tv_sec + time.tv_nsec; + RTTimeSpecSetNano(ts, nanosec); +} + +int +sfprov_set_attr( + sfp_mount_t *mnt, + char *path, + uint_t mask, + mode_t mode, + timestruc_t atime, + timestruc_t mtime, + timestruc_t ctime) +{ + int rc, err; + SHFLCREATEPARMS parms; + SHFLSTRING *str; + SHFLFSOBJINFO info; + uint32_t bytes; + int str_size; + + str = sfprov_string(path, &str_size); + parms.Handle = 0; + parms.Info.cbObject = 0; + parms.CreateFlags = SHFL_CF_ACT_OPEN_IF_EXISTS + | SHFL_CF_ACT_FAIL_IF_NEW + | SHFL_CF_ACCESS_ATTR_WRITE; + + rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms); + + if (RT_FAILURE(rc)) { + cmn_err(CE_WARN, "sfprov_set_attr: VbglR0SfCreate(%s) failed rc=%d\n", + path, rc); + err = EINVAL; + goto fail2; + } + if (parms.Result != SHFL_FILE_EXISTS) { + err = ENOENT; + goto fail1; + } + + RT_ZERO(info); + if (mask & AT_MODE) + sfprov_fmode_from_mode(&info.Attr.fMode, mode); + if (mask & AT_ATIME) + sfprov_timespec_from_ftime(&info.AccessTime, atime); + if (mask & AT_MTIME) + sfprov_timespec_from_ftime(&info.ModificationTime, mtime); + if (mask & AT_CTIME) + sfprov_timespec_from_ftime(&info.ChangeTime, ctime); + + bytes = sizeof(info); + rc = VbglR0SfFsInfo(&vbox_client, &mnt->map, parms.Handle, SHFL_INFO_SET | SHFL_INFO_FILE, + &bytes, (SHFLDIRINFO *)&info); + if (RT_FAILURE(rc)) { + if (rc != VERR_ACCESS_DENIED && rc != VERR_WRITE_PROTECT) + { + cmn_err(CE_WARN, "sfprov_set_attr: VbglR0SfFsInfo(%s, FILE) failed rc=%d\n", + path, rc); + } + err = sfprov_vbox2errno(rc); + goto fail1; + } + + err = 0; + +fail1: + rc = VbglR0SfClose(&vbox_client, &mnt->map, parms.Handle); + if (RT_FAILURE(rc)) { + cmn_err(CE_WARN, "sfprov_set_attr: VbglR0SfClose(%s) failed rc=%d\n", + path, rc); + } +fail2: + kmem_free(str, str_size); + return err; +} + +int +sfprov_set_size(sfp_mount_t *mnt, char *path, uint64_t size) +{ + int rc, err; + SHFLCREATEPARMS parms; + SHFLSTRING *str; + SHFLFSOBJINFO info; + uint32_t bytes; + int str_size; + + str = sfprov_string(path, &str_size); + parms.Handle = 0; + parms.Info.cbObject = 0; + parms.CreateFlags = SHFL_CF_ACT_OPEN_IF_EXISTS + | SHFL_CF_ACT_FAIL_IF_NEW + | SHFL_CF_ACCESS_WRITE; + + rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms); + + if (RT_FAILURE(rc)) { + cmn_err(CE_WARN, "sfprov_set_size: VbglR0SfCreate(%s) failed rc=%d\n", + path, rc); + err = EINVAL; + goto fail2; + } + if (parms.Result != SHFL_FILE_EXISTS) { + err = ENOENT; + goto fail1; + } + + RT_ZERO(info); + info.cbObject = size; + bytes = sizeof(info); + rc = VbglR0SfFsInfo(&vbox_client, &mnt->map, parms.Handle, SHFL_INFO_SET | SHFL_INFO_SIZE, + &bytes, (SHFLDIRINFO *)&info); + if (RT_FAILURE(rc)) { + cmn_err(CE_WARN, "sfprov_set_size: VbglR0SfFsInfo(%s, SIZE) failed rc=%d\n", + path, rc); + err = sfprov_vbox2errno(rc); + goto fail1; + } + + err = 0; + +fail1: + rc = VbglR0SfClose(&vbox_client, &mnt->map, parms.Handle); + if (RT_FAILURE(rc)) { + cmn_err(CE_WARN, "sfprov_set_size: VbglR0SfClose(%s) failed rc=%d\n", + path, rc); + } +fail2: + kmem_free(str, str_size); + return err; +} + +/* + * Directory operations + */ +int +sfprov_mkdir( + sfp_mount_t *mnt, + char *path, + mode_t mode, + sfp_file_t **fp, + sffs_stat_t *stat) +{ + int rc; + SHFLCREATEPARMS parms; + SHFLSTRING *str; + int size; + sfp_file_t *newfp; + + str = sfprov_string(path, &size); + parms.Handle = SHFL_HANDLE_NIL; + parms.Info.cbObject = 0; + sfprov_fmode_from_mode(&parms.Info.Attr.fMode, mode); + parms.CreateFlags = SHFL_CF_DIRECTORY | SHFL_CF_ACT_CREATE_IF_NEW | + SHFL_CF_ACT_FAIL_IF_EXISTS | SHFL_CF_ACCESS_READ; + rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms); + kmem_free(str, size); + + if (RT_FAILURE(rc)) + return (sfprov_vbox2errno(rc)); + if (parms.Handle == SHFL_HANDLE_NIL) { + if (parms.Result == SHFL_FILE_EXISTS) + return (EEXIST); + return (ENOENT); + } + newfp = kmem_alloc(sizeof(sfp_file_t), KM_SLEEP); + newfp->handle = parms.Handle; + newfp->map = mnt->map; + *fp = newfp; + sfprov_stat_from_info(mnt, stat, &parms.Info); + return (0); +} + +int +sfprov_set_show_symlinks(void) +{ + int rc; + + rc = VbglR0SfSetSymlinks(&vbox_client); + if (RT_FAILURE(rc)) + return (sfprov_vbox2errno(rc)); + + return (0); +} + +int +sfprov_remove(sfp_mount_t *mnt, char *path, uint_t is_link) +{ + int rc; + SHFLSTRING *str; + int size; + + str = sfprov_string(path, &size); + rc = VbglR0SfRemove(&vbox_client, &mnt->map, str, + SHFL_REMOVE_FILE | (is_link ? SHFL_REMOVE_SYMLINK : 0)); + kmem_free(str, size); + if (RT_FAILURE(rc)) + return (sfprov_vbox2errno(rc)); + return (0); +} + +int +sfprov_readlink( + sfp_mount_t *mnt, + char *path, + char *target, + size_t tgt_size) +{ + int rc; + SHFLSTRING *str; + int size; + + str = sfprov_string(path, &size); + + rc = VbglR0SfReadLink(&vbox_client, &mnt->map, str, (uint32_t) tgt_size, + target); + if (RT_FAILURE(rc)) + rc = sfprov_vbox2errno(rc); + + kmem_free(str, size); + return (rc); +} + +int +sfprov_symlink( + sfp_mount_t *mnt, + char *linkname, + char *target, + sffs_stat_t *stat) +{ + int rc; + SHFLSTRING *lnk, *tgt; + int lnk_size, tgt_size; + SHFLFSOBJINFO info; + + lnk = sfprov_string(linkname, &lnk_size); + tgt = sfprov_string(target, &tgt_size); + + rc = VbglR0SfSymlink(&vbox_client, &mnt->map, lnk, tgt, &info); + if (RT_FAILURE(rc)) { + rc = sfprov_vbox2errno(rc); + goto done; + } + + if (stat != NULL) + sfprov_stat_from_info(mnt, stat, &info); + +done: + kmem_free(lnk, lnk_size); + kmem_free(tgt, tgt_size); + + return (rc); +} + +int +sfprov_rmdir(sfp_mount_t *mnt, char *path) +{ + int rc; + SHFLSTRING *str; + int size; + + str = sfprov_string(path, &size); + rc = VbglR0SfRemove(&vbox_client, &mnt->map, str, SHFL_REMOVE_DIR); + kmem_free(str, size); + if (RT_FAILURE(rc)) + return (sfprov_vbox2errno(rc)); + return (0); +} + +int +sfprov_rename(sfp_mount_t *mnt, char *from, char *to, uint_t is_dir) +{ + int rc; + SHFLSTRING *old, *new; + int old_size, new_size; + + old = sfprov_string(from, &old_size); + new = sfprov_string(to, &new_size); + rc = VbglR0SfRename(&vbox_client, &mnt->map, old, new, + (is_dir ? SHFL_RENAME_DIR : SHFL_RENAME_FILE) | SHFL_RENAME_REPLACE_IF_EXISTS); + kmem_free(old, old_size); + kmem_free(new, new_size); + if (RT_FAILURE(rc)) + return (sfprov_vbox2errno(rc)); + return (0); +} + + +/* + * Read all filenames in a directory. + * + * - success - all entries read and returned + * - ENOENT - Couldn't open the directory for reading + * - EINVAL - Internal error of some kind + * + * On successful return, *dirents points to a list of sffs_dirents_t; + * for each dirent, all fields except the d_ino will be set appropriately. + * The caller is responsible for freeing the dirents buffer. + */ +int +sfprov_readdir( + sfp_mount_t *mnt, + char *path, + sffs_dirents_t **dirents, + int flag) +{ + int error; + char *cp; + int len; + SHFLSTRING *mask_str = NULL; /* must be path with "/ *" appended */ + int mask_size; + sfp_file_t *fp; + uint32_t infobuff_alloc = 16384; + SHFLDIRINFO *infobuff = NULL, *info; + uint32_t numbytes; + uint32_t nents; + uint32_t size; + off_t offset; + sffs_dirents_t *cur_buf; + struct sffs_dirent *dirent; + unsigned short reclen; + unsigned short entlen; + + *dirents = NULL; + + error = sfprov_diropen(mnt, path, &fp); + if (error != 0) + return (ENOENT); + + /* + * Allocate the first dirents buffers. + */ + *dirents = kmem_alloc(SFFS_DIRENTS_SIZE, KM_SLEEP); + if (*dirents == NULL) { + error = (ENOSPC); + goto done; + } + cur_buf = *dirents; + cur_buf->sf_next = NULL; + cur_buf->sf_len = 0; + + /* + * Create mask that VBox expects. This needs to be the directory path, + * plus a "*" wildcard to get all files. + */ + len = strlen(path) + 3; + cp = kmem_alloc(len, KM_SLEEP); + if (cp == NULL) { + error = (ENOSPC); + goto done; + } + strcpy(cp, path); + strcat(cp, "/*"); + mask_str = sfprov_string(cp, &mask_size); + kmem_free(cp, len); + + /* + * Now loop using VbglR0SfDirInfo + */ + infobuff = kmem_alloc(infobuff_alloc, KM_SLEEP); + if (infobuff == NULL) { + error = (ENOSPC); + goto done; + } + + offset = 0; + for (;;) { + numbytes = infobuff_alloc; + error = VbglR0SfDirInfo(&vbox_client, &fp->map, fp->handle, + mask_str, 0, 0, &numbytes, infobuff, &nents); + switch (error) { + + case VINF_SUCCESS: + /* fallthrough */ + case VERR_NO_MORE_FILES: + break; + + case VERR_NO_TRANSLATION: + /* XXX ??? */ + break; + + default: + error = sfprov_vbox2errno(error); + goto done; + } + + /* + * Create the dirent_t's and save the stats for each name + */ + for (info = infobuff; (char *) info < (char *) infobuff + numbytes; nents--) { + /* expand buffers if we need more space */ + reclen = DIRENT64_RECLEN(strlen(info->name.String.utf8)); + entlen = sizeof(sffs_stat_t) + reclen; + if (SFFS_DIRENTS_OFF + cur_buf->sf_len + entlen > SFFS_DIRENTS_SIZE) { + cur_buf->sf_next = kmem_alloc(SFFS_DIRENTS_SIZE, KM_SLEEP); + if (cur_buf->sf_next == NULL) { + error = ENOSPC; + goto done; + } + cur_buf = cur_buf->sf_next; + cur_buf->sf_next = NULL; + cur_buf->sf_len = 0; + } + + /* create the dirent with the name, offset, and len */ + dirent = (struct sffs_dirent *) + (((char *) &cur_buf->sf_entries[0]) + cur_buf->sf_len); + strncpy(&dirent->sf_entry.d_name[0], info->name.String.utf8, DIRENT64_NAMELEN(reclen)); + dirent->sf_entry.d_reclen = reclen; + offset += entlen; + dirent->sf_entry.d_off = offset; + + /* save the stats */ + sfprov_stat_from_info(mnt, &dirent->sf_stat, &info->Info); + + /* next info */ + cur_buf->sf_len += entlen; + size = offsetof (SHFLDIRINFO, name.String) + info->name.u16Size; + info = (SHFLDIRINFO *) ((uintptr_t) info + size); + } + ASSERT(nents == 0); + ASSERT((char *) info == (char *) infobuff + numbytes); + + if (error == VERR_NO_MORE_FILES) + break; + } + error = 0; + +done: + if (error != 0) { + while (*dirents) { + cur_buf = (*dirents)->sf_next; + kmem_free(*dirents, SFFS_DIRENTS_SIZE); + *dirents = cur_buf; + } + } + if (infobuff != NULL) + kmem_free(infobuff, infobuff_alloc); + if (mask_str != NULL) + kmem_free(mask_str, mask_size); + sfprov_close(fp); + return (error); +} diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.h b/src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.h new file mode 100644 index 00000000..b7bada48 --- /dev/null +++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.h @@ -0,0 +1,200 @@ +/* $Id: vboxfs_prov.h $ */ +/** @file + * VirtualBox File System for Solaris Guests, provider header. + * Portions contributed by: Ronald. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_prov_h +#define GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_prov_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/VBoxGuestLibSharedFolders.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * These are the provider interfaces used by sffs to access the underlying + * shared file system. + */ +#define SFPROV_VERSION 1 + +/* + * Initialization and termination. + * sfprov_connect() is called once before any other interfaces and returns + * a handle used in further calls. The argument should be SFPROV_VERSION + * from above. On failure it returns a NULL pointer. + * + * sfprov_disconnect() must only be called after all sf file systems have been + * unmounted. + */ +typedef struct sfp_connection sfp_connection_t; + +extern sfp_connection_t *sfprov_connect(int); +extern void sfprov_disconnect(sfp_connection_t *); + + +/* + * Mount / Unmount a shared folder. + * + * sfprov_mount() takes as input the connection pointer and the name of + * the shared folder. On success, it returns zero and supplies an + * sfp_mount_t handle. On failure it returns any relevant errno value. + * + * sfprov_unmount() unmounts the mounted file system. It returns 0 on + * success and any relevant errno on failure. + * + * spf_mount_t is the representation of an active mount point. + */ +typedef struct spf_mount_t { + VBGLSFMAP map; /**< guest<->host mapping */ + uid_t sf_uid; /**< owner of the mount point */ + gid_t sf_gid; /**< group of the mount point */ + mode_t sf_dmode; /**< mode of all directories if != ~0U */ + mode_t sf_fmode; /**< mode of all files if != ~0U */ + mode_t sf_dmask; /**< mask of all directories */ + mode_t sf_fmask; /**< mask of all files */ +} sfp_mount_t; + +extern int sfprov_mount(sfp_connection_t *, char *, sfp_mount_t **); +extern int sfprov_unmount(sfp_mount_t *); + +/* + * query information about a mounted file system + */ +typedef struct sffs_fsinfo { + uint64_t blksize; + uint64_t blksused; + uint64_t blksavail; + uint32_t maxnamesize; + uint32_t readonly; +} sffs_fsinfo_t; + +extern int sfprov_get_fsinfo(sfp_mount_t *, sffs_fsinfo_t *); + +/* + * File operations: open/close/read/write/etc. + * + * open/create can return any relevant errno, however ENOENT + * generally means that the host file didn't exist. + */ +typedef struct sffs_stat { + mode_t sf_mode; + off_t sf_size; + off_t sf_alloc; + timestruc_t sf_atime; + timestruc_t sf_mtime; + timestruc_t sf_ctime; +} sffs_stat_t; + +typedef struct sfp_file sfp_file_t; + +extern int sfprov_create(sfp_mount_t *, char *path, mode_t mode, + sfp_file_t **fp, sffs_stat_t *stat); +extern int sfprov_diropen(sfp_mount_t *mnt, char *path, sfp_file_t **fp); +extern int sfprov_open(sfp_mount_t *, char *path, sfp_file_t **fp, int flag); +extern int sfprov_close(sfp_file_t *fp); +extern int sfprov_read(sfp_file_t *, char * buffer, uint64_t offset, + uint32_t *numbytes); +extern int sfprov_write(sfp_file_t *, char * buffer, uint64_t offset, + uint32_t *numbytes); +extern int sfprov_fsync(sfp_file_t *fp); + + +/* + * get/set information about a file (or directory) using pathname + */ +extern int sfprov_get_mode(sfp_mount_t *, char *, mode_t *); +extern int sfprov_get_size(sfp_mount_t *, char *, uint64_t *); +extern int sfprov_get_atime(sfp_mount_t *, char *, timestruc_t *); +extern int sfprov_get_mtime(sfp_mount_t *, char *, timestruc_t *); +extern int sfprov_get_ctime(sfp_mount_t *, char *, timestruc_t *); +extern int sfprov_get_attr(sfp_mount_t *, char *, sffs_stat_t *); +extern int sfprov_set_attr(sfp_mount_t *, char *, uint_t, mode_t, + timestruc_t, timestruc_t, timestruc_t); +extern int sfprov_set_size(sfp_mount_t *, char *, uint64_t); + + +/* + * File/Directory operations + */ +extern int sfprov_remove(sfp_mount_t *, char *path, uint_t is_link); +extern int sfprov_mkdir(sfp_mount_t *, char *path, mode_t mode, + sfp_file_t **fp, sffs_stat_t *stat); +extern int sfprov_rmdir(sfp_mount_t *, char *path); +extern int sfprov_rename(sfp_mount_t *, char *from, char *to, uint_t is_dir); + + +/* + * Symbolic link operations + */ +extern int sfprov_set_show_symlinks(void); +extern int sfprov_readlink(sfp_mount_t *, char *path, char *target, + size_t tgt_size); +extern int sfprov_symlink(sfp_mount_t *, char *linkname, char *target, + sffs_stat_t *stat); + + +/* + * Read directory entries. + */ +/* + * a singly linked list of buffers, each containing an array of stat's+dirent's. + * sf_len is length of the sf_entries array, in bytes. + */ +typedef struct sffs_dirents { + struct sffs_dirents *sf_next; + len_t sf_len; + struct sffs_dirent { + sffs_stat_t sf_stat; + dirent64_t sf_entry; /* this is variable length */ + } sf_entries[1]; +} sffs_dirents_t; + +#define SFFS_DIRENTS_SIZE 8192 +#define SFFS_DIRENTS_OFF (offsetof(sffs_dirents_t, sf_entries[0])) + +extern int sfprov_readdir(sfp_mount_t *mnt, char *path, + sffs_dirents_t **dirents, int flag); + +#ifdef __cplusplus +} +#endif + +#endif /* !GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_prov_h */ + diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.c b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.c new file mode 100644 index 00000000..2f2d9d17 --- /dev/null +++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.c @@ -0,0 +1,641 @@ +/* $Id: vboxfs_vfs.c $ */ +/** @file + * VirtualBox File System for Solaris Guests, VFS implementation. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include <VBox/log.h> +#include <VBox/version.h> +#include <iprt/dbg.h> + +#include <sys/types.h> +#include <sys/mntent.h> +#include <sys/param.h> +#include <sys/modctl.h> +#include <sys/mount.h> +#include <sys/policy.h> +#include <sys/atomic.h> +#include <sys/sysmacros.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/vfs.h> +#if !defined(VBOX_VFS_SOLARIS_10U6) +# include <sys/vfs_opreg.h> +#endif +#include <sys/pathname.h> +#include <sys/cmn_err.h> +#include <sys/vmsystm.h> +#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */ + +#include "vboxfs_prov.h" +#include "vboxfs_vnode.h" +#include "vboxfs_vfs.h" +#include "vboxfs.h" + + +#define VBOXSOLQUOTE2(x) #x +#define VBOXSOLQUOTE(x) VBOXSOLQUOTE2(x) +/** The module name. */ +#define DEVICE_NAME "vboxfs" +/** The module description as seen in 'modinfo'. */ +#define DEVICE_DESC "VirtualBox ShrdFS" + + +/* + * Shared Folders filesystem implementation of the Solaris VFS interfaces. + * Much of this is cookie cutter code for Solaris filesystem implementation. + */ + +/* forward declarations */ +static int sffs_init(int fstype, char *name); +static int sffs_mount(vfs_t *, vnode_t *, struct mounta *, cred_t *); +static int sffs_unmount(vfs_t *vfsp, int flag, cred_t *cr); +static int sffs_root(vfs_t *vfsp, vnode_t **vpp); +static int sffs_statvfs(vfs_t *vfsp, statvfs64_t *sbp); + +static mntopt_t sffs_options[] = { + /* Option Cancels Opt Arg Flags Data */ + {"uid", NULL, NULL, MO_HASVALUE, NULL}, + {"gid", NULL, NULL, MO_HASVALUE, NULL}, + {"dmode", NULL, NULL, MO_HASVALUE, NULL}, + {"fmode", NULL, NULL, MO_HASVALUE, NULL}, + {"dmask", NULL, NULL, MO_HASVALUE, NULL}, + {"fmask", NULL, NULL, MO_HASVALUE, NULL}, + {"stat_ttl", NULL, NULL, MO_HASVALUE, NULL}, + {"fsync", NULL, NULL, 0, NULL}, + {"tag", NULL, NULL, MO_HASVALUE, NULL} +}; + +static mntopts_t sffs_options_table = { + sizeof (sffs_options) / sizeof (mntopt_t), + sffs_options +}; + +static vfsdef_t sffs_vfsdef = { + VFSDEF_VERSION, + DEVICE_NAME, + sffs_init, + VSW_HASPROTO, + &sffs_options_table +}; + +static int sffs_fstype; +static int sffs_major; /* major number for device */ + +kmutex_t sffs_minor_lock; +int sffs_minor; /* minor number for device */ + +/** Whether to use the old-style map_addr()/choose_addr() routines. */ +bool g_fVBoxVFS_SolOldAddrMap; +/** The map_addr()/choose_addr() hooks callout table structure. */ +VBoxVFS_SolAddrMap g_VBoxVFS_SolAddrMap; + +/* + * Module linkage information + */ +static struct modlfs modlfs = { + &mod_fsops, + DEVICE_DESC " " VBOX_VERSION_STRING "r" VBOXSOLQUOTE(VBOX_SVN_REV), + &sffs_vfsdef +}; + +static struct modlinkage modlinkage = { + MODREV_1, &modlfs, NULL +}; + +static sfp_connection_t *sfprov = NULL; + +int +_init() +{ + RTDBGKRNLINFO hKrnlDbgInfo; + int rc = RTR0DbgKrnlInfoOpen(&hKrnlDbgInfo, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + { + rc = RTR0DbgKrnlInfoQuerySymbol(hKrnlDbgInfo, NULL /* pszModule */, "plat_map_align_amount", NULL /* ppvSymbol */); + if (RT_SUCCESS(rc)) + { +#if defined(VBOX_VFS_SOLARIS_10U6) + g_VBoxVFS_SolAddrMap.MapAddr.pfnSol_map_addr = (void *)map_addr; +#else + g_VBoxVFS_SolAddrMap.ChooseAddr.pfnSol_choose_addr = (void *)choose_addr; +#endif + } + else + { + g_fVBoxVFS_SolOldAddrMap = true; +#if defined(VBOX_VFS_SOLARIS_10U6) + g_VBoxVFS_SolAddrMap.MapAddr.pfnSol_map_addr_old = (void *)map_addr; +#else + g_VBoxVFS_SolAddrMap.ChooseAddr.pfnSol_choose_addr_old = (void *)choose_addr; +#endif + } + + RTR0DbgKrnlInfoRelease(hKrnlDbgInfo); + } + else + { + cmn_err(CE_NOTE, "RTR0DbgKrnlInfoOpen failed. rc=%d\n", rc); + return rc; + } + + return (mod_install(&modlinkage)); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + + +int +_fini() +{ + int error; + + error = mod_remove(&modlinkage); + if (error) + return (error); + + /* + * Tear down the operations vectors + */ + sffs_vnode_fini(); + (void) vfs_freevfsops_by_type(sffs_fstype); + + /* + * close connection to the provider + */ + sfprov_disconnect(sfprov); + return (0); +} + + +static int +sffs_init(int fstype, char *name) +{ +#if defined(VBOX_VFS_SOLARIS_10U6) + static const fs_operation_def_t sffs_vfsops_template[] = { + VFSNAME_MOUNT, sffs_mount, + VFSNAME_UNMOUNT, sffs_unmount, + VFSNAME_ROOT, sffs_root, + VFSNAME_STATVFS, sffs_statvfs, + NULL, NULL + }; +#else + static const fs_operation_def_t sffs_vfsops_template[] = { + VFSNAME_MOUNT, { .vfs_mount = sffs_mount }, + VFSNAME_UNMOUNT, { .vfs_unmount = sffs_unmount }, + VFSNAME_ROOT, { .vfs_root = sffs_root }, + VFSNAME_STATVFS, { .vfs_statvfs = sffs_statvfs }, + NULL, NULL + }; +#endif + int error; + + ASSERT(fstype != 0); + sffs_fstype = fstype; + LogFlowFunc(("sffs_init() name=%s\n", name)); + + /* + * This may seem a silly way to do things for now. But the code + * is structured to easily allow it to be used on other hypervisors + * which would have a different implementation of the provider. + * Hopefully that'll never happen. :) + */ + sfprov = sfprov_connect(SFPROV_VERSION); + if (sfprov == NULL) { + cmn_err(CE_WARN, "sffs_init: couldn't init sffs provider"); + return (ENODEV); + } + + error = sfprov_set_show_symlinks(); + if (error != 0) { + cmn_err(CE_WARN, "sffs_init: host unable to show symlinks, " + "rc=%d\n", error); + } + + error = vfs_setfsops(fstype, sffs_vfsops_template, NULL); + if (error != 0) { + cmn_err(CE_WARN, "sffs_init: bad vfs ops template"); + return (error); + } + + error = sffs_vnode_init(); + if (error != 0) { + (void) vfs_freevfsops_by_type(fstype); + cmn_err(CE_WARN, "sffs_init: bad vnode ops template"); + return (error); + } + + if ((sffs_major = getudev()) == (major_t)-1) { + cmn_err(CE_WARN, "sffs_init: Can't get unique device number."); + sffs_major = 0; + } + mutex_init(&sffs_minor_lock, NULL, MUTEX_DEFAULT, NULL); + return (0); +} + +/* + * wrapper for pn_get + */ +static int +sf_pn_get(char *rawpath, struct mounta *uap, char **outpath) +{ + pathname_t path; + int error; + + error = pn_get(rawpath, (uap->flags & MS_SYSSPACE) ? UIO_SYSSPACE : + UIO_USERSPACE, &path); + if (error) { + LogFlowFunc(("pn_get(%s) failed\n", rawpath)); + return (error); + } + *outpath = kmem_alloc(path.pn_pathlen + 1, KM_SLEEP); + strcpy(*outpath, path.pn_path); + pn_free(&path); + return (0); +} + +#ifdef DEBUG_ramshankar +static void +sffs_print(sffs_data_t *sffs) +{ + cmn_err(CE_NOTE, "sffs_data_t at 0x%p\n", sffs); + cmn_err(CE_NOTE, " vfs_t *sf_vfsp = 0x%p\n", sffs->sf_vfsp); + cmn_err(CE_NOTE, " vnode_t *sf_rootnode = 0x%p\n", sffs->sf_rootnode); + cmn_err(CE_NOTE, " uid_t sf_uid = 0x%lu\n", (ulong_t)sffs->sf_handle->sf_uid); + cmn_err(CE_NOTE, " gid_t sf_gid = 0x%lu\n", (ulong_t)sffs->sf_handle->sf_gid); + cmn_err(CE_NOTE, " mode_t sf_dmode = 0x%lu\n", (ulong_t)sffs->sf_handle->sf_dmode); + cmn_err(CE_NOTE, " mode_t sf_fmode = 0x%lu\n", (ulong_t)sffs->sf_handle->sf_fmode); + cmn_err(CE_NOTE, " mode_t sf_dmask = 0x%lu\n", (ulong_t)sffs->sf_handle->sf_dmask); + cmn_err(CE_NOTE, " mode_t sf_fmask = 0x%lu\n", (ulong_t)sffs->sf_handle->sf_fmask); + cmn_err(CE_NOTE, " char *sf_share_name = %s\n", sffs->sf_share_name); + cmn_err(CE_NOTE, " char *sf_mntpath = %s\n", sffs->sf_mntpath); + cmn_err(CE_NOTE, " sfp_mount_t *sf_handle = 0x%p\n", sffs->sf_handle); +} +#endif + +static int +sffs_mount(vfs_t *vfsp, vnode_t *mvp, struct mounta *uap, cred_t *cr) +{ + sffs_data_t *sffs; + char *mount_point = NULL; + char *share_name = NULL; + int error; + dev_t dev; + uid_t uid = 0; + gid_t gid = 0; + mode_t dmode = ~0U; + mode_t fmode = ~0U; + mode_t dmask = 0; + mode_t fmask = 0; + int stat_ttl = DEF_STAT_TTL_MS; + int fsync = 0; + char *optval; + long val; + char *path; + sfp_mount_t *handle; + sfnode_t *sfnode; + + /* + * check we have permission to do the mount + */ + LogFlowFunc(("sffs_mount() started\n")); + error = secpolicy_fs_mount(cr, mvp, vfsp); + if (error != 0) + return (error); + + /* + * Mount point must be a directory + */ + if (mvp->v_type != VDIR) + return (ENOTDIR); + + /* + * no support for remount (what is it?) + */ + if (uap->flags & MS_REMOUNT) + return (ENOTSUP); + + /* + * Ensure that nothing else is actively in/under the mount point + */ + mutex_enter(&mvp->v_lock); + if ((uap->flags & MS_OVERLAY) == 0 && + (mvp->v_count != 1 || (mvp->v_flag & VROOT))) { + mutex_exit(&mvp->v_lock); + return (EBUSY); + } + mutex_exit(&mvp->v_lock); + + /* + * check for read only has to be done early + */ + if (uap->flags & MS_RDONLY) { + vfsp->vfs_flag |= VFS_RDONLY; + vfs_setmntopt(vfsp, MNTOPT_RO, NULL, 0); + } + + /* + * UID to use for all files + */ + if (vfs_optionisset(vfsp, "uid", &optval) && + ddi_strtol(optval, NULL, 10, &val) == 0 && + (uid_t)val == val) + uid = val; + + /* + * GID to use for all files + */ + if (vfs_optionisset(vfsp, "gid", &optval) && + ddi_strtol(optval, NULL, 10, &val) == 0 && + (gid_t)val == val) + gid = val; + + /* + * dmode to use for all directories + */ + if (vfs_optionisset(vfsp, "dmode", &optval) && + ddi_strtol(optval, NULL, 8, &val) == 0 && + (mode_t)val == val) + dmode = val; + + /* + * fmode to use for all files + */ + if (vfs_optionisset(vfsp, "fmode", &optval) && + ddi_strtol(optval, NULL, 8, &val) == 0 && + (mode_t)val == val) + fmode = val; + + /* + * dmask to use for all directories + */ + if (vfs_optionisset(vfsp, "dmask", &optval) && + ddi_strtol(optval, NULL, 8, &val) == 0 && + (mode_t)val == val) + dmask = val; + + /* + * fmask to use for all files + */ + if (vfs_optionisset(vfsp, "fmask", &optval) && + ddi_strtol(optval, NULL, 8, &val) == 0 && + (mode_t)val == val) + fmask = val; + + /* + * umask to use for all directories & files + */ + if (vfs_optionisset(vfsp, "umask", &optval) && + ddi_strtol(optval, NULL, 8, &val) == 0 && + (mode_t)val == val) + dmask = fmask = val; + + /* + * ttl to use for stat caches + */ + if (vfs_optionisset(vfsp, "stat_ttl", &optval) && + ddi_strtol(optval, NULL, 10, &val) == 0 && + (int)val == val) + { + stat_ttl = val; + } + else + vfs_setmntopt(vfsp, "stat_ttl", VBOXSOLQUOTE(DEF_STAT_TTL_MS), 0); + + /* + * whether to honor fsync + */ + if (vfs_optionisset(vfsp, "fsync", &optval)) + fsync = 1; + + /* + * Any unknown options are an error + */ + if ((uap->flags & MS_DATA) && uap->datalen > 0) { + cmn_err(CE_WARN, "sffs: unknown mount options specified"); + return (EINVAL); + } + + /* + * get the mount point pathname + */ + error = sf_pn_get(uap->dir, uap, &mount_point); + if (error) + return (error); + + /* + * find what we are mounting + */ + error = sf_pn_get(uap->spec, uap, &share_name); + if (error) { + kmem_free(mount_point, strlen(mount_point) + 1); + return (error); + } + + /* + * Invoke Hypervisor mount interface before proceeding + */ + error = sfprov_mount(sfprov, share_name, &handle); + if (error) { + kmem_free(share_name, strlen(share_name) + 1); + kmem_free(mount_point, strlen(mount_point) + 1); + return (error); + } + + /* + * find an available minor device number for this mount + */ + mutex_enter(&sffs_minor_lock); + do { + sffs_minor = (sffs_minor + 1) & L_MAXMIN32; + dev = makedevice(sffs_major, sffs_minor); + } while (vfs_devismounted(dev)); + mutex_exit(&sffs_minor_lock); + + /* + * allocate and fill in the sffs structure + */ + sffs = kmem_alloc(sizeof (*sffs), KM_SLEEP); + sffs->sf_vfsp = vfsp; + sffs->sf_handle = handle; + sffs->sf_handle->sf_uid = uid; + sffs->sf_handle->sf_gid = gid; + sffs->sf_handle->sf_dmode = dmode; + sffs->sf_handle->sf_fmode = fmode; + sffs->sf_handle->sf_dmask = dmask; + sffs->sf_handle->sf_fmask = fmask; + sffs->sf_stat_ttl = stat_ttl; + sffs->sf_fsync = fsync; + sffs->sf_share_name = share_name; + sffs->sf_mntpath = mount_point; + sffs->sf_ino = 3; /* root mount point is always '3' */ + + /* + * fill in the vfs structure + */ + vfsp->vfs_data = (caddr_t)sffs; + vfsp->vfs_fstype = sffs_fstype; + vfsp->vfs_dev = dev; + vfsp->vfs_bsize = PAGESIZE; /* HERE JOE ??? */ + vfsp->vfs_flag |= VFS_NOTRUNC; /* HERE JOE ???? */ + vfs_make_fsid(&vfsp->vfs_fsid, dev, sffs_fstype); + + /* + * create the root vnode. + * XXX JOE What should the path be here? is "/" really right? + * other options? + */ + path = kmem_alloc(2, KM_SLEEP); + strcpy(path, "."); + mutex_enter(&sffs_lock); + sfnode = sfnode_make(sffs, path, VDIR, NULL, NULL, NULL, 0); + sffs->sf_rootnode = sfnode_get_vnode(sfnode); + sffs->sf_rootnode->v_flag |= VROOT; + sffs->sf_rootnode->v_vfsp = vfsp; + mutex_exit(&sffs_lock); + + LogFlowFunc(("sffs_mount() success sffs=0x%p\n", sffs)); +#ifdef DEBUG_ramshankar + sffs_print(sffs); +#endif + return (error); +} + +static int +sffs_unmount(vfs_t *vfsp, int flag, cred_t *cr) +{ + sffs_data_t *sffs = (sffs_data_t *)vfsp->vfs_data; + int error; + + /* + * generic security check + */ + LogFlowFunc(("sffs_unmount() of sffs=0x%p\n", sffs)); + if ((error = secpolicy_fs_unmount(cr, vfsp)) != 0) + return (error); + + /* + * forced unmount is not supported by this file system + * and thus, ENOTSUP, is being returned. + */ + if (flag & MS_FORCE) { + LogFlowFunc(("sffs_unmount(MS_FORCE) returns ENOSUP\n")); + return (ENOTSUP); + } + + /* + * Mark the file system unmounted. + */ + vfsp->vfs_flag |= VFS_UNMOUNTED; + + /* + * Make sure nothing is still in use. + */ + if (sffs_purge(sffs) != 0) { + vfsp->vfs_flag &= ~VFS_UNMOUNTED; + LogFlowFunc(("sffs_unmount() returns EBUSY\n")); + return (EBUSY); + } + + /* + * Invoke Hypervisor unmount interface before proceeding + */ + error = sfprov_unmount(sffs->sf_handle); + if (error != 0) { + /* TBD anything here? */ + } + + kmem_free(sffs->sf_share_name, strlen(sffs->sf_share_name) + 1); + kmem_free(sffs->sf_mntpath, strlen(sffs->sf_mntpath) + 1); + kmem_free(sffs, sizeof(*sffs)); + LogFlowFunc(("sffs_unmount() done\n")); + return (0); +} + +/* + * return the vnode for the root of the mounted file system + */ +static int +sffs_root(vfs_t *vfsp, vnode_t **vpp) +{ + sffs_data_t *sffs = (sffs_data_t *)vfsp->vfs_data; + vnode_t *vp = sffs->sf_rootnode; + + VN_HOLD(vp); + *vpp = vp; + return (0); +} + +/* + * get some stats.. fake up the rest + */ +static int +sffs_statvfs(vfs_t *vfsp, statvfs64_t *sbp) +{ + sffs_data_t *sffs = (sffs_data_t *)vfsp->vfs_data; + sffs_fsinfo_t fsinfo; + dev32_t d32; + int error; + + bzero(sbp, sizeof(*sbp)); + error = sfprov_get_fsinfo(sffs->sf_handle, &fsinfo); + if (error != 0) + return (error); + + sbp->f_bsize = fsinfo.blksize; + sbp->f_frsize = fsinfo.blksize; + + sbp->f_bfree = fsinfo.blksavail; + sbp->f_bavail = fsinfo.blksavail; + sbp->f_files = fsinfo.blksavail / 4; /* some kind of reasonable value */ + sbp->f_ffree = fsinfo.blksavail / 4; + sbp->f_favail = fsinfo.blksavail / 4; + + sbp->f_blocks = fsinfo.blksused + sbp->f_bavail; + + (void) cmpldev(&d32, vfsp->vfs_dev); + sbp->f_fsid = d32; + strcpy(&sbp->f_basetype[0], "sffs"); + sbp->f_flag |= ST_NOSUID; + + if (fsinfo.readonly) + sbp->f_flag |= ST_RDONLY; + + sbp->f_namemax = fsinfo.maxnamesize; + return (0); +} + diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.h b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.h new file mode 100644 index 00000000..6338d95d --- /dev/null +++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.h @@ -0,0 +1,90 @@ +/* $Id: vboxfs_vfs.h $ */ +/** @file + * VirtualBox File System for Solaris Guests, VFS header. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_vfs_h +#define GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_vfs_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Shared Folders filesystem per-mount data structure. + */ +typedef struct sffs_data { + vfs_t *sf_vfsp; /* filesystem's vfs struct */ + vnode_t *sf_rootnode; /* of vnode of the root directory */ + int sf_stat_ttl; /* ttl for stat caches (in ms) */ + int sf_fsync; /* whether to honor fsync or not */ + char *sf_share_name; + char *sf_mntpath; /* name of mount point */ + sfp_mount_t *sf_handle; + uint64_t sf_ino; /* per FS ino generator */ +} sffs_data_t; + +/* + * Workaround for older Solaris versions which called map_addr()/choose_addr()/ + * map_addr_proc() with an 'alignment' argument that was removed in Solaris + * 11.4. + */ +typedef struct VBoxVFS_SolAddrMap +{ + union + { + void *(*pfnSol_map_addr) (caddr_t *, size_t, offset_t, uint_t); + void *(*pfnSol_map_addr_old) (caddr_t *, size_t, offset_t, int, uint_t); + } MapAddr; + + union + { + int (*pfnSol_choose_addr) (struct as *, caddr_t *, size_t, offset_t, uint_t); + int (*pfnSol_choose_addr_old) (struct as *, caddr_t *, size_t, offset_t, int, uint_t); + } ChooseAddr; +} VBoxVFS_SolAddrMap; +typedef VBoxVFS_SolAddrMap *pVBoxVFS_SolAddrMap; + +extern bool g_fVBoxVFS_SolOldAddrMap; +extern VBoxVFS_SolAddrMap g_VBoxVFS_SolAddrMap; + +#ifdef __cplusplus +} +#endif + +#endif /* !GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_vfs_h */ + diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.c b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.c new file mode 100644 index 00000000..e639468c --- /dev/null +++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.c @@ -0,0 +1,2500 @@ +/* $Id: vboxfs_vnode.c $ */ +/** @file + * VirtualBox File System for Solaris Guests, vnode implementation. + * Portions contributed by: Ronald. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/* + * Shared Folder File System is used from Solaris when run as a guest operating + * system on VirtualBox, though is meant to be usable with any hypervisor that + * can provide similar functionality. The sffs code handles all the Solaris + * specific semantics and relies on a provider module to actually access + * directories, files, etc. The provider interfaces are described in + * "vboxfs_prov.h" and the module implementing them is shipped as part of the + * VirtualBox Guest Additions for Solaris. + * + * The shared folder file system is similar to a networked file system, + * but with some caveats. The sffs code caches minimal information and proxies + * out to the provider whenever possible. Here are some things that are + * handled in this code and not by the proxy: + * + * - a way to open ".." from any already open directory + * - st_ino numbers + * - detecting directory changes that happened on the host. + * + * The implementation builds a cache of information for every file/directory + * ever accessed in all mounted sffs filesystems using sf_node structures. + * + * This information for both open or closed files can become invalid if + * asynchronous changes are made on the host. Solaris should not panic() in + * this event, but some file system operations may return unexpected errors. + * Information for such directories or files while they have active vnodes + * is removed from the regular cache and stored in a "stale" bucket until + * the vnode becomes completely inactive. + * + * We suppport only read-only mmap (VBOXVFS_WITH_MMAP) i.e. MAP_SHARED, + * MAP_PRIVATE in PROT_READ, this data caching would not be coherent with + * normal simultaneous read()/write() operations, nor will it be coherent + * with data access on the host. Writable mmap(MAP_SHARED) access is not + * implemented, as guaranteeing any kind of coherency with concurrent + * activity on the host would be near impossible with the existing + * interfaces. + * + * A note about locking. sffs is not a high performance file system. + * No fine grained locking is done. The one sffs_lock protects just about + * everything. + */ + +#include <VBox/log.h> +#include <iprt/asm.h> + +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mntent.h> +#include <sys/param.h> +#include <sys/modctl.h> +#include <sys/mount.h> +#include <sys/policy.h> +#include <sys/atomic.h> +#include <sys/sysmacros.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/vfs.h> +#include <sys/vmsystm.h> +#include <vm/seg_kpm.h> +#include <vm/pvn.h> +#if !defined(VBOX_VFS_SOLARIS_10U6) +# include <sys/vfs_opreg.h> +#endif +#include <sys/pathname.h> +#include <sys/dirent.h> +#include <sys/fs_subr.h> +#include <sys/time.h> +#include <sys/cmn_err.h> +#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */ + +#include "vboxfs_prov.h" +#include "vboxfs_vnode.h" +#include "vboxfs_vfs.h" + +/* + * Solaris 11u1b10 Extended Policy putback CR 7121445 removes secpolicy_vnode_access from sys/policy.h + */ +#ifdef VBOX_VFS_EXTENDED_POLICY +int secpolicy_vnode_access(const cred_t *, vnode_t *, uid_t, mode_t); +#endif + +#define VBOXVFS_WITH_MMAP + +static struct vnodeops *sffs_ops = NULL; + +kmutex_t sffs_lock; +static avl_tree_t sfnodes; +static avl_tree_t stale_sfnodes; + +/* + * For now we'll use an I/O buffer that doesn't page fault for VirtualBox + * to transfer data into. + */ +char *sffs_buffer; + +/* + * sfnode_compare() is needed for AVL tree functionality. + * The nodes are sorted by mounted filesystem, then path. If the + * nodes are stale, the node pointer itself is used to force uniqueness. + */ +static int +sfnode_compare(const void *a, const void *b) +{ + sfnode_t *x = (sfnode_t *)a; + sfnode_t *y = (sfnode_t *)b; + int diff; + + if (x->sf_is_stale) { + ASSERT(y->sf_is_stale); + diff = strcmp(x->sf_path, y->sf_path); + if (diff == 0) + diff = (uintptr_t)y - (uintptr_t)x; + } else { + ASSERT(!y->sf_is_stale); + diff = (uintptr_t)y->sf_sffs - (uintptr_t)x->sf_sffs; + if (diff == 0) + diff = strcmp(x->sf_path, y->sf_path); + } + if (diff < 0) + return (-1); + if (diff > 0) + return (1); + return (0); +} + +/* + * Construct a new pathname given an sfnode plus an optional tail component. + * This handles ".." and "." + */ +static char * +sfnode_construct_path(sfnode_t *node, char *tail) +{ + char *p; + + if (strcmp(tail, ".") == 0 || strcmp(tail, "..") == 0) + panic("construct path for %s", tail); + p = kmem_alloc(strlen(node->sf_path) + 1 + strlen(tail) + 1, KM_SLEEP); + strcpy(p, node->sf_path); + strcat(p, "/"); + strcat(p, tail); + return (p); +} + +/* + * Clears the (cached) directory listing for the node. + */ +static void +sfnode_clear_dir_list(sfnode_t *node) +{ + ASSERT(MUTEX_HELD(&sffs_lock)); + + while (node->sf_dir_list != NULL) { + sffs_dirents_t *next = node->sf_dir_list->sf_next; + kmem_free(node->sf_dir_list, SFFS_DIRENTS_SIZE); + node->sf_dir_list = next; + } +} + +/* + * Open the provider file associated with a vnode. Holding the file open is + * the only way we have of trying to have a vnode continue to refer to the + * same host file in the host in light of the possibility of host side renames. + */ +static void +sfnode_open(sfnode_t *node, int flag) +{ + int error; + sfp_file_t *fp; + + if (node->sf_file != NULL) + return; + error = sfprov_open(node->sf_sffs->sf_handle, node->sf_path, &fp, flag); + if (error == 0) + { + node->sf_file = fp; + node->sf_flag = flag; + } + else + node->sf_flag = ~0; +} + +/* + * get a new vnode reference for an sfnode + */ +vnode_t * +sfnode_get_vnode(sfnode_t *node) +{ + vnode_t *vp; + + if (node->sf_vnode != NULL) { + VN_HOLD(node->sf_vnode); + } else { + vp = vn_alloc(KM_SLEEP); + LogFlowFunc((" %s gets vnode 0x%p\n", node->sf_path, vp)); + vp->v_type = node->sf_type; + vp->v_vfsp = node->sf_sffs->sf_vfsp; + vn_setops(vp, sffs_ops); + vp->v_flag = VNOSWAP; +#ifndef VBOXVFS_WITH_MMAP + vp->v_flag |= VNOMAP; +#endif + vn_exists(vp); + vp->v_data = node; + node->sf_vnode = vp; + } + return (node->sf_vnode); +} + +/* + * Allocate and initialize a new sfnode and assign it a vnode + */ +sfnode_t * +sfnode_make( + sffs_data_t *sffs, + char *path, + vtype_t type, + sfp_file_t *fp, + sfnode_t *parent, /* can be NULL for root */ + sffs_stat_t *stat, + uint64_t stat_time) +{ + sfnode_t *node; + avl_index_t where; + + ASSERT(MUTEX_HELD(&sffs_lock)); + ASSERT(path != NULL); + + /* + * build the sfnode + */ + LogFlowFunc(("sffs_make(%s)\n", path)); + node = kmem_alloc(sizeof (*node), KM_SLEEP); + node->sf_sffs = sffs; + VFS_HOLD(node->sf_sffs->sf_vfsp); + node->sf_path = path; + node->sf_ino = sffs->sf_ino++; + node->sf_type = type; + node->sf_is_stale = 0; /* never stale at creation */ + node->sf_file = fp; + node->sf_flag = ~0; + node->sf_vnode = NULL; /* do this before any sfnode_get_vnode() */ + node->sf_children = 0; + node->sf_parent = parent; + if (parent) + ++parent->sf_children; + node->sf_dir_list = NULL; + if (stat != NULL) { + node->sf_stat = *stat; + node->sf_stat_time = stat_time; + } else { + node->sf_stat_time = 0; + } + + /* + * add the new node to our cache + */ + if (avl_find(&sfnodes, node, &where) != NULL) + panic("sffs_create_sfnode(%s): duplicate sfnode_t", path); + avl_insert(&sfnodes, node, where); + return (node); +} + +/* + * destroy an sfnode + */ +static void +sfnode_destroy(sfnode_t *node) +{ + avl_index_t where; + avl_tree_t *tree; + sfnode_t *parent; +top: + parent = node->sf_parent; + ASSERT(MUTEX_HELD(&sffs_lock)); + ASSERT(node->sf_path != NULL); + LogFlowFunc(("sffs_destroy(%s)%s\n", node->sf_path, node->sf_is_stale ? " stale": "")); + if (node->sf_children != 0) + panic("sfnode_destroy(%s) has %d children", node->sf_path, node->sf_children); + if (node->sf_vnode != NULL) + panic("sfnode_destroy(%s) has active vnode", node->sf_path); + + if (node->sf_is_stale) + tree = &stale_sfnodes; + else + tree = &sfnodes; + if (avl_find(tree, node, &where) == NULL) + panic("sfnode_destroy(%s) not found", node->sf_path); + avl_remove(tree, node); + + VFS_RELE(node->sf_sffs->sf_vfsp); + sfnode_clear_dir_list(node); + kmem_free(node->sf_path, strlen(node->sf_path) + 1); + kmem_free(node, sizeof (*node)); + if (parent != NULL) { + sfnode_clear_dir_list(parent); + if (parent->sf_children == 0) + panic("sfnode_destroy parent (%s) has no child", parent->sf_path); + --parent->sf_children; + if (parent->sf_children == 0 && + parent->sf_is_stale && + parent->sf_vnode == NULL) { + node = parent; + goto top; + } + } +} + +/* + * Some sort of host operation on an sfnode has failed or it has been + * deleted. Mark this node and any children as stale, deleting knowledge + * about any which do not have active vnodes or children + * This also handle deleting an inactive node that was already stale. + */ +static void +sfnode_make_stale(sfnode_t *node) +{ + sfnode_t *n; + int len; + ASSERT(MUTEX_HELD(&sffs_lock)); + avl_index_t where; + + /* + * First deal with any children of a directory node. + * If a directory becomes stale, anything below it becomes stale too. + */ + if (!node->sf_is_stale && node->sf_type == VDIR) { + len = strlen(node->sf_path); + + n = node; + while ((n = AVL_NEXT(&sfnodes, node)) != NULL) { + ASSERT(!n->sf_is_stale); + + /* + * quit when no longer seeing children of node + */ + if (n->sf_sffs != node->sf_sffs || + strncmp(node->sf_path, n->sf_path, len) != 0 || + n->sf_path[len] != '/') + break; + + /* + * Either mark the child as stale or destroy it + */ + if (n->sf_vnode == NULL && n->sf_children == 0) { + sfnode_destroy(n); + } else { + LogFlowFunc(("sffs_make_stale(%s) sub\n", n->sf_path)); + sfnode_clear_dir_list(n); + if (avl_find(&sfnodes, n, &where) == NULL) + panic("sfnode_make_stale(%s)" + " not in sfnodes", n->sf_path); + avl_remove(&sfnodes, n); + n->sf_is_stale = 1; + if (avl_find(&stale_sfnodes, n, &where) != NULL) + panic("sffs_make_stale(%s) duplicates", + n->sf_path); + avl_insert(&stale_sfnodes, n, where); + } + } + } + + /* + * Now deal with the given node. + */ + if (node->sf_vnode == NULL && node->sf_children == 0) { + sfnode_destroy(node); + } else if (!node->sf_is_stale) { + LogFlowFunc(("sffs_make_stale(%s)\n", node->sf_path)); + sfnode_clear_dir_list(node); + if (node->sf_parent) + sfnode_clear_dir_list(node->sf_parent); + if (avl_find(&sfnodes, node, &where) == NULL) + panic("sfnode_make_stale(%s) not in sfnodes", + node->sf_path); + avl_remove(&sfnodes, node); + node->sf_is_stale = 1; + if (avl_find(&stale_sfnodes, node, &where) != NULL) + panic("sffs_make_stale(%s) duplicates", node->sf_path); + avl_insert(&stale_sfnodes, node, where); + } +} + +static uint64_t +sfnode_cur_time_usec(void) +{ + clock_t now = drv_hztousec(ddi_get_lbolt()); + return now; +} + +static int +sfnode_stat_cached(sfnode_t *node) +{ + return (sfnode_cur_time_usec() - node->sf_stat_time) < + node->sf_sffs->sf_stat_ttl * 1000L; +} + +static void +sfnode_invalidate_stat_cache(sfnode_t *node) +{ + node->sf_stat_time = 0; +} + +static int +sfnode_update_stat_cache(sfnode_t *node) +{ + int error; + + error = sfprov_get_attr(node->sf_sffs->sf_handle, node->sf_path, + &node->sf_stat); + if (error == ENOENT) + sfnode_make_stale(node); + if (error == 0) + node->sf_stat_time = sfnode_cur_time_usec(); + + return (error); +} + +/* + * Rename a file or a directory + */ +static void +sfnode_rename(sfnode_t *node, sfnode_t *newparent, char *path) +{ + sfnode_t *n; + sfnode_t template; + avl_index_t where; + int len = strlen(path); + int old_len; + char *new_path; + char *tail; + ASSERT(MUTEX_HELD(&sffs_lock)); + + ASSERT(!node->sf_is_stale); + + /* + * Have to remove anything existing that had the new name. + */ + template.sf_sffs = node->sf_sffs; + template.sf_path = path; + template.sf_is_stale = 0; + n = avl_find(&sfnodes, &template, &where); + if (n != NULL) + sfnode_make_stale(n); + + /* + * Do the renaming, deal with any children of this node first. + */ + if (node->sf_type == VDIR) { + old_len = strlen(node->sf_path); + while ((n = AVL_NEXT(&sfnodes, node)) != NULL) { + + /* + * quit when no longer seeing children of node + */ + if (n->sf_sffs != node->sf_sffs || + strncmp(node->sf_path, n->sf_path, old_len) != 0 || + n->sf_path[old_len] != '/') + break; + + /* + * Rename the child: + * - build the new path name + * - unlink the AVL node + * - assign the new name + * - re-insert the AVL name + */ + ASSERT(strlen(n->sf_path) > old_len); + tail = n->sf_path + old_len; /* includes initial "/" */ + new_path = kmem_alloc(len + strlen(tail) + 1, + KM_SLEEP); + strcpy(new_path, path); + strcat(new_path, tail); + if (avl_find(&sfnodes, n, &where) == NULL) + panic("sfnode_rename(%s) not in sfnodes", + n->sf_path); + avl_remove(&sfnodes, n); + LogFlowFunc(("sfnode_rname(%s to %s) sub\n", n->sf_path, new_path)); + kmem_free(n->sf_path, strlen(n->sf_path) + 1); + n->sf_path = new_path; + if (avl_find(&sfnodes, n, &where) != NULL) + panic("sfnode_rename(%s) duplicates", + n->sf_path); + avl_insert(&sfnodes, n, where); + } + } + + /* + * Deal with the given node. + */ + if (avl_find(&sfnodes, node, &where) == NULL) + panic("sfnode_rename(%s) not in sfnodes", node->sf_path); + avl_remove(&sfnodes, node); + LogFlowFunc(("sfnode_rname(%s to %s)\n", node->sf_path, path)); + kmem_free(node->sf_path, strlen(node->sf_path) + 1); + node->sf_path = path; + if (avl_find(&sfnodes, node, &where) != NULL) + panic("sfnode_rename(%s) duplicates", node->sf_path); + avl_insert(&sfnodes, node, where); + + /* + * change the parent + */ + if (node->sf_parent == NULL) + panic("sfnode_rename(%s) no parent", node->sf_path); + if (node->sf_parent->sf_children == 0) + panic("sfnode_rename(%s) parent has no child", node->sf_path); + sfnode_clear_dir_list(node->sf_parent); + sfnode_clear_dir_list(newparent); + --node->sf_parent->sf_children; + node->sf_parent = newparent; + ++newparent->sf_children; +} + +/* + * Look for a cached node, if not found either handle ".." or try looking + * via the provider. Create an entry in sfnodes if found but not cached yet. + * If the create flag is set, a file or directory is created. If the file + * already existed, an error is returned. + * Nodes returned from this routine always have a vnode with its ref count + * bumped by 1. + */ +static sfnode_t * +sfnode_lookup( + sfnode_t *dir, + char *name, + vtype_t create, + mode_t c_mode, + sffs_stat_t *stat, + uint64_t stat_time, + int *err) +{ + avl_index_t where; + sfnode_t template; + sfnode_t *node; + int error = 0; + int type; + char *fullpath; + sfp_file_t *fp; + sffs_stat_t tmp_stat; + + ASSERT(MUTEX_HELD(&sffs_lock)); + + if (err) + *err = error; + + /* + * handle referencing myself + */ + if (strcmp(name, "") == 0 || strcmp(name, ".") == 0) + return (dir); + + /* + * deal with parent + */ + if (strcmp(name, "..") == 0) + return (dir->sf_parent); + + /* + * Look for an existing node. + */ + fullpath = sfnode_construct_path(dir, name); + template.sf_sffs = dir->sf_sffs; + template.sf_path = fullpath; + template.sf_is_stale = 0; + node = avl_find(&sfnodes, &template, &where); + if (node != NULL) { + kmem_free(fullpath, strlen(fullpath) + 1); + if (create != VNON) + return (NULL); + return (node); + } + + /* + * No entry for this path currently. + * Check if the file exists with the provider and get the type from + * there. + */ + if (create == VREG) { + type = VREG; + stat = &tmp_stat; + error = sfprov_create(dir->sf_sffs->sf_handle, fullpath, c_mode, + &fp, stat); + stat_time = sfnode_cur_time_usec(); + } else if (create == VDIR) { + type = VDIR; + stat = &tmp_stat; + error = sfprov_mkdir(dir->sf_sffs->sf_handle, fullpath, c_mode, + &fp, stat); + stat_time = sfnode_cur_time_usec(); + } else { + mode_t m; + fp = NULL; + type = VNON; + if (stat == NULL) { + stat = &tmp_stat; + error = sfprov_get_attr(dir->sf_sffs->sf_handle, + fullpath, stat); + stat_time = sfnode_cur_time_usec(); + } else { + error = 0; + } + m = stat->sf_mode; + if (error != 0) + error = ENOENT; + else if (S_ISDIR(m)) + type = VDIR; + else if (S_ISREG(m)) + type = VREG; + else if (S_ISLNK(m)) + type = VLNK; + } + + if (err) + *err = error; + + /* + * If no errors, make a new node and return it. + */ + if (error) { + kmem_free(fullpath, strlen(fullpath) + 1); + return (NULL); + } + node = sfnode_make(dir->sf_sffs, fullpath, type, fp, dir, stat, + stat_time); + return (node); +} + + +/* + * uid and gid in sffs determine owner and group for all files. + */ +static int +sfnode_access(sfnode_t *node, mode_t mode, cred_t *cr) +{ + sffs_data_t *sffs = node->sf_sffs; + mode_t m; + int shift = 0; + int error; + vnode_t *vp; + + ASSERT(MUTEX_HELD(&sffs_lock)); + + /* + * get the mode from the cache or provider + */ + if (sfnode_stat_cached(node)) + error = 0; + else + error = sfnode_update_stat_cache(node); + m = (error == 0) ? (node->sf_stat.sf_mode & MODEMASK) : 0; + + /* + * mask off the permissions based on uid/gid + */ + if (crgetuid(cr) != sffs->sf_handle->sf_uid) { + shift += 3; + if (groupmember(sffs->sf_handle->sf_gid, cr) == 0) + shift += 3; + } + mode &= ~(m << shift); + + if (mode == 0) { + error = 0; + } else { + /** @todo r=ramshankar: This can probably be optimized by holding static vnode + * templates for dir/file, as it only checks the type rather than + * fetching/allocating the real vnode. */ + vp = sfnode_get_vnode(node); + error = secpolicy_vnode_access(cr, vp, sffs->sf_handle->sf_uid, mode); + VN_RELE(vp); + } + return (error); +} + + +/* + * + * Everything below this point are the vnode operations used by Solaris VFS + */ +static int +sffs_readdir( + vnode_t *vp, + uio_t *uiop, + cred_t *cred, + int *eofp, + caller_context_t *ct, + int flag) +{ + sfnode_t *dir = VN2SFN(vp); + sfnode_t *node; + struct sffs_dirent *dirent = NULL; + sffs_dirents_t *cur_buf; + offset_t offset = 0; + offset_t orig_off = uiop->uio_loffset; + int dummy_eof; + int error = 0; + + if (uiop->uio_iovcnt != 1) + return (EINVAL); + + if (vp->v_type != VDIR) + return (ENOTDIR); + + if (eofp == NULL) + eofp = &dummy_eof; + *eofp = 0; + + if (uiop->uio_loffset >= MAXOFFSET_T) { + *eofp = 1; + return (0); + } + + /* + * Get the directory entry names from the host. This gets all + * entries. These are stored in a linked list of sffs_dirents_t + * buffers, each of which contains a list of dirent64_t's. + */ + mutex_enter(&sffs_lock); + + if (dir->sf_dir_list == NULL) { + error = sfprov_readdir(dir->sf_sffs->sf_handle, dir->sf_path, + &dir->sf_dir_list, flag); + if (error != 0) + goto done; + } + + /* + * Validate and skip to the desired offset. + */ + cur_buf = dir->sf_dir_list; + offset = 0; + + while (cur_buf != NULL && + offset + cur_buf->sf_len <= uiop->uio_loffset) { + offset += cur_buf->sf_len; + cur_buf = cur_buf->sf_next; + } + + if (cur_buf == NULL && offset != uiop->uio_loffset) { + error = EINVAL; + goto done; + } + if (cur_buf != NULL && offset != uiop->uio_loffset) { + offset_t off = offset; + int step; + dirent = &cur_buf->sf_entries[0]; + + while (off < uiop->uio_loffset) { + if (dirent->sf_entry.d_off == uiop->uio_loffset) + break; + step = sizeof(sffs_stat_t) + dirent->sf_entry.d_reclen; + dirent = (struct sffs_dirent *) (((char *) dirent) + step); + off += step; + } + + if (off >= uiop->uio_loffset) { + error = EINVAL; + goto done; + } + } + + offset = uiop->uio_loffset - offset; + + /* + * Lookup each of the names, so that we have ino's, and copy to + * result buffer. + */ + while (cur_buf != NULL) { + if (offset >= cur_buf->sf_len) { + cur_buf = cur_buf->sf_next; + offset = 0; + continue; + } + + dirent = (struct sffs_dirent *) + (((char *) &cur_buf->sf_entries[0]) + offset); + if (dirent->sf_entry.d_reclen > uiop->uio_resid) + break; + + if (strcmp(dirent->sf_entry.d_name, ".") == 0) { + node = dir; + } else if (strcmp(dirent->sf_entry.d_name, "..") == 0) { + node = dir->sf_parent; + if (node == NULL) + node = dir; + } else { + node = sfnode_lookup(dir, dirent->sf_entry.d_name, VNON, + 0, &dirent->sf_stat, sfnode_cur_time_usec(), NULL); + if (node == NULL) + panic("sffs_readdir() lookup failed"); + } + dirent->sf_entry.d_ino = node->sf_ino; + + error = uiomove(&dirent->sf_entry, dirent->sf_entry.d_reclen, UIO_READ, uiop); + if (error != 0) + break; + + uiop->uio_loffset= dirent->sf_entry.d_off; + offset += sizeof(sffs_stat_t) + dirent->sf_entry.d_reclen; + } + if (error == 0 && cur_buf == NULL) + *eofp = 1; +done: + mutex_exit(&sffs_lock); + if (error != 0) + uiop->uio_loffset = orig_off; + return (error); +} + + +#if defined(VBOX_VFS_SOLARIS_10U6) +/* + * HERE JOE.. this may need more logic, need to look at other file systems + */ +static int +sffs_pathconf( + vnode_t *vp, + int cmd, + ulong_t *valp, + cred_t *cr) +{ + return (fs_pathconf(vp, cmd, valp, cr)); +} +#else +/* + * HERE JOE.. this may need more logic, need to look at other file systems + */ +static int +sffs_pathconf( + vnode_t *vp, + int cmd, + ulong_t *valp, + cred_t *cr, + caller_context_t *ct) +{ + return (fs_pathconf(vp, cmd, valp, cr, ct)); +} +#endif + +static int +sffs_getattr( + vnode_t *vp, + vattr_t *vap, + int flags, + cred_t *cred, + caller_context_t *ct) +{ + sfnode_t *node = VN2SFN(vp); + sffs_data_t *sffs = node->sf_sffs; + mode_t mode; + int error = 0; + + mutex_enter(&sffs_lock); + vap->va_type = vp->v_type; + vap->va_uid = sffs->sf_handle->sf_uid; + vap->va_gid = sffs->sf_handle->sf_gid; + vap->va_fsid = sffs->sf_vfsp->vfs_dev; + vap->va_nodeid = node->sf_ino; + vap->va_nlink = 1; + vap->va_rdev = sffs->sf_vfsp->vfs_dev; + vap->va_seq = 0; + + if (!sfnode_stat_cached(node)) { + error = sfnode_update_stat_cache(node); + if (error != 0) + goto done; + } + + vap->va_atime = node->sf_stat.sf_atime; + vap->va_mtime = node->sf_stat.sf_mtime; + vap->va_ctime = node->sf_stat.sf_ctime; + + mode = node->sf_stat.sf_mode; + vap->va_mode = mode & MODEMASK; + + vap->va_size = node->sf_stat.sf_size; + vap->va_blksize = 512; + vap->va_nblocks = (node->sf_stat.sf_alloc + 511) / 512; + +done: + mutex_exit(&sffs_lock); + return (error); +} + +static int +sffs_setattr( + vnode_t *vp, + vattr_t *vap, + int flags, + cred_t *cred, + caller_context_t *ct) +{ + sfnode_t *node = VN2SFN(vp); + int error; + mode_t mode; + + mode = vap->va_mode; + if (vp->v_type == VREG) + mode |= S_IFREG; + else if (vp->v_type == VDIR) + mode |= S_IFDIR; + else if (vp->v_type == VBLK) + mode |= S_IFBLK; + else if (vp->v_type == VCHR) + mode |= S_IFCHR; + else if (vp->v_type == VLNK) + mode |= S_IFLNK; + else if (vp->v_type == VFIFO) + mode |= S_IFIFO; + else if (vp->v_type == VSOCK) + mode |= S_IFSOCK; + + mutex_enter(&sffs_lock); + + sfnode_invalidate_stat_cache(node); + error = sfprov_set_attr(node->sf_sffs->sf_handle, node->sf_path, + vap->va_mask, mode, vap->va_atime, vap->va_mtime, vap->va_ctime); + if (error == ENOENT) + sfnode_make_stale(node); + + mutex_exit(&sffs_lock); + return (error); +} + +static int +sffs_space( + vnode_t *vp, + int cmd, + struct flock64 *bfp, + int flags, + offset_t off, + cred_t *cred, + caller_context_t *ct) +{ + sfnode_t *node = VN2SFN(vp); + int error; + + /* we only support changing the length of the file */ + if (bfp->l_whence != SEEK_SET || bfp->l_len != 0) + return ENOSYS; + + mutex_enter(&sffs_lock); + + sfnode_invalidate_stat_cache(node); + + error = sfprov_set_size(node->sf_sffs->sf_handle, node->sf_path, + bfp->l_start); + if (error == ENOENT) + sfnode_make_stale(node); + + mutex_exit(&sffs_lock); + return (error); +} + +/*ARGSUSED*/ +static int +sffs_read( + vnode_t *vp, + struct uio *uio, + int ioflag, + cred_t *cred, + caller_context_t *ct) +{ + sfnode_t *node = VN2SFN(vp); + int error = 0; + uint32_t bytes; + uint32_t done; + ulong_t offset; + ssize_t total; + + if (vp->v_type == VDIR) + return (EISDIR); + if (vp->v_type != VREG) + return (EINVAL); + if (uio->uio_loffset >= MAXOFFSET_T) + return (0); + if (uio->uio_loffset < 0) + return (EINVAL); + total = uio->uio_resid; + if (total == 0) + return (0); + + mutex_enter(&sffs_lock); + if (node->sf_file == NULL) { + ASSERT(node->sf_flag != ~0); + sfnode_open(node, node->sf_flag); + if (node->sf_file == NULL) + return (EBADF); + } + + do { + offset = uio->uio_offset; + done = bytes = MIN(PAGESIZE, uio->uio_resid); + error = sfprov_read(node->sf_file, sffs_buffer, offset, &done); + if (error == 0 && done > 0) + error = uiomove(sffs_buffer, done, UIO_READ, uio); + } while (error == 0 && uio->uio_resid > 0 && done > 0); + + mutex_exit(&sffs_lock); + + /* + * a partial read is never an error + */ + if (total != uio->uio_resid) + error = 0; + return (error); +} + +/*ARGSUSED*/ +static int +sffs_write( + vnode_t *vp, + struct uio *uiop, + int ioflag, + cred_t *cred, + caller_context_t *ct) +{ + sfnode_t *node = VN2SFN(vp); + int error = 0; + uint32_t bytes; + uint32_t done; + ulong_t offset; + ssize_t total; + rlim64_t limit = uiop->uio_llimit; + + if (vp->v_type == VDIR) + return (EISDIR); + if (vp->v_type != VREG) + return (EINVAL); + + /* + * We have to hold this lock for a long time to keep + * multiple FAPPEND writes from intermixing + */ + mutex_enter(&sffs_lock); + if (node->sf_file == NULL) { + ASSERT(node->sf_flag != ~0); + sfnode_open(node, node->sf_flag); + if (node->sf_file == NULL) + return (EBADF); + } + + sfnode_invalidate_stat_cache(node); + + if (ioflag & FAPPEND) { + uint64_t endoffile; + + error = sfprov_get_size(node->sf_sffs->sf_handle, + node->sf_path, &endoffile); + if (error == ENOENT) + sfnode_make_stale(node); + if (error != 0) { + mutex_exit(&sffs_lock); + return (error); + } + uiop->uio_loffset = endoffile; + } + + if (vp->v_type != VREG || uiop->uio_loffset < 0) { + mutex_exit(&sffs_lock); + return (EINVAL); + } + if (limit == RLIM64_INFINITY || limit > MAXOFFSET_T) + limit = MAXOFFSET_T; + + if (uiop->uio_loffset >= limit) { + mutex_exit(&sffs_lock); + return (EFBIG); + } + + if (uiop->uio_loffset >= MAXOFFSET_T) { + mutex_exit(&sffs_lock); + return (EFBIG); + } + + total = uiop->uio_resid; + if (total == 0) { + mutex_exit(&sffs_lock); + return (0); + } + + do { + offset = uiop->uio_offset; + bytes = MIN(PAGESIZE, uiop->uio_resid); + if (offset + bytes >= limit) { + if (offset >= limit) { + error = EFBIG; + break; + } + bytes = limit - offset; + } + error = uiomove(sffs_buffer, bytes, UIO_WRITE, uiop); + if (error != 0) + break; + done = bytes; + if (error == 0) + error = sfprov_write(node->sf_file, sffs_buffer, + offset, &done); + total -= done; + if (done != bytes) { + uiop->uio_resid += bytes - done; + break; + } + } while (error == 0 && uiop->uio_resid > 0 && done > 0); + + mutex_exit(&sffs_lock); + + /* + * A short write is never really an error. + */ + if (total != uiop->uio_resid) + error = 0; + return (error); +} + +/*ARGSUSED*/ +static int +sffs_access(vnode_t *vp, int mode, int flags, cred_t *cr, caller_context_t *ct) +{ + sfnode_t *node = VN2SFN(vp); + int error; + + mutex_enter(&sffs_lock); + error = sfnode_access(node, mode, cr); + mutex_exit(&sffs_lock); + return (error); +} + +/* + * Lookup an entry in a directory and create a new vnode if found. + */ +/* ARGSUSED3 */ +static int +sffs_lookup( + vnode_t *dvp, /* the directory vnode */ + char *name, /* the name of the file or directory */ + vnode_t **vpp, /* the vnode we found or NULL */ + struct pathname *pnp, + int flags, + vnode_t *rdir, + cred_t *cred, + caller_context_t *ct, + int *direntflags, + struct pathname *realpnp) +{ + int error; + sfnode_t *node; + + /* + * dvp must be a directory + */ + if (dvp->v_type != VDIR) + return (ENOTDIR); + + /* + * An empty component name or just "." means the directory itself. + * Don't do any further lookup or checking. + */ + if (strcmp(name, "") == 0 || strcmp(name, ".") == 0) { + VN_HOLD(dvp); + *vpp = dvp; + return (0); + } + + /* + * Check permission to look at this directory. We always allow "..". + */ + mutex_enter(&sffs_lock); + if (strcmp(name, "..") != 0) { + error = sfnode_access(VN2SFN(dvp), VEXEC, cred); + if (error) { + mutex_exit(&sffs_lock); + return (error); + } + } + + /* + * Lookup the node. + */ + node = sfnode_lookup(VN2SFN(dvp), name, VNON, 0, NULL, 0, NULL); + if (node != NULL) + *vpp = sfnode_get_vnode(node); + mutex_exit(&sffs_lock); + return ((node == NULL) ? ENOENT : 0); +} + +/*ARGSUSED*/ +static int +sffs_create( + vnode_t *dvp, + char *name, + struct vattr *vap, + vcexcl_t exclusive, + int mode, + vnode_t **vpp, + cred_t *cr, + int flag, + caller_context_t *ct, + vsecattr_t *vsecp) +{ + vnode_t *vp; + sfnode_t *node; + int error; + + ASSERT(name != NULL); + + /* + * this is used for regular files, not mkdir + */ + if (vap->va_type == VDIR) + return (EISDIR); + if (vap->va_type != VREG) + return (EINVAL); + + /* + * is this a pre-existing file? + */ + error = sffs_lookup(dvp, name, &vp, + NULL, 0, NULL, cr, ct, NULL, NULL); + if (error == ENOENT) + vp = NULL; + else if (error != 0) + return (error); + + /* + * Operation on a pre-existing file. + */ + if (vp != NULL) { + if (exclusive == EXCL) { + VN_RELE(vp); + return (EEXIST); + } + if (vp->v_type == VDIR && (mode & VWRITE) == VWRITE) { + VN_RELE(vp); + return (EISDIR); + } + + mutex_enter(&sffs_lock); + node = VN2SFN(vp); + error = sfnode_access(node, mode, cr); + if (error != 0) { + mutex_exit(&sffs_lock); + VN_RELE(vp); + return (error); + } + + sfnode_invalidate_stat_cache(VN2SFN(dvp)); + + /* + * handle truncating an existing file + */ + if (vp->v_type == VREG && (vap->va_mask & AT_SIZE) && + vap->va_size == 0) { + sfnode_open(node, flag | FTRUNC); + if (node->sf_path == NULL) { + mutex_exit(&sffs_lock); + VN_RELE(vp); + return (ENOENT); + } + } + mutex_exit(&sffs_lock); + *vpp = vp; + return (0); + } + + /* + * Create a new node. First check for a race creating it. + */ + mutex_enter(&sffs_lock); + node = sfnode_lookup(VN2SFN(dvp), name, VNON, 0, NULL, 0, NULL); + if (node != NULL) { + mutex_exit(&sffs_lock); + return (EEXIST); + } + + /* + * Doesn't exist yet and we have the lock, so create it. + */ + sfnode_invalidate_stat_cache(VN2SFN(dvp)); + int lookuperr; + node = sfnode_lookup(VN2SFN(dvp), name, VREG, + (vap->va_mask & AT_MODE) ? vap->va_mode : 0, NULL, 0, &lookuperr); + + if (node && node->sf_parent) + sfnode_clear_dir_list(node->sf_parent); + + mutex_exit(&sffs_lock); + if (node == NULL) + return (lookuperr); + *vpp = sfnode_get_vnode(node); + return (0); +} + +/*ARGSUSED*/ +static int +sffs_mkdir( + vnode_t *dvp, + char *nm, + vattr_t *va, + vnode_t **vpp, + cred_t *cred, + caller_context_t *ct, + int flags, + vsecattr_t *vsecp) +{ + sfnode_t *node; + vnode_t *vp; + int error; + + /* + * These should never happen + */ + ASSERT(nm != NULL); + ASSERT(strcmp(nm, "") != 0); + ASSERT(strcmp(nm, ".") != 0); + ASSERT(strcmp(nm, "..") != 0); + + /* + * Do an unlocked look up first + */ + error = sffs_lookup(dvp, nm, &vp, NULL, 0, NULL, cred, ct, NULL, NULL); + if (error == 0) { + VN_RELE(vp); + return (EEXIST); + } + if (error != ENOENT) + return (error); + + /* + * Must be able to write in current directory + */ + mutex_enter(&sffs_lock); + error = sfnode_access(VN2SFN(dvp), VWRITE, cred); + if (error) { + mutex_exit(&sffs_lock); + return (error); + } + + sfnode_invalidate_stat_cache(VN2SFN(dvp)); + int lookuperr = EACCES; + node = sfnode_lookup(VN2SFN(dvp), nm, VDIR, + (va->va_mode & AT_MODE) ? va->va_mode : 0, NULL, 0, &lookuperr); + + if (node && node->sf_parent) + sfnode_clear_dir_list(node->sf_parent); + + mutex_exit(&sffs_lock); + if (node == NULL) + return (lookuperr); + *vpp = sfnode_get_vnode(node); + return (0); +} + +/*ARGSUSED*/ +static int +sffs_rmdir( + struct vnode *dvp, + char *nm, + vnode_t *cdir, + cred_t *cred, + caller_context_t *ct, + int flags) +{ + sfnode_t *node; + vnode_t *vp; + int error; + + /* + * Return error when removing . and .. + */ + if (strcmp(nm, ".") == 0 || strcmp(nm, "") == 0) + return (EINVAL); + if (strcmp(nm, "..") == 0) + return (EEXIST); + + error = sffs_lookup(dvp, nm, &vp, NULL, 0, NULL, cred, ct, NULL, NULL); + if (error) + return (error); + if (vp->v_type != VDIR) { + VN_RELE(vp); + return (ENOTDIR); + } + +#ifdef VBOXVFS_WITH_MMAP + if (vn_vfswlock(vp)) { + VN_RELE(vp); + return (EBUSY); + } +#endif + + if (vn_mountedvfs(vp)) { + VN_RELE(vp); + return (EBUSY); + } + + node = VN2SFN(vp); + + mutex_enter(&sffs_lock); + error = sfnode_access(VN2SFN(dvp), VEXEC | VWRITE, cred); + if (error) + goto done; + + /* + * If anything else is using this vnode, then fail the remove. + * Why? Windows hosts can't remove something that is open, + * so we have to sfprov_close() it first. + * There is no errno for this - since it's not a problem on UNIX, + * but EINVAL is the closest. + */ + if (node->sf_file != NULL) { + if (vp->v_count > 1) { + error = EINVAL; + goto done; + } + (void)sfprov_close(node->sf_file); + node->sf_file = NULL; + } + + /* + * Remove the directory on the host and mark the node as stale. + */ + sfnode_invalidate_stat_cache(VN2SFN(dvp)); + error = sfprov_rmdir(node->sf_sffs->sf_handle, node->sf_path); + if (error == ENOENT || error == 0) + sfnode_make_stale(node); + + if (node->sf_parent) + sfnode_clear_dir_list(node->sf_parent); +done: + mutex_exit(&sffs_lock); +#ifdef VBOXVFS_WITH_MMAP + vn_vfsunlock(vp); +#endif + VN_RELE(vp); + return (error); +} + + +#ifdef VBOXVFS_WITH_MMAP +static caddr_t +sffs_page_map( + page_t *ppage, + enum seg_rw segaccess) +{ + /* Use seg_kpm driver if possible (64-bit) */ + if (kpm_enable) + return (hat_kpm_mapin(ppage, NULL)); + ASSERT(segaccess == S_READ || segaccess == S_WRITE); + return (ppmapin(ppage, PROT_READ | ((segaccess == S_WRITE) ? PROT_WRITE : 0), (caddr_t)-1)); +} + + +static void +sffs_page_unmap( + page_t *ppage, + caddr_t addr) +{ + if (kpm_enable) + hat_kpm_mapout(ppage, NULL, addr); + else + ppmapout(addr); +} + + +/* + * Called when there's no page in the cache. This will create new page(s) and read + * the file data into it. + */ +static int +sffs_readpages( + vnode_t *dvp, + offset_t off, + page_t *pagelist[], + size_t pagelistsize, + struct seg *segp, + caddr_t addr, + enum seg_rw segaccess) +{ + ASSERT(MUTEX_HELD(&sffs_lock)); + + int error = 0; + u_offset_t io_off, total; + size_t io_len; + page_t *ppages; + page_t *pcur; + + sfnode_t *node = VN2SFN(dvp); + ASSERT(node); + ASSERT(node->sf_file); + + if (pagelistsize == PAGESIZE) + { + io_off = off; + io_len = PAGESIZE; + ppages = page_create_va(dvp, io_off, io_len, PG_WAIT | PG_EXCL, segp, addr); + } + else + ppages = pvn_read_kluster(dvp, off, segp, addr, &io_off, &io_len, off, pagelistsize, 0); + + /* If page already exists return success */ + if (!ppages) + { + *pagelist = NULL; + return (0); + } + + /* + * Map & read page-by-page. + */ + total = io_off + io_len; + pcur = ppages; + while (io_off < total) + { + ASSERT3U(io_off, ==, pcur->p_offset); + + caddr_t virtaddr = sffs_page_map(pcur, segaccess); + uint32_t bytes = PAGESIZE; + error = sfprov_read(node->sf_file, virtaddr, io_off, &bytes); + /* + * If we reuse pages without zero'ing them, one process can mmap() and read-past the length + * to read previously mmap'd contents (from possibly other processes). + */ + if (error == 0 && bytes < PAGESIZE) + memset(virtaddr + bytes, 0, PAGESIZE - bytes); + sffs_page_unmap(pcur, virtaddr); + if (error != 0) + { + cmn_err(CE_WARN, "sffs_readpages: sfprov_read() failed. error=%d bytes=%u\n", error, bytes); + /* Get rid of all kluster pages read & bail. */ + pvn_read_done(ppages, B_ERROR); + return (error); + } + pcur = pcur->p_next; + io_off += PAGESIZE; + } + + /* + * Fill in the pagelist from kluster at the requested offset. + */ + pvn_plist_init(ppages, pagelist, pagelistsize, off, io_len, segaccess); + ASSERT(pagelist == NULL || (*pagelist)->p_offset == off); + return (0); +} + + +/*ARGSUSED*/ +static int +sffs_getpage( + vnode_t *dvp, + offset_t off, + size_t len, + uint_t *protp, + page_t *pagelist[], + size_t pagelistsize, + struct seg *segp, + caddr_t addr, + enum seg_rw segaccess, + cred_t *credp +#if !defined(VBOX_VFS_SOLARIS_10U6) + , caller_context_t *ct +#endif + ) +{ + int error = 0; + int is_recursive = 0; + page_t **pageliststart = pagelist; + sfnode_t *node = VN2SFN(dvp); + ASSERT(node); + ASSERT(node->sf_file); + + if (segaccess == S_WRITE) + return (ENOSYS); /* Will this ever happen? */ + + /* Don't bother about faultahead for now. */ + if (pagelist == NULL) + return (0); + + if (len > pagelistsize) + len = pagelistsize; + else + len = P2ROUNDUP(len, PAGESIZE); + ASSERT(pagelistsize >= len); + + if (protp) + *protp = PROT_ALL; + + /* + * The buffer passed to sffs_write may be mmap'd so we may get a + * pagefault there, in which case we'll end up here with this thread + * already owning the mutex. Mutexes aren't recursive. + */ + if (mutex_owner(&sffs_lock) == curthread) + is_recursive = 1; + else + mutex_enter(&sffs_lock); + + /* Don't map pages past end of the file. */ + if (off + len > node->sf_stat.sf_size + PAGEOFFSET) + { + if (!is_recursive) + mutex_exit(&sffs_lock); + return (EFAULT); + } + + while (len > 0) + { + /* + * Look for pages in the requested offset range, or create them if we can't find any. + */ + if ((*pagelist = page_lookup(dvp, off, SE_SHARED)) != NULL) + *(pagelist + 1) = NULL; + else if ((error = sffs_readpages(dvp, off, pagelist, pagelistsize, segp, addr, segaccess)) != 0) + { + while (pagelist > pageliststart) + page_unlock(*--pagelist); + + *pagelist = NULL; + if (!is_recursive) + mutex_exit(&sffs_lock); + return (error); + } + + while (*pagelist) + { + ASSERT3U((*pagelist)->p_offset, ==, off); + off += PAGESIZE; + addr += PAGESIZE; + if (len > 0) + { + ASSERT3U(len, >=, PAGESIZE); + len -= PAGESIZE; + } + + ASSERT3U(pagelistsize, >=, PAGESIZE); + pagelistsize -= PAGESIZE; + pagelist++; + } + } + + /* + * Fill the page list array with any pages left in the cache. + */ + while ( pagelistsize > 0 + && (*pagelist++ = page_lookup_nowait(dvp, off, SE_SHARED))) + { + off += PAGESIZE; + pagelistsize -= PAGESIZE; + } + + *pagelist = NULL; + if (!is_recursive) + mutex_exit(&sffs_lock); + return (error); +} + + +/*ARGSUSED*/ +static int +sffs_putpage( + vnode_t *dvp, + offset_t off, + size_t len, + int flags, + cred_t *credp +#if !defined(VBOX_VFS_SOLARIS_10U6) + , caller_context_t *ct +#endif + ) +{ + /* + * We don't support PROT_WRITE mmaps. + */ + return (ENOSYS); +} + + +/*ARGSUSED*/ +static int +sffs_discardpage( + vnode_t *dvp, + page_t *ppage, + u_offset_t *poff, + size_t *plen, + int flags, + cred_t *pcred) +{ + /* + * This would not get invoked i.e. via pvn_vplist_dirty() since we don't support + * PROT_WRITE mmaps and therefore will not have dirty pages. + */ + pvn_write_done(ppage, B_INVAL | B_ERROR | B_FORCE); + return (0); +} + + +/*ARGSUSED*/ +static int +sffs_map( + vnode_t *dvp, + offset_t off, + struct as *asp, + caddr_t *addrp, + size_t len, + uchar_t prot, + uchar_t maxprot, + uint_t flags, + cred_t *credp +#if !defined(VBOX_VFS_SOLARIS_10U6) + , caller_context_t *ct +#endif + ) +{ + /* + * Invocation: mmap()->smmap_common()->VOP_MAP()->sffs_map(). Once the + * segment driver creates the new segment via segvn_create(), it'll + * invoke down the line VOP_ADDMAP()->sffs_addmap() + */ + int error = 0; + sfnode_t *node = VN2SFN(dvp); + ASSERT(node); + if ((flags & MAP_SHARED) && (prot & PROT_WRITE)) + return (ENOTSUP); + + if (off < 0 || len > MAXOFFSET_T - off) + return (ENXIO); + + if (dvp->v_type != VREG) + return (ENODEV); + + if (dvp->v_flag & VNOMAP) + return (ENOSYS); + + if (vn_has_mandatory_locks(dvp, node->sf_stat.sf_mode)) + return (EAGAIN); + + mutex_enter(&sffs_lock); + as_rangelock(asp); + +#if defined(VBOX_VFS_SOLARIS_10U6) + if ((flags & MAP_FIXED) == 0) + { + if (g_fVBoxVFS_SolOldAddrMap) + g_VBoxVFS_SolAddrMap.MapAddr.pfnSol_map_addr_old(addrp, len, off, 1, flags); + else + g_VBoxVFS_SolAddrMap.MapAddr.pfnSol_map_addr(addrp, len, off, flags); + if (*addrp == NULL) + error = ENOMEM; + } + else + as_unmap(asp, *addrp, len); /* User specified address, remove any previous mappings */ +#else + if (g_fVBoxVFS_SolOldAddrMap) + error = g_VBoxVFS_SolAddrMap.ChooseAddr.pfnSol_choose_addr_old(asp, addrp, len, off, 1, flags); + else + error = g_VBoxVFS_SolAddrMap.ChooseAddr.pfnSol_choose_addr(asp, addrp, len, off, flags); +#endif + + if (error) + { + as_rangeunlock(asp); + mutex_exit(&sffs_lock); + return (error); + } + + segvn_crargs_t vnodeargs; + memset(&vnodeargs, 0, sizeof(vnodeargs)); + vnodeargs.vp = dvp; + vnodeargs.cred = credp; + vnodeargs.offset = off; + vnodeargs.type = flags & MAP_TYPE; + vnodeargs.prot = prot; + vnodeargs.maxprot = maxprot; + vnodeargs.flags = flags & ~MAP_TYPE; + vnodeargs.amp = NULL; /* anon. mapping */ + vnodeargs.szc = 0; /* preferred page size code */ + vnodeargs.lgrp_mem_policy_flags = 0; + + error = as_map(asp, *addrp, len, segvn_create, &vnodeargs); + + as_rangeunlock(asp); + mutex_exit(&sffs_lock); + return (error); +} + + +/*ARGSUSED*/ +static int +sffs_addmap( + vnode_t *dvp, + offset_t off, + struct as *asp, + caddr_t addr, + size_t len, + uchar_t prot, + uchar_t maxprot, + uint_t flags, + cred_t *credp +#if !defined(VBOX_VFS_SOLARIS_10U6) + , caller_context_t *ct +#endif + ) +{ + if (dvp->v_flag & VNOMAP) + return (ENOSYS); + return (0); +} + + +/*ARGSUSED*/ +static int +sffs_delmap( + vnode_t *dvp, + offset_t off, + struct as *asp, + caddr_t addr, + size_t len, + uint_t prot, + uint_t maxprot, + uint_t flags, + cred_t *credp +#if !defined(VBOX_VFS_SOLARIS_10U6) + , caller_context_t *ct +#endif + ) +{ + if (dvp->v_flag & VNOMAP) + return (ENOSYS); + + return (0); +} +#endif /* VBOXVFS_WITH_MMAP */ + + +/*ARGSUSED*/ +static int +sffs_readlink( + vnode_t *vp, + uio_t *uiop, + cred_t *cred +#if !defined(VBOX_VFS_SOLARIS_10U6) + , + caller_context_t *ct +#endif + ) +{ + sfnode_t *node; + int error = 0; + char *target = NULL; + + if (uiop->uio_iovcnt != 1) + return (EINVAL); + + if (vp->v_type != VLNK) + return (EINVAL); + + mutex_enter(&sffs_lock); + node = VN2SFN(vp); + + target = kmem_alloc(MAXPATHLEN, KM_SLEEP); + + error = sfprov_readlink(node->sf_sffs->sf_handle, node->sf_path, target, + MAXPATHLEN); + if (error) + goto done; + + error = uiomove(target, strlen(target), UIO_READ, uiop); + +done: + mutex_exit(&sffs_lock); + if (target) + kmem_free(target, MAXPATHLEN); + return (error); +} + + +/*ARGSUSED*/ +static int +sffs_symlink( + vnode_t *dvp, + char *linkname, + vattr_t *vap, + char *target, + cred_t *cred +#if !defined(VBOX_VFS_SOLARIS_10U6) + , + caller_context_t *ct, + int flags +#endif + ) +{ + sfnode_t *dir; + sfnode_t *node; + sffs_stat_t stat; + int error = 0; + char *fullpath; + + /* + * These should never happen + */ + ASSERT(linkname != NULL); + ASSERT(strcmp(linkname, "") != 0); + ASSERT(strcmp(linkname, ".") != 0); + ASSERT(strcmp(linkname, "..") != 0); + + /* + * Basic checks. + */ + if (vap->va_type != VLNK) + return (EINVAL); + + mutex_enter(&sffs_lock); + + if (sfnode_lookup(VN2SFN(dvp), linkname, VNON, 0, NULL, 0, NULL) != + NULL) { + error = EEXIST; + goto done; + } + + dir = VN2SFN(dvp); + error = sfnode_access(dir, VWRITE, cred); + if (error) + goto done; + + /* + * Create symlink. Note that we ignore vap->va_mode because generally + * we can't change the attributes of the symlink itself. + */ + fullpath = sfnode_construct_path(dir, linkname); + error = sfprov_symlink(dir->sf_sffs->sf_handle, fullpath, target, + &stat); + kmem_free(fullpath, strlen(fullpath) + 1); + if (error) + goto done; + + node = sfnode_lookup(dir, linkname, VLNK, 0, &stat, + sfnode_cur_time_usec(), NULL); + + sfnode_invalidate_stat_cache(dir); + sfnode_clear_dir_list(dir); + +done: + mutex_exit(&sffs_lock); + return (error); +} + + +/*ARGSUSED*/ +static int +sffs_remove( + vnode_t *dvp, + char *name, + cred_t *cred, + caller_context_t *ct, + int flags) +{ + vnode_t *vp; + sfnode_t *node; + int error; + + /* + * These should never happen + */ + ASSERT(name != NULL); + ASSERT(strcmp(name, "..") != 0); + + error = sffs_lookup(dvp, name, &vp, + NULL, 0, NULL, cred, ct, NULL, NULL); + if (error) + return (error); + node = VN2SFN(vp); + + mutex_enter(&sffs_lock); + error = sfnode_access(VN2SFN(dvp), VEXEC | VWRITE, cred); + if (error) + goto done; + + /* + * If anything else is using this vnode, then fail the remove. + * Why? Windows hosts can't sfprov_remove() a file that is open, + * so we have to sfprov_close() it first. + * There is no errno for this - since it's not a problem on UNIX, + * but ETXTBSY is the closest. + */ + if (node->sf_file != NULL) { + if (vp->v_count > 1) { + error = ETXTBSY; + goto done; + } + (void)sfprov_close(node->sf_file); + node->sf_file = NULL; + } + + /* + * Remove the file on the host and mark the node as stale. + */ + sfnode_invalidate_stat_cache(VN2SFN(dvp)); + + error = sfprov_remove(node->sf_sffs->sf_handle, node->sf_path, + node->sf_type == VLNK); + if (error == ENOENT || error == 0) + sfnode_make_stale(node); + + if (node->sf_parent) + sfnode_clear_dir_list(node->sf_parent); +done: + mutex_exit(&sffs_lock); + VN_RELE(vp); + return (error); +} + +/*ARGSUSED*/ +static int +sffs_rename( + vnode_t *old_dir, + char *old_nm, + vnode_t *new_dir, + char *new_nm, + cred_t *cred, + caller_context_t *ct, + int flags) +{ + char *newpath; + int error; + sfnode_t *node; + + if (strcmp(new_nm, "") == 0 || + strcmp(new_nm, ".") == 0 || + strcmp(new_nm, "..") == 0 || + strcmp(old_nm, "") == 0 || + strcmp(old_nm, ".") == 0 || + strcmp(old_nm, "..") == 0) + return (EINVAL); + + /* + * make sure we have permission to do the rename + */ + mutex_enter(&sffs_lock); + error = sfnode_access(VN2SFN(old_dir), VEXEC | VWRITE, cred); + if (error == 0 && new_dir != old_dir) + error = sfnode_access(VN2SFN(new_dir), VEXEC | VWRITE, cred); + if (error) + goto done; + + node = sfnode_lookup(VN2SFN(old_dir), old_nm, VNON, 0, NULL, 0, NULL); + if (node == NULL) { + error = ENOENT; + goto done; + } + + /* + * Rename the file on the host and in our caches. + */ + sfnode_invalidate_stat_cache(node); + sfnode_invalidate_stat_cache(VN2SFN(old_dir)); + sfnode_invalidate_stat_cache(VN2SFN(new_dir)); + + newpath = sfnode_construct_path(VN2SFN(new_dir), new_nm); + error = sfprov_rename(node->sf_sffs->sf_handle, node->sf_path, newpath, + node->sf_type == VDIR); + if (error == 0) + sfnode_rename(node, VN2SFN(new_dir), newpath); + else { + kmem_free(newpath, strlen(newpath) + 1); + if (error == ENOENT) + sfnode_make_stale(node); + } +done: + mutex_exit(&sffs_lock); + return (error); +} + + +/*ARGSUSED*/ +static int +sffs_fsync(vnode_t *vp, int flag, cred_t *cr, caller_context_t *ct) +{ + sfnode_t *node; + int error; + + /* + * Ask the host to sync any data it may have cached for open files. + */ + mutex_enter(&sffs_lock); + node = VN2SFN(vp); + if (node->sf_file == NULL) + error = EBADF; + else if (node->sf_sffs->sf_fsync) + error = sfprov_fsync(node->sf_file); + else + error = 0; + mutex_exit(&sffs_lock); + return (error); +} + +/* + * This may be the last reference, possibly time to close the file and + * destroy the vnode. If the sfnode is stale, we'll destroy that too. + */ +/*ARGSUSED*/ +static void +#if defined(VBOX_VFS_SOLARIS_10U6) +sffs_inactive(vnode_t *vp, cred_t *cr) +#else +sffs_inactive(vnode_t *vp, cred_t *cr, caller_context_t *ct) +#endif +{ + sfnode_t *node; + + /* + * nothing to do if this isn't the last use + */ + mutex_enter(&sffs_lock); + node = VN2SFN(vp); + mutex_enter(&vp->v_lock); + if (vp->v_count > 1) { + --vp->v_count; + mutex_exit(&vp->v_lock); + mutex_exit(&sffs_lock); + return; + } + + if (vn_has_cached_data(vp)) { +#ifdef VBOXVFS_WITH_MMAP + /* We're fine with releasing the vnode lock here as we should be covered by the sffs_lock */ + mutex_exit(&vp->v_lock); + /* We won't have any dirty pages, this will just invalidate (destroy) the pages and move it to the cachelist. */ + pvn_vplist_dirty(vp, 0 /* offset */, sffs_discardpage, B_INVAL, cr); + mutex_enter(&vp->v_lock); +#else + panic("sffs_inactive() found cached data"); +#endif + } + + /* + * destroy the vnode + */ + node->sf_vnode = NULL; + mutex_exit(&vp->v_lock); + vn_invalid(vp); + vn_free(vp); + LogFlowFunc((" %s vnode cleared\n", node->sf_path)); + + /* + * Close the sf_file for the node. + */ + if (node->sf_file != NULL) { + (void)sfprov_close(node->sf_file); + node->sf_file = NULL; + } + + /* + * Free the directory entries for the node. This should normally + * have been taken care of in sffs_close(), but better safe than + * sorry. + */ + sfnode_clear_dir_list(node); + + /* + * If the node is stale, we can also destroy it. + */ + if (node->sf_is_stale && node->sf_children == 0) + sfnode_destroy(node); + + mutex_exit(&sffs_lock); + return; +} + +/* + * All the work for this is really done in sffs_lookup(). + */ +/*ARGSUSED*/ +static int +sffs_open(vnode_t **vpp, int flag, cred_t *cr, caller_context_t *ct) +{ + sfnode_t *node; + int error = 0; + + mutex_enter(&sffs_lock); + + node = VN2SFN(*vpp); + sfnode_open(node, flag); + if (node->sf_file == NULL) + error = EINVAL; + mutex_exit(&sffs_lock); + + return (error); +} + +/* + * All the work for this is really done in inactive. + */ +/*ARGSUSED*/ +static int +sffs_close( + vnode_t *vp, + int flag, + int count, + offset_t offset, + cred_t *cr, + caller_context_t *ct) +{ + sfnode_t *node; + + mutex_enter(&sffs_lock); + node = VN2SFN(vp); + + /* + * Free the directory entries for the node. We do this on this call + * here because the directory node may not become inactive for a long + * time after the readdir is over. Case in point, if somebody cd's into + * the directory then it won't become inactive until they cd away again. + * In such a case we would end up with the directory listing not getting + * updated (i.e. the result of 'ls' always being the same) until they + * change the working directory. + */ + sfnode_clear_dir_list(node); + + sfnode_invalidate_stat_cache(node); + + if (node->sf_file != NULL && vp->v_count <= 1) + { + (void)sfprov_close(node->sf_file); + node->sf_file = NULL; + } + + mutex_exit(&sffs_lock); + return (0); +} + +/* ARGSUSED */ +static int +sffs_seek(vnode_t *v, offset_t o, offset_t *no, caller_context_t *ct) +{ + if (*no < 0 || *no > MAXOFFSET_T) + return (EINVAL); + + if (v->v_type == VDIR) + { + sffs_dirents_t *cur_buf = VN2SFN(v)->sf_dir_list; + off_t offset = 0; + + if (cur_buf == NULL) + return (0); + + while (cur_buf != NULL) { + if (*no >= offset && *no <= offset + cur_buf->sf_len) + return (0); + offset += cur_buf->sf_len; + cur_buf = cur_buf->sf_next; + } + return (EINVAL); + } + return (0); +} + + + +/* + * By returning an error for this, we prevent anything in sffs from + * being re-exported by NFS + */ +/* ARGSUSED */ +static int +sffs_fid(vnode_t *vp, fid_t *fidp, caller_context_t *ct) +{ + return (ENOTSUP); +} + +/* + * vnode operations for regular files + */ +const fs_operation_def_t sffs_ops_template[] = { +#if defined(VBOX_VFS_SOLARIS_10U6) + VOPNAME_ACCESS, sffs_access, + VOPNAME_CLOSE, sffs_close, + VOPNAME_CREATE, sffs_create, + VOPNAME_FID, sffs_fid, + VOPNAME_FSYNC, sffs_fsync, + VOPNAME_GETATTR, sffs_getattr, + VOPNAME_INACTIVE, sffs_inactive, + VOPNAME_LOOKUP, sffs_lookup, + VOPNAME_MKDIR, sffs_mkdir, + VOPNAME_OPEN, sffs_open, + VOPNAME_PATHCONF, sffs_pathconf, + VOPNAME_READ, sffs_read, + VOPNAME_READDIR, sffs_readdir, + VOPNAME_READLINK, sffs_readlink, + VOPNAME_REMOVE, sffs_remove, + VOPNAME_RENAME, sffs_rename, + VOPNAME_RMDIR, sffs_rmdir, + VOPNAME_SEEK, sffs_seek, + VOPNAME_SETATTR, sffs_setattr, + VOPNAME_SPACE, sffs_space, + VOPNAME_SYMLINK, sffs_symlink, + VOPNAME_WRITE, sffs_write, + +# ifdef VBOXVFS_WITH_MMAP + VOPNAME_MAP, sffs_map, + VOPNAME_ADDMAP, sffs_addmap, + VOPNAME_DELMAP, sffs_delmap, + VOPNAME_GETPAGE, sffs_getpage, + VOPNAME_PUTPAGE, sffs_putpage, +# endif + + NULL, NULL +#else + VOPNAME_ACCESS, { .vop_access = sffs_access }, + VOPNAME_CLOSE, { .vop_close = sffs_close }, + VOPNAME_CREATE, { .vop_create = sffs_create }, + VOPNAME_FID, { .vop_fid = sffs_fid }, + VOPNAME_FSYNC, { .vop_fsync = sffs_fsync }, + VOPNAME_GETATTR, { .vop_getattr = sffs_getattr }, + VOPNAME_INACTIVE, { .vop_inactive = sffs_inactive }, + VOPNAME_LOOKUP, { .vop_lookup = sffs_lookup }, + VOPNAME_MKDIR, { .vop_mkdir = sffs_mkdir }, + VOPNAME_OPEN, { .vop_open = sffs_open }, + VOPNAME_PATHCONF, { .vop_pathconf = sffs_pathconf }, + VOPNAME_READ, { .vop_read = sffs_read }, + VOPNAME_READDIR, { .vop_readdir = sffs_readdir }, + VOPNAME_READLINK, { .vop_readlink = sffs_readlink }, + VOPNAME_REMOVE, { .vop_remove = sffs_remove }, + VOPNAME_RENAME, { .vop_rename = sffs_rename }, + VOPNAME_RMDIR, { .vop_rmdir = sffs_rmdir }, + VOPNAME_SEEK, { .vop_seek = sffs_seek }, + VOPNAME_SETATTR, { .vop_setattr = sffs_setattr }, + VOPNAME_SPACE, { .vop_space = sffs_space }, + VOPNAME_SYMLINK, { .vop_symlink = sffs_symlink }, + VOPNAME_WRITE, { .vop_write = sffs_write }, + +# ifdef VBOXVFS_WITH_MMAP + VOPNAME_MAP, { .vop_map = sffs_map }, + VOPNAME_ADDMAP, { .vop_addmap = sffs_addmap }, + VOPNAME_DELMAP, { .vop_delmap = sffs_delmap }, + VOPNAME_GETPAGE, { .vop_getpage = sffs_getpage }, + VOPNAME_PUTPAGE, { .vop_putpage = sffs_putpage }, +# endif + + NULL, NULL +#endif +}; + +/* + * Also, init and fini functions... + */ +int +sffs_vnode_init(void) +{ + int err; + + err = vn_make_ops("sffs", sffs_ops_template, &sffs_ops); + if (err) + return (err); + + avl_create(&sfnodes, sfnode_compare, sizeof (sfnode_t), + offsetof(sfnode_t, sf_linkage)); + avl_create(&stale_sfnodes, sfnode_compare, sizeof (sfnode_t), + offsetof(sfnode_t, sf_linkage)); + + sffs_buffer = kmem_alloc(PAGESIZE, KM_SLEEP); + + return (0); +} + +void +sffs_vnode_fini(void) +{ + if (sffs_ops) + vn_freevnodeops(sffs_ops); + ASSERT(avl_first(&sfnodes) == NULL); + avl_destroy(&sfnodes); + if (sffs_buffer != NULL) { + kmem_free(sffs_buffer, PAGESIZE); + sffs_buffer = NULL; + } +} + +/* + * Utility at unmount to get all nodes in that mounted filesystem removed. + */ +int +sffs_purge(struct sffs_data *sffs) +{ + sfnode_t *node; + sfnode_t *prev; + + /* + * Check that no vnodes are active. + */ + if (sffs->sf_rootnode->v_count > 1) + return (-1); + for (node = avl_first(&sfnodes); node; + node = AVL_NEXT(&sfnodes, node)) { + if (node->sf_sffs == sffs && node->sf_vnode && + node->sf_vnode != sffs->sf_rootnode) + return (-1); + } + for (node = avl_first(&stale_sfnodes); node; + node = AVL_NEXT(&stale_sfnodes, node)) { + if (node->sf_sffs == sffs && node->sf_vnode && + node->sf_vnode != sffs->sf_rootnode) + return (-1); + } + + /* + * All clear to destroy all node information. Since there are no + * vnodes, the make stale will cause deletion. + */ + VN_RELE(sffs->sf_rootnode); + mutex_enter(&sffs_lock); + for (prev = NULL;;) { + if (prev == NULL) + node = avl_first(&sfnodes); + else + node = AVL_NEXT(&sfnodes, prev); + + if (node == NULL) + break; + + if (node->sf_sffs == sffs) { + if (node->sf_vnode != NULL) + panic("vboxfs: purge hit active vnode"); + sfnode_make_stale(node); + } else { + prev = node; + } + } + mutex_exit(&sffs_lock); + return (0); +} + +#if 0 +/* Debug helper functions */ +static void +sfnode_print(sfnode_t *node) +{ + Log(("0x%p", node)); + Log((" type=%s (%d)", + node->sf_type == VDIR ? "VDIR" : + node->sf_type == VNON ? "VNON" : + node->sf_type == VLNK ? "VLNK" : + node->sf_type == VREG ? "VREG" : "other", node->sf_type)); + Log((" ino=%d", (uint_t)node->sf_ino)); + Log((" path=%s", node->sf_path)); + Log((" parent=0x%p", node->sf_parent)); + if (node->sf_children) + Log((" children=%d", node->sf_children)); + if (node->sf_vnode) + Log((" vnode=0x%p", node->sf_vnode)); + Log(("%s\n", node->sf_is_stale ? " STALE" : "")); +} + +static void +sfnode_list(void) +{ + sfnode_t *n; + for (n = avl_first(&sfnodes); n != NULL; n = AVL_NEXT(&sfnodes, n)) + sfnode_print(n); + for (n = avl_first(&stale_sfnodes); n != NULL; + n = AVL_NEXT(&stale_sfnodes, n)) + sfnode_print(n); +} +#endif + diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.h b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.h new file mode 100644 index 00000000..04e06225 --- /dev/null +++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.h @@ -0,0 +1,99 @@ +/* $Id: vboxfs_vnode.h $ */ +/** @file + * VirtualBox File System for Solaris Guests, VNode header. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_vnode_h +#define GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_vnode_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <sys/t_lock.h> +#include <sys/avl.h> +#include <vm/seg.h> +#include <vm/seg_vn.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * sfnode is the file system dependent vnode data for vboxsf. + * sfnode's also track all files ever accessed, both open and closed. + * It duplicates some of the information in vnode, since it holds + * information for files that may have been completely closed. + * + * The sfnode_t's are stored in an AVL tree sorted by: + * sf_sffs, sf_path + */ +typedef struct sfnode { + avl_node_t sf_linkage; /* AVL tree linkage */ + struct sffs_data *sf_sffs; /* containing mounted file system */ + char *sf_path; /* full pathname to file or dir */ + uint64_t sf_ino; /* assigned unique ID number */ + vnode_t *sf_vnode; /* vnode if active */ + sfp_file_t *sf_file; /* non NULL if open */ + int sf_flag; /* last opened file-mode. */ + struct sfnode *sf_parent; /* parent sfnode of this one */ + uint16_t sf_children; /* number of children sfnodes */ + uint8_t sf_type; /* VDIR or VREG */ + uint8_t sf_is_stale; /* this is stale and should be purged */ + sffs_stat_t sf_stat; /* cached file attrs for this node */ + uint64_t sf_stat_time; /* last-modified time of sf_stat */ + sffs_dirents_t *sf_dir_list; /* list of entries for this directory */ +} sfnode_t; + +#define VN2SFN(vp) ((sfnode_t *)(vp)->v_data) + +#ifdef _KERNEL +extern int sffs_vnode_init(void); +extern void sffs_vnode_fini(void); +extern sfnode_t *sfnode_make(struct sffs_data *, char *, vtype_t, sfp_file_t *, + sfnode_t *parent, sffs_stat_t *, uint64_t stat_time); +extern vnode_t *sfnode_get_vnode(sfnode_t *); + +/* + * Purge all cached information about a shared file system at unmount + */ +extern int sffs_purge(struct sffs_data *); + +extern kmutex_t sffs_lock; +#endif /* _KERNEL */ + +#ifdef __cplusplus +} +#endif + +#endif /* !GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_vnode_h */ |