summaryrefslogtreecommitdiffstats
path: root/src/VBox/Additions/solaris/SharedFolders
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Additions/solaris/SharedFolders
parentInitial commit. (diff)
downloadvirtualbox-upstream/7.0.14-dfsg.tar.xz
virtualbox-upstream/7.0.14-dfsg.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.kmk113
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/deps.asm49
-rwxr-xr-xsrc/VBox/Additions/solaris/SharedFolders/loadfs.sh105
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs.h106
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_mount.c179
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.c1070
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.h200
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.c641
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.h90
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.c2500
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.h99
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 */